zoukankan      html  css  js  c++  java
  • react-native WebView 返回处理 (非回调方法可解决)

    1.前言 

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

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

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

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

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

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

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

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

    2.原因

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

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

       node_modules eact-nativeReactAndroidsrcmainjavacomfacebook eactviewswebview

       node_modules eact-nativeLibrariesComponentsWebView

            

       目录结构是这样的:      

    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)));
        }
    
        //...
     }
    View Code

     (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)));
        }
    
        //...
     }
    View Code

     (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);
      }
    }
    View Code

    (node_modules eact-nativeReactAndroidsrcmainjavacomfacebook eactuimanagerUIManagerModuleConstants.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();
      }
    
    }
    View Code

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

    (node_modules eact-nativeLibrariesComponentsWebViewWebView.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;  
    View Code

    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)));
            }
      }
    }
    View Code
    • 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);
      }
    }
    View Code

     

     

    • 新建 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()
            );
        }
    }
    View Code
    • 然后在MainApplication中添加这个模块
    public class MainApplication extends Application implements ReactApplication {
        @Override
        protected List<ReactPackage> getPackages() {
          return Arrays.<ReactPackage>asList(
              new MainReactPackage(),
              new ReactWebViewPackage()    //WebView
          );
        }
    }
    View Code

           以上就是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;  //修改名称
    View Code

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

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

  • 相关阅读:
    Java.io.outputstream.PrintStream:打印流
    Codeforces 732F. Tourist Reform (Tarjan缩点)
    退役了
    POJ 3281 Dining (最大流)
    Light oj 1233
    Light oj 1125
    HDU 5521 Meeting (最短路)
    Light oj 1095
    Light oj 1044
    HDU 3549 Flow Problem (dinic模版 && isap模版)
  • 原文地址:https://www.cnblogs.com/zhangxinyan/p/8459487.html
Copyright © 2011-2022 走看看