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

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

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

  1. React Native WebView关闭缓存

    React Native WebView关闭缓存 网上搜索没有找到关闭React Native下webview控件的缓存的方法,经测试找到解决方案,记录如下 核心思路:通过请求时设置请求头,使页面缓存 ...

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

    因为init项目的时候需要下载资源,但又因为react native的网站被墙所以下载很慢,解决方法就是换成淘宝的NPM镜像 我是直接使用了命令去替换了NPM $ npm install -g cnp ...

  3. react native webview 不能滑动页面

    用RN 写Android的时候,webview点击正常,但是不能滑动,或者滑动很艰难.调试不报错,inspect一切正常. 遍查网络无良方, 最后发现其他同事在最外层套了个这东西: Touchable ...

  4. react组件中返回并列元素的方法

    我们在写react组件的时候,经常会遇到这种问题,在render中return元素只能有一个顶级元素,比如div,假如写成这样就会报错: render(){ return( <div>12 ...

  5. shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决(XML配置)

    在使用Shiro框架进行项目整合时,使用注解在使用Shiro框架进行项目整合时,使用注解在使用Shiro框架进行项目整合时,使用注解@RequiresPermissions为方法提供是需要的权限,但是 ...

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

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

  7. React Native入门指南

    转载自:http://www.jianshu.com/p/b88944250b25 前言 React Native 诞生于 2015 年,名副其实的富二代,主要使命是为父出征,与 Apple 和 Go ...

  8. 写给移动开发者的 React Native 指南

    本文原创版权归 简书 wingjay 所有,如有转载,请于文章篇头位置显示标注原创作者及出处,以示尊重! 作者:wingjay 出处:http://www.jianshu.com/p/b8894425 ...

  9. React Native桥接器初探

    本文假设你已经有一定的React Native基础,并且想要了解React Native的JS和原生代码之间是如何交互的. React Native的工作线程 shadow queue:布局在这个线程 ...

随机推荐

  1. Mysql开启远程连接方法

    分类: 数据库开发技术 解决MySQL不允许从远程访问的方法 开启 MySQL 的远程登陆帐号有两大步: 1.确定服务器上的防火墙没有阻止 3306 端口. MySQL 默认的端口是 3306 ,需要 ...

  2. 从#65279字符看dede模板页面编码问题

    今天一位朋友让帮忙给解决一个dede模板的问题,问题主要是:模板文件生成html文件之后会在body开头处加入一个可见的控制符&#65279,导致页面头部会出现一个空白行. 接到"& ...

  3. 谈谈Python、Java与AI

    Python好像天生是为AI而生的,随着AI的火热,特别是用Python写的TensorFlow越来越火,Python的热度越来越高,就像当年Java就是随着互联网火起来的感觉.在我的工作中,Pyth ...

  4. 单词拼写检查之cutoff距离

    前言 cutoff是一个比较冷门的概念,相比于DP经典算法的编辑距离,cutoff距离只局限于自然语言处理领域.提出cutoff距离的起因很简单,因为经典的编辑距离无法很好地衡量在字符串搜索过程中的编 ...

  5. python3 第十八章 - 迭代器与生成器

    1.迭代器(Iterator) 迭代是访问集合元素的一种方式 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退. 迭代器 ...

  6. 1 let和const

    let命令 1)let声明的变量只在let命令所在的代码块内有效,如:   { let a = ; ;} a // ReferenceError: a is not defined. b 对于for循 ...

  7. kindeditor使用

    下载地址http://kindeditor.net/down.php @官方文档 使用步骤: 引入js <script charset="utf-8" src="r ...

  8. linkinFrame--web应用举例--准备工作

    OK,前面的博客我们已经搭好了linkinFrame的项目结构,现在我们这里添加一个简单的web应用,在编写此web应用的过程中我们来一步一步的搭好自己的框架. 现在开始,这里举一个例子,一个客户的C ...

  9. junit测试延伸--私有方法测试

    关于junit测试的延伸,这里有类概念级别的测试,继承类的测试,接口的测试,抽象类的测试,关于这些类级别的测试,这里我就不做多的赘述了. 关于上面的几个测试就是说,我们不应该单纯的去测试类中的一些方法 ...

  10. Linux下安装mysql(yum和源码编译两种方式)

    这里介绍Linux下两种安装mysql的方式:yum安装和源码编译安装. 1. yum安装 (1)首先查看centos自带的mysql是否被安装: # yum list installed |grep ...