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

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

  • 相关阅读:
    (转)修改Android解锁界面
    linux terminal 快捷键
    (转)Android蓝牙开发浅析
    (转)android 编译单个模块
    (转)Android关机AppWidget的实现
    MyEclipse修改页面模板(JSP和HTML等) 分类: WEB项目应用 20100131 00:03 698人阅读 评论(1) 收藏
    定时任务:Timer类、TimerTask类 分类: java 20100317 22:01 551人阅读 评论(0) 收藏
    Socket读取输入流 分类: java 20100322 17:38 3330人阅读 评论(0) 收藏
    [Microsoft][SQLServer 2000 Driver for JDBC]Object has been closed. 分类: 开发常见问题解决方案 20100318 22:05 1106人阅读 评论(0) 收藏
    3/17/10 9:55:59 AM CST: [INFO] User settings file does not exist C:/Documents and Settings/Administrator/.m2/settings.xml 分类: 开发常见问题解决方案 20100317 10:20 3448人阅读 评论(4) 收藏
  • 原文地址:https://www.cnblogs.com/zhangxinyan/p/8459487.html
Copyright © 2020-2023  润新知