react-native WebView 返回处理 (非回调方法可解决)

1.前言

项目中有些页面内容是变更比较频繁的,这些页面我们会考虑用网页来解决。

在RN项目中提供一个公用的Web页,如果是网页内容,就跳转到这个界面展示。

此时会有一个问题是,网页会有一级页面,二级页面,这就会设计到导航栏返回键的处理(以及在Android上返回键的处理)。

这个问题,在RN官网就可找到解决方式。就是用onNavigationStateChange这个回调方法记录当前的导航状态,从而判断是返回上一级页面还是退出这个网页,回到App的其他界面。

但是,当网页的实现是React时,就会有问题了,你会发现,当页面跳转的时候,onNavigationStateChange这个回调方法没有回调!!!怎么肥四!!

一开始尝试了把网页地址换成百度的,可以接收回调,一切都运行的很好,可是换成我们的链接就不行,所以就把锅甩给了后台,以为是React哪边写的不对。

因为上一个项目时间紧,没有时间好好去看一下源码,就想了一个不是很完善的解决方案,就是网页用js来回调App来告知现在的导航状态,这样的解决方式显示是不友好的。

现在稍微有点时间看了源码才知道真正原因。

2.原因

下面就分析一下这个问题的原因和我的解决方式。

1.首先,先找到源码的位置。

node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\views\webview

node_modules\react-native\Libraries\Components\WebView

目录结构是这样的:      

2.实现的代码段 (JAVA端)

RN的实际运行代码都是原生代码,所以,像WebView组件的一些事件回调,其实都是原生代码中的回调触发的。如下

(ReactWebViewManager.java) rn版本0.47.1

  protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我们在写Android原生代码时,监听网页加载情况使用的工具。
      protected static final String REACT_CLASS = "RCTWebView"; //定义的原生组件名,在后面JS中会对应到。

    //...

    @Override
    public void onPageStarted(WebView webView, String url, Bitmap favicon) {  //有很多回调方法,此处只举一例
      super.onPageStarted(webView, url, favicon);
      mLastLoadFailed = false;

      dispatchEvent(
          webView,
          new TopLoadingStartEvent(      //自己定义的时间,dispatch后,事件会传给js
              webView.getId(),
              createWebViewEvent(webView, url)));
    }

    //...
 }

(ReactWebViewManager.java) rn版本0.43.3  ,RN不同版本会有代码调整,所以RN升级的时候,需要仔细的回归测试。

protected static class ReactWebViewClient extends WebViewClient { //WebViewClient就是我们在写Android原生代码时,监听网页加载情况使用的工具。
      protected static final String REACT_CLASS = "RCTWebView"; //定义的原生组件名,在后面JS中会对应到。

    //...

    @Override
    public void onPageStarted(WebView webView, String url, Bitmap favicon) {  //有很多回调方法,此处只举一例
      super.onPageStarted(webView, url, favicon);
      mLastLoadFailed = false;

      dispatchEvent(
          webView,
          new TopLoadingStartEvent(      //自己定义的时间,dispatch后,事件会传给js
              webView.getId(),
              createWebViewEvent(webView, url)));
    }

    @Override
    public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {  //坑在这,这里就是导航有变化的时候会回调在这个版本是有这个处理的,但是不知道在哪个版本删掉了 -.-
      super.doUpdateVisitedHistory(webView, url, isReload);

      dispatchEvent(
          webView,
          new TopLoadingStartEvent(
              webView.getId(),
              createWebViewEvent(webView, url)));
    }

    //...
 }

(TopLoadingStartEvent.java) 回调JS的Event

public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {

  public static final String EVENT_NAME = "topLoadingStart";   //对应方法是onLoadingStart, 因为对RN的结构不熟悉,在此处花了很长时间研究是怎么对应的,最后找到了定义对应的文件
  private WritableMap mEventData;

  public TopLoadingStartEvent(int viewId, WritableMap eventData) {
    super(viewId);
    mEventData = eventData;
  }

  @Override
  public String getEventName() {
    return EVENT_NAME;
  }

  @Override
  public boolean canCoalesce() {
    return false;
  }

  @Override
  public short getCoalescingKey() {
    // All events for a given view can be coalesced.
    return 0;
  }

  @Override
  public void dispatch(RCTEventEmitter rctEventEmitter) {
    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
  }
}

(node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\uimanager\UIManagerModuleConstants.java)

这个文件里,定义了对应关系

/**
 * Constants exposed to JS from {@link UIManagerModule}.
 */
/* package */ class UIManagerModuleConstants {

  /* package */ static Map getDirectEventTypeConstants() {
    return MapBuilder.builder()
        .put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange"))
        .put("topLayout", MapBuilder.of("registrationName", "onLayout"))
        .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError"))
        .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
        .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
        .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
        .put("topMessage", MapBuilder.of("registrationName", "onMessage"))
        .build();
  }

}

