欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-02 14:46:06
1.前言 项目中有些页面内容是变更比较频繁的,这些页面我们会考虑用 网页 来解决。 在rn项目中提供一个公用的web页,如果是网页内容,就跳转到这个界面展示。 此时会...

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

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