前言:因为刚换工作的原因,好久没写博客了,目前一直在做跟rn相关的东西,android已经停滞快半年了,h5也差不多,说多了感觉心累啊,有些东西你不碰的话还真会忘记,真是二者不可兼得额,废话不多说了,最近在看rn的webview,很听很多小伙伴说了,rn的webview很多坑,特别是android!!好吧~那我们就带着小伙伴的需求跟坑一步一步的解析下源码~~
一、获取webview的加载进度,rn中用进度条显示
做过原生android的都知道,如果要监听webview的progress只需要进行如下操作就可以了:
private class MyWebCromeClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
//加载完毕进度条消失
progressView.setVisibility(View.GONE);
} else {
//更新进度
progressView.setProgress(newProgress);
}
super.onProgressChanged(view, newProgress);
}
}
是的,在android原生中很简单,但是rn的webview并没有给我们提供这样的需求,看rn的webview源码我们可以看到就给我们提供了两个方法:
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
所以这时有小伙伴说了,rn原生webview很坑!!于是马上转换思路了,rn原生没有这方法,那只有让原生写个页面了,rn直接跳转过去,是的,没错!可以这样做,但是就失去一个rn app的含义了,然后还会牵扯出一堆原生路由跟rn路由的坑~ 所以我们得换一个思路了,既然rn的webview没有这方法,我们可以重写一个叫webview2的组件。
第一种解决方法我就不说了~
第二种解决方法:
第一步:我们直接copy一个rn的webview叫CusWebView.js:
(/node_modules/react-native/Libraries/Components/WebView/WebView.android.js)
copy完后,我们把原本的这行代码:
var RCTWebView = requireNativeComponent('RCTWebView', WebView1, WebView1.extraNativeComponentConfig);
改为:
var RCTWebView = requireNativeComponent('RCTWebView1', WebView1, WebView1.extraNativeComponentConfig);
可以看到,我们只是把RCTWebView改为了RCTWebView1,然而这个RCTWebView1是哪来的呢?对的!原生过来的,所以接下来我们去操作一波android。
copy一下源码这两个文件:
然后copy两份到我们的工程目录下,我们修改下ReactWebViewManager,为起添加进度条监听:
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
。。。。
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
});
可以看到,我们已经拿到我们的进度newProgress了,所以我们只需要在这个方法回调rn就可以了,那rn组件跟native组件怎么通信呢?(通信原理我后面会一步一步解析)
我们创建一个ProgressMessageEvent.java文件:
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* <p>
* 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.
*/
package com.webviewdemo.webview;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Event emitted when there is an error in loading.
*/
public class ProgressMessageEvent extends Event<ProgressMessageEvent> {
public static final String EVENT_NAME = "progressMessage";
private double mData;
public ProgressMessageEvent(int viewId, double data) {
super(viewId);
mData = data;
}
@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) {
WritableMap data = Arguments.createMap();
data.putDouble("data", mData);
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
}
}
然后修改下ReactWebViewManager.java的onProgress方法:
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
。。。。。。
@Override
public void onProgressChanged(WebView view, int newProgress) {
ReactWebViewManager.this.progress = newProgress;
//发消息到rn端
dispatchEvent(view,new ProgressMessageEvent(view.getId(),newProgress));
super.onProgressChanged(view, newProgress);
}
});
最后在ReactWebViewManager.java类中重写下getExportedCustomDirectEventTypeConstants方法:
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("progressMessage", MapBuilder.of("registrationName", "onProgress"))
.build();
}
发消息到rn端大概流程就是,native 端发送一个event—>native端会通过组件id找到该组件—>通过event的事件名字(progressMessage)匹配到registrationName对应的onProgress名字–>拿到rn端注册的方法名onProgress—>调用rn端的onProgress方法
然后我们再创建一个ReactWebViewPackage.java文件把ReactWebViewManager放进去:
package com.webviewdemo.webview;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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());
}
}
然后我们把ReactWebViewPackage.java添加进application:
package com.webviewdemo;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.webviewdemo.webview.ReactWebViewPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactWebViewPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
然后我们回到rn端,给CusWebView.js提供一个onProgress方法:
<NativeWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
。。。。。。
onProgress={(event) => {
this.props.onProgress&&this.props.onProgress(event.nativeEvent.data);
}}
。。。。。。
{...nativeConfig.props}
/>;
最后在我们的App.js文件中引用我们的CusWebview,并加一个进度条:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
ProgressBarAndroid,
} from 'react-native';
import CusWebView from './app/CusWebView';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
export default class App extends Component<{}> {
state = {
progress: 0,
};
render() {
return (
<View style={styles.container}>
<Text>sssss</Text>
<ProgressBarAndroid
style={{width: '100%'}}
indeterminate={false}
progress={this.state.progress/100}
styleAttr={'Horizontal'}
/>
<CusWebView
style={styles.instructions}
source={{uri: 'http://blog.csdn.net/vv_bug/article'}}
startInLoadingState={true}
automaticallyAdjustContentInsets={false}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
scalesPageToFit={true}
onProgress={(progress) => {
if (this.state.progress !== progress) {
this.setState({progress});
}
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
width: '100%',
flex: 1,
},
});
最后运行我们的代码:
好啦~! 到这我们就实现了我们的一个小小功能了
总结一下吧:
rn这东西吧,fb不可能满足我们所有的业务需求,遇到问题一定要先尝试一下,其实真正去做的时候也没有我们想象中的那么难,好啦!不装逼了,下面还会继续讲webview的一些坑和一些项目中遇到的业务需求,今天先到这里了~~
欢迎入群,欢迎交流~~~