3.实现的代码段 (JS端)

(node_modules\react-native\Libraries\Components\WebView\WebView.android.js)

在下面的代码中可以看到只有 onLoadingStart   和 onLoadingFinish才会调用 updateNavigationState,问题就出现在这了,由于我们的网页实现是React,只有一个页面啊!所以只会调用一次onLoadingStart  和onLoadingFinish。再点击详情页并不会跳转到新页面,而是刷新原来的页面。所以也就没有updateNavigationState回调了。

class WebView extends React.Component {
  static propTypes = {    //给外部定义的可设置的属性
    ...ViewPropTypes,
    renderError: PropTypes.func,
    renderLoading: PropTypes.func,
    onLoad: PropTypes.func,
    //...
   }

  render() {  //绘制页面内容
    //...
    var webView =
      <RCTWebView
        ref={RCT_WEBVIEW_REF}
        key="webViewKey"
        style={webViewStyles}
        source={resolveAssetSource(source)}
        onLoadingStart={this.onLoadingStart}
        onLoadingFinish={this.onLoadingFinish}
        onLoadingError={this.onLoadingError}/>;

    return (
      <View style={styles.container}>
        {webView}
        {otherView}
      </View>
    );
  }

  onLoadingStart = (event) => {
    var onLoadStart = this.props.onLoadStart;
    onLoadStart && onLoadStart(event);
    this.updateNavigationState(event);
  };

  onLoadingFinish = (event) => {
    var {onLoad, onLoadEnd} = this.props;
    onLoad && onLoad(event);
    onLoadEnd && onLoadEnd(event);
    this.setState({
      viewState: WebViewState.IDLE,
    });
    this.updateNavigationState(event);
  };

  updateNavigationState = (event) => {
    if (this.props.onNavigationStateChange) {
      this.props.onNavigationStateChange(event.nativeEvent);
    }
  };
}

var RCTWebView = requireNativeComponent(‘RCTWebView‘, WebView, {    //对应上面JAVA中的 ‘RCTWebView’
 nativeOnly: { messagingEnabled: PropTypes.bool, }, });

 module.exports = WebView;  

2.解决方法

既然原因找到了,就容易解决了

解决方式:自定义WebView,添加 doUpdateVisitedHistory 处理,在每次导航变化的时候,通知JS。

1. 拷贝下图中的文件到我们自己项目中的Android代码目录下

拷贝完后的Android目录:

  • ReactWebViewManager.java中需要修改几个地方

public class ReactWebViewManager extends SimpleViewManager<WebView> {
  protected static final String REACT_CLASS = "RCTWebView1";  //此处修改一下名字

  protected static class ReactWebViewClient extends WebViewClient {
        @Override
        public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
            super.doUpdateVisitedHistory(webView, url, isReload);

            dispatchEvent(       //在导航变化的时候,dispatchEvent
                    webView,
                    new TopCanGoBackEvent(
                            webView.getId(),
                            createCanGoBackWebViewEvent(webView, url)));
        }
  }
}

  • TopCanGoBackEvent是我自己添加的一个Event,专门用来通知导航变化

TopCanGoBackEvent.java

public class TopCanGoBackEvent extends Event<TopCanGoBackEvent> {

  public static final String EVENT_NAME = "topChange";
  private WritableMap mEventData;

  public TopCanGoBackEvent(int viewId, WritableMap eventData) {
    super(viewId);
    mEventData = eventData;
  }

  @Override
  public String getEventName() {
    return EVENT_NAME;
  }

  @Override
  public boolean canCoalesce() {
    return false;
  }

  @Override
  public short getCoalescingKey() {
    // All events for a given view can be coalesced.
    return 0;
  }

  @Override
  public void dispatch(RCTEventEmitter rctEventEmitter) {
    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
  }
}

  • 新建 ReactWebViewPage.java

public class ReactWebViewPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new ReactWebViewManager()
        );
    }
}

  • 然后在MainApplication中添加这个模块

public class MainApplication extends Application implements ReactApplication {
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new ReactWebViewPackage()    //WebView
      );
    }
}

以上就是Android需要修改的地方,ios我没有尝试过,应该大差不差同一个道理。

2. 拷贝下图中的文件到我们自己项目中的JS代码目录下,并修改一下名字

JS代码目录:

    • CustomWebView.android.js 有几个地方需要修改。

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule CustomWebView    //此处需要修改名称
 */

var RCT_WEBVIEW_REF = ‘webview1‘;  //此处需要修改名称

  render() {
    var webView =
      <NativeWebView
        onLoadingStart={this.onLoadingStart}
        onLoadingFinish={this.onLoadingFinish}
        onLoadingError={this.onLoadingError}
        onChange={this.onChange} //添加方法
      />;

    return (
      <View style={styles.container}>
        {webView}
        {otherView}
      </View>
    );
  }

  onChange = (event) => {    //添加方法
    this.updateNavigationState(event);
  };
}

