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
//...
@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升级的时候,需要仔细的回归测试。
//...
@Override
public void onPageStarted(WebView webView,url)));
}
@Override
public void doUpdateVisitedHistory(WebView webView,boolean isReload) { //坑在这,这里就是导航有变化的时候会回调在这个版本是有这个处理的,但是不知道在哪个版本删掉了 -.-
super.doUpdateVisitedHistory(webView,isReload);
dispatchEvent(
webView,new TopLoadingStartEvent(
webView.getId(),url)));
}
//...
}
(TopLoadingStartEvent.java) 回调JS的Event
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)
这个文件里,定义了对应关系
return MapBuilder.builder()
.put("topContentSizeChange",MapBuilder.of("registrationName","onContentSizeChange"))
.put("topLayout","onLayout"))
.put("topLoadingError","onLoadingError"))
.put("topLoadingFinish","onLoadingFinish"))
.put("topLoadingStart","onLoadingStart"))
.put("topSelectionChange","onSelectionChange"))
.put("topMessage","onMessage"))
.build();
}
}
3.实现的代码段 (JS端)
(node_modules\react-native\Libraries\Components\WebView\WebView.android.js)
在下面的代码中可以看到只有 onLoadingStart 和 onLoadingFinish 才会调用 updateNavigationState ,问题就出现在这了,由于我们的网页实现是React,只有一个页面啊!所以只会调用一次 onLoadingStart 和 onLoadingFinish 。再点击详情页并不会跳转到新页面,而是刷新原来的页面。所以也就没有 updateNavigationState 回调了。
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 (
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中需要修改几个地方
protected static class ReactWebViewClient extends WebViewClient {
@Override
public void doUpdateVisitedHistory(WebView webView,boolean isReload) {
super.doUpdateVisitedHistory(webView,isReload);
dispatchEvent( //在导航变化的时候,dispatchEvent
webView,new TopCanGoBackEvent(
webView.getId(),createCanGoBackWebViewEvent(webView,url)));
}
}
}
TopCanGoBackEvent是我自己添加的一个Event,专门用来通知导航变化
TopCanGoBackEvent.java
public static final String EVENT_NAME = "topChange";
private WritableMap mEventData;
public TopCanGoBackEvent(int viewId,mEventData);
}
}
新建 ReactWebViewPage.java
public List
return Collections.emptyList();
}
@Override
public List
return Arrays.
new ReactWebViewManager()
);
}
}
然后在MainApplication中添加这个模块
以上就是Android需要修改的地方,ios我没有尝试过,应该大差不差同一个道理。
2. 拷贝下图中的文件到我们自己项目中的JS代码目录下,并修改一下名字
JS代码目录:
CustomWebView.android.js 有几个地方需要修改。
var RCT_WEBVIEW_REF = 'webview1'; //此处需要修改名称
render() {
var webView =
<NativeWebView
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
onChange={this.onChange} //添加方法
/>;
return (
onChange = (event) => { //添加方法
this.updateNavigationState(event);
};
}
var RCTWebView = requireNativeComponent('RCTWebView1',CustomWebView,CustomWebView.extraNativeComponentConfig); //修改名称