react-nativeWebView返回处理(⾮回调⽅法可解决)
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) { //有很多回调⽅法,此处只举⼀例
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent( //⾃⼰定义的时间,dispatch后,事件会传给js
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) { //有很多回调⽅法,此处只举⼀例
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent( //⾃⼰定义的时间,dispatch后,事件会传给js
createWebViewEvent(webView, url)));
}
@Override
public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) { //坑在这,这⾥就是导航有变化的时候会回调在这个版本是有这个处理的,但是不知道在哪个版本删掉了 -.-super.doUpdateVisitedHistory(webView, url, isReload);
dispatchEvent(
webView,
new TopLoadingStartEvent(
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;
}
react to do@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) {
}
}
View Code
(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();
}
View Code
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={LoadingStart}
onLoadingFinish={LoadingFinish}
onLoadingError={LoadingError}/>;
return (
<View style={ainer}>
{webView}
{otherView}
</View>
)
;
}
onLoadingStart = (event) => {
var onLoadStart = LoadStart;
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 (NavigationStateChange) {
NavigationStateChange(event.nativeEvent);
}
};
}
var RCTWebView = requireNativeComponent('RCTWebView', WebView, { //对应上⾯JAVA中的 ‘RCTWebView’
nativeOnly: { messagingEnabled: PropTypes.bool, }, });
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(
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) {
}
}
View Code
新建 ReactWebViewPage.java
public class ReactWebViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { ptyList();
}
@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={LoadingStart}
onLoadingFinish={LoadingFinish}
onLoadingError={LoadingError}
onChange={Change} //添加⽅法
/>;
return (
<View style={ainer}>
{webView}
{otherView}
</View>
);
}
onChange = (event) => { //添加⽅法
this.updateNavigationState(event);
};
}
var RCTWebView = requireNativeComponent('RCTWebView1', CustomWebView,
traNativeComponentConfig); //修改名称ports = CustomWebView; //修改名称
View Code
⾄此就完成⾃定义WebView模块。也可以解决⽹页是React实现,不能导航的问题。
不善排版,看不懂的可留⾔
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论