var RCTWebView = requireNativeComponent(‘RCTWebView1‘, CustomWebView, CustomWebView.extraNativeComponentConfig);  //修改名称

module.exports = CustomWebView;  //修改名称

至此就完成自定义WebView模块。也可以解决网页是React实现,不能导航的问题。

不善排版,看不懂的可留言

原文地址:https://www.cnblogs.com/zhangxinyan/p/8459487.html

时间: 2024-10-23 13:30:07

react-native WebView 返回处理 (非回调方法可解决)的相关文章

React Native导航Navigator组件基本使用方法

最近在学React Native,了解了一个原本iOS中非常重要的导航控件的使用方法.不过在React Nativa中,这个导航控件是不会自带顶部的导航栏的,也不会自动生成返回按钮之类的,只是提供了类似的导航功能,且原理也是出栈入栈的方式,也就是说同样是有着push和pop方法的.这里不讲React Native的基础了,直接讲一讲Navigator这个组件的基本使用方法. 对于一个导航组件,最基本的就是下面几个点: 进入下一个界面 返回上一个界面 传递数据给下一个界面 返回数据给上一个界面 我

关于React Native init 项目时候速度太慢的解决方法

因为init项目的时候需要下载资源,但又因为react native的网站被墙所以下载很慢,解决方法就是换成淘宝的NPM镜像 我是直接使用了命令去替换了NPM $ npm install -g cnpm --registry=https://registry.npm.taobao.org 这样一来,就会快很多了,实测,大概3分钟. 淘宝NPM地址:https://npm.taobao.org 如有错误,恳请指出.

React Native Expected a component class,got [object Object]解决

报错原因: 组件大小写错误. 解决方式: 修改组件名称即可. 这篇博客介绍了大部分RN的错误原因和解决方法: http://blog.csdn.net/chichengjunma/article/details/52943013

React Native Android 应用层实战沦陷记

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 一眨眼又一年快要过去了,原计划今年的最后一个小目标(React Native)看样子要留尾巴到明年了,React Native 想说爱你不容易.怎么评价你呢?应用层 JSX 编写还是很友好的,尼玛框架接入的各种锅却让人痛哭不已,万事开头难,对于 React Native 的接入可以说大量工作可能都需要投入到框架接入中(各种灰度实验的兼容性.各种锅),一旦接入稳定以

React Native 开发小Tips

相信好多写React Native的都是前端出身,当然遇见问题的,也很多时候会想从前端出发,但由于React Native本身的限制,并不是支持足够多的属性和样式,所以Bo主结合自己的开发实践,并总结了一些将来开发可能会遇见的问题并给出一些小的代码参考;(PS实现不好的希望能大家提出看法,自己也会更新). 自己将代码放到了 example 下,并且做成了一个App.这样可以查看具体运行效果: 截图1: 截图2: 项目地址 开始 git clone https://github.com/JackP

python基础之多态与多态性、绑定方法和非绑定方法

多态与多态性 多态 多态并不是一个新的知识 多态是指一类事物有多种形态,在类里就是指一个抽象类有多个子类,因而多态的概念依赖于继承 举个栗子:动物有多种形态,人.狗.猫.猪等,python的序列数据类型有字符串.列表.元组,文件的类型分为普通文件和可执行文件,人类又有多种形态,男女老少..等等例子 1 import abc 2 class Animal(metaclass=abc.ABCMeta): #模拟动物类 3 @abc.abstractmethod 4 def talk(self): 5

react native 入门 (1)- 环境搭建, 创建第一个Hello World

Create React Native App 是开始构建新的React Native应用程序的最简单方法.它允许您启动项目而无需安装或配置任何工具来构建本机代码 - 无需安装Xcode或Android Studio. 先安装Node.Js,则可以使用npm来安装create-react-native-app命令行实用程序: (NPM的全称是Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准), npm install -

react native 之 Android物理返回键

基本用法 根据文档,安卓back键的处理主要就是一个事件监听: 1 BackAndroid.addEventListener('hardwareBackPress', this.onBackPressed); 2 BackAndroid.removeEventListener('hardwareBackPress', this.onBackPressed); 在starter-kit里,我们在App这一级别,实现了按back键回退导航栈的功能: 1 class App extends React

【React Native开发】React Native控件之WebView组件详解以及实例使用(22)

转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50676379 本文出自:[江清清的博客] (一)前言 [好消息]个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org 今天我们一起来看一下WebView组件讲解以及使用实例 刚创建的React Native技术交流群(282693535),欢迎各位大牛,React Native技术爱好者加入交流!同