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


Posted in Javascript onFebruary 27, 2018

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

目录结构是这样的:

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

详解react-native 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代码目录下

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

拷贝完后的Android目录:

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

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代码目录下,并修改一下名字

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

JS代码目录:

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

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实现,不能导航的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js下拉菜单语言选项简单实现
Sep 23 Javascript
js数组循环遍历数组内所有元素的方法
Jan 18 Javascript
单击和双击事件的冲突处理示例代码
Apr 03 Javascript
通过node-mysql搭建Windows+Node.js+MySQL环境的教程
Mar 01 Javascript
基于Bootstrap里面的Button dropdown打造自定义select
May 30 Javascript
js中判断变量类型函数typeof的用法总结
Aug 09 Javascript
bootstrap table 表格中增加下拉菜单末行出现滚动条的快速解决方法
Jan 05 Javascript
利用Angular2的Observables实现交互控制的方法
Dec 27 Javascript
vue2.0+SVG实现音乐播放圆形进度条组件
Sep 21 Javascript
浅谈webpack构建工具配置和常用插件总结
May 11 Javascript
JS字符串和数组如何实现相互转化
Jul 02 Javascript
jQuery实现滑动开关效果
Aug 02 jQuery
Vue2.5通过json文件读取数据的方法
Feb 27 #Javascript
vue2.5.2使用http请求获取静态json数据的实例代码
Feb 27 #Javascript
jQuery幻灯片插件owlcarousel参数说明中文文档
Feb 27 #jQuery
关于ES6箭头函数中的this问题
Feb 27 #Javascript
vue中echarts3.0自适应的方法
Feb 26 #Javascript
swiper 解决动态加载数据滑动失效的问题
Feb 26 #Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
Feb 26 #Javascript
You might like
复杂检索数据并分页显示的处理方法
2006/10/09 PHP
一组PHP加密解密函数分享
2014/06/05 PHP
PHP连接操作access数据库实例
2015/03/30 PHP
PHP批斗大会之缺失的异常详解
2019/07/09 PHP
Array对象方法参考
2006/10/03 Javascript
国外的为初学者写的JavaScript教程
2008/06/09 Javascript
javascript prototype 原型链
2009/03/12 Javascript
jquery遍历筛选数组的几种方法和遍历解析json对象
2013/12/13 Javascript
Iframe实现跨浏览器自适应高度解决方法
2014/09/02 Javascript
javascript实现完美拖拽效果
2015/05/06 Javascript
javascript实现dom动态创建省市纵向列表菜单的方法
2015/05/14 Javascript
基于jQuery实现歌词滚动版音乐播放器的代码
2016/09/17 Javascript
jquery对Json的各种遍历方法总结(必看篇)
2016/09/29 Javascript
video.js 一个页面同时播放多个视频的实例代码
2018/11/27 Javascript
详解小程序设置缓存并且不覆盖原有数据
2019/04/15 Javascript
vue 解除鼠标的监听事件的方法
2019/11/13 Javascript
如何基于javascript实现贪吃蛇游戏
2020/02/09 Javascript
微信jssdk踩坑之签名错误invalid signature
2020/05/19 Javascript
[53:43]VP vs NewBee Supermajor 胜者组 BO3 第三场 6.5
2018/06/06 DOTA
Python编程中time模块的一些关键用法解析
2016/01/19 Python
举例讲解Python编程中对线程锁的使用
2016/07/12 Python
基于Python代码编辑器的选用(详解)
2017/09/13 Python
Python 忽略warning的输出方法
2018/10/18 Python
python3.6数独问题的解决
2019/01/21 Python
python子线程退出及线程退出控制的代码
2019/10/16 Python
pytorch 指定gpu训练与多gpu并行训练示例
2019/12/31 Python
python numpy矩阵信息说明,shape,size,dtype
2020/05/22 Python
python使用matplotlib:subplot绘制多个子图的示例
2020/09/24 Python
python实现图像高斯金字塔的示例代码
2020/12/11 Python
美国户外运动商店:Sun & Ski
2018/08/23 全球购物
英国文具、办公用品和科技商店:Ryman
2018/09/27 全球购物
阿拉伯时尚购物网站:Nisnass
2021/02/07 全球购物
艺术系应届生的自我评价
2013/10/19 职场文书
钳工实训报告总结
2014/11/04 职场文书
2014年教师个人工作总结
2014/11/10 职场文书
员工试用期工作总结
2019/06/20 职场文书