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 返回处理 (非回调方法可解决)
原文地址:https://www.cnblogs.com/zhangxinyan/p/8459487.html