前言
基于React-Native0.41及0.25兩個版本來分析
公司項目基于ReactNative開發(fā),最近有個需求為嵌入Web頁面至應(yīng)用中,分析需求發(fā)現(xiàn)技術(shù)實現(xiàn)中涉及了Web頁面與app代碼之間的交互,這對于原生來說實現(xiàn)并不難,但對于接觸不久的RN來說,實現(xiàn)起來有點難度。下面結(jié)合React-Native WebView API來分析WebView的使用及簡單的交互。
以下內(nèi)容包括:
- React-Native WebView API 屬性介紹
- webview 實現(xiàn)與RN代碼簡單交互
- 在Android原生代碼中對ReactNative WebView控件進行初始設(shè)置
React-Native WebView
首先結(jié)合React-Native 高版本與低版本(0.41.2 與 0.25.1)分析其RN源碼(偏向于Android方向)及api
WebView
WebView 作為一個RN組件也是有其生命周期方法,翻開源碼,查看其render方法,其return返回值如下:
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
從此處可以看出,WebView只是一層殼,其包括了兩層覆蓋的View,其中{webView}為RCTWebView組件,映射原生RCTWebView組件,是真正加載web頁面的組件,{otherView}分析其構(gòu)造可以發(fā)現(xiàn)它主要用來渲染加載失敗視圖及加載中的提示視圖。
{webView}
render中定義webView變量的代碼如下(高版本低版本部分屬性有所出入):
var webView =
<RCTWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
source={resolveAssetSource(source)}
scalesPageToFit={this.props.scalesPageToFit}
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabled={this.props.javaScriptEnabled}
domStorageEnabled={this.props.domStorageEnabled}
messagingEnabled={typeof this.props.onMessage === 'function'}
onMessage={this.onMessage}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onContentSizeChange={this.props.onContentSizeChange}
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
testID={this.props.testID}
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
allowUniversalAccessFromFileURLs={this.props.allowUniversalAccessFromFileURLs}
/>;
<RCTWebView/> 所設(shè)置的屬性在webView Api幾乎都有介紹,但部分屬性卻沒有說明,大概分析下:
- scalesPageToFit bool
其對應(yīng)Android端橋接方法為:
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(WebView view, boolean enabled) {
view.getSettings().setUseWideViewPort(!enabled);//android原生WebView設(shè)置此屬性,可任意比例縮放
}
由此可知,設(shè)置是否要把網(wǎng)頁縮放到適應(yīng)視圖的大小,以及是否允許用戶改變縮放比例。RN實現(xiàn)中其值默認(rèn)設(shè)置為true。
- injectedJavaScript
設(shè)置在網(wǎng)頁加載之前注入的一段JS代碼。
- userAgent
其對應(yīng)Android端橋接方法為:
@ReactProp(name = "userAgent")
public void setUserAgent(WebView view, @Nullable String userAgent) {
if (userAgent != null) {
// TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
view.getSettings().setUserAgentString(userAgent);
}
}
RN源碼注釋:
Sets the user-agent for this WebView. The user-agent can also be set in native using WebViewConfig. This prop will overwrite that config.
綜合上面信息,可知該屬性為設(shè)置瀏覽器標(biāo)識,也可通過原生接口WebViewConfig實現(xiàn)定制WebView,下面會稍作詳細(xì)介紹如何使用WebViewConfig。
- domStorageEnabled
其對應(yīng)Android端橋接方法為:
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(WebView view, boolean enabled) {
view.getSettings().setDomStorageEnabled(enabled);
}
該屬性定義指定是否開啟DOM本地存儲。(僅限Android平臺),具體可參考:
http://blog.csdn.net/a345017062/article/details/8703221
- messagingEnabled bool
- onMessage
這兩個屬性支持高版本ReactNative Api 低版本無此屬性。
messagingEnabled參數(shù)控制onMessage函數(shù)是否有效,如果不主動設(shè)置,則該值為onMessage函數(shù)是否定義的結(jié)果為值。
onMessage為function類型,官方api解釋為:
在webview內(nèi)部的網(wǎng)頁中調(diào)用window.postMessage方法時可以觸發(fā)此屬性對應(yīng)的函數(shù),從而實現(xiàn)網(wǎng)頁和RN之間的數(shù)據(jù)交換。 設(shè)置此屬性的同時會在webview中注入一個postMessage的全局函數(shù)并覆蓋可能已經(jīng)存在的同名實現(xiàn)。網(wǎng)頁端的window.postMessage只發(fā)送一個參數(shù)data,此參數(shù)封裝在RN端的event對象中,即event.nativeEvent.data。data 只能是一個字符串。
Android原生中實現(xiàn)為
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(WebView view, boolean enabled) {
((ReactWebView) view).setMessagingEnabled(enabled);
}
public void setMessagingEnabled(boolean enabled) {
if (messagingEnabled == enabled) {
return;
}
messagingEnabled = enabled;
if (enabled) {
addJavascriptInterface(new ReactWebViewBridge(this), BRIDGE_NAME);
linkBridge();
} else {
removeJavascriptInterface(BRIDGE_NAME);
}
}
public void linkBridge() {
if (messagingEnabled) {
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// See isNative in lodash
String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
if (value.equals("true")) {
FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
}
}
});
}
loadUrl("javascript:(" +
"window.originalPostMessage = window.postMessage," +
"window.postMessage = function(data) {" +
BRIDGE_NAME + ".postMessage(String(data));" +
"}" +
")");
}
}
原生中實現(xiàn)是調(diào)用webView的loadUrl(),執(zhí)行一段js代碼,實現(xiàn)代碼的注入。
定義該函數(shù),即可實現(xiàn)網(wǎng)頁端與RN代碼之間的數(shù)據(jù)交互,下面詳細(xì)介紹。
- onContentSizeChange func
該函數(shù)在Rn api上并未提及,且在低版本rn上并沒有
其對應(yīng)Android端橋接方法為:
@ReactProp(name = "onContentSizeChange")
public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
if (sendContentSizeChangeEvents) {
view.setPictureListener(getPictureListener());
} else {
view.setPictureListener(null);
}
}
結(jié)合Android Api可知,該函數(shù)用途為定義網(wǎng)頁中圖片加載完畢的狀態(tài)回調(diào)。
這個方法已經(jīng)被Android標(biāo)為棄用,這個對應(yīng)的picture并不包含復(fù)合層或可以滾動的Div,只能被使用來偵測WebView內(nèi)容的變化.在以后的版本會提供他的替代事件,所以該屬性可不用。
其他屬性可參看RN源碼解釋與官方Api。
{otherView}
在render中,otherView是這么賦值的:
var otherView = null;
if (this.state.viewState === WebViewState.LOADING) {
otherView = (this.props.renderLoading || defaultRenderLoading)();
} else if (this.state.viewState === WebViewState.ERROR) {
var errorEvent = this.state.lastErrorEvent;
otherView = this.props.renderError && this.props.renderError(
errorEvent.domain,
errorEvent.code,
errorEvent.description);
} else if (this.state.viewState !== WebViewState.IDLE) {
console.error('RCTWebView invalid state encountered: ' + this.state.loading);
}
可直觀看出,otherView根據(jù)WebView不同狀態(tài)繪制提示頁的。
- renderLoading func
繪制加載中提示頁
- renderError func
繪制加載錯誤提示頁
其他屬性
- onNavigationStateChange func
源碼注釋如下:
We return an event with a bunch of fields including:
url, title, loading, canGoBack, canGoForward
具體為重寫該函數(shù)可在webView狀態(tài)發(fā)生改變的時候回調(diào)webView的event信息,event信息里包含了url, title, loading, canGoBack, canGoForward.
- startInLoadingState bool
源碼中的注釋:
force WebView to show loadingView on first load
具體為設(shè)置第一次加載數(shù)據(jù)時是否顯示loading狀態(tài)視圖,默認(rèn)值為true.
webview 實現(xiàn)與RN代碼簡單交互
不管安卓還是ios App,當(dāng)內(nèi)嵌webView加載網(wǎng)頁時,多少都會有涉及網(wǎng)頁端代碼與原生代碼之間的交互。
比如對原生代碼返回鍵的監(jiān)聽,來實現(xiàn)對當(dāng)網(wǎng)頁可返回時點擊app返回鍵不關(guān)閉網(wǎng)頁,而是打開前一個網(wǎng)頁,當(dāng)沒有前一個網(wǎng)頁時,關(guān)閉當(dāng)前webView頁面,返回App上個頁面。
這里涉及了app端與原生代碼之間的簡單交互,下面來說說我是怎么簡單在高版本,低版本上實現(xiàn)的。
使用高低版本都有的屬性方法--onNavigationStateChange
這個函數(shù)上面介紹過,重寫該回調(diào)時會傳入一個event參數(shù),event封裝了url, title, loading, canGoBack, canGoForward五個方法。且每次webView狀態(tài)改變時會回調(diào)該函數(shù),很簡單,通過url來判斷。
onNavigationStateChange={this.onNavigationStateChange}//在WebView中注冊該回調(diào)方法
onNavigationStateChange(event){
console.log('onNavigationStateChange:');
console.log(event); //打印出event中屬性
}
event的打印結(jié)果如下圖:

這里便可直觀的獲取到WebView的重要狀態(tài)屬性,url為點擊html<a/>標(biāo)簽觸發(fā)的超鏈接,這里自定義成app能判斷的協(xié)議鏈接,即可實現(xiàn)簡單交互,舉例點擊網(wǎng)頁按鈕退出webView,即可用該方法實現(xiàn)。
其他值如canGoBack,canGoForword,title,見名之意。
但有個屬性target有些疑惑,咱打開android源碼看看:
private WritableMap createWebViewEvent(WebView webView, String url) {
WritableMap event = Arguments.createMap();
event.putDouble("target", webView.getId());
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event.putString("url", url);
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
event.putString("title", webView.getTitle());
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
return event;
}
target也就是原生WebView的getId()返回值,也就是android中布局文件里的id值,這里算是唯一標(biāo)識吧應(yīng)該。
使用高版本的屬性方法--onMessage(event)
這個函數(shù)在RN官方API中有介紹,專門用來進行網(wǎng)頁端與RN端的通信,這里來實現(xiàn)下。
onMessage={this.onMessage}
onMessage(event){
console.log('onMessage->event.nativeEvent.data:');
console.log(event.nativeEvent.data);
}
在html代碼中通過點擊方式發(fā)送data:
<script language="javascript">
$('#btn_msg1').click(function() {
window.postMessage('網(wǎng)頁端點擊了按鈕啦。。。')
});
</script>
結(jié)果:

WebView Javascript Bridge
該三方庫兼容低版本實現(xiàn)網(wǎng)頁端發(fā)送message,并可實現(xiàn)rn向html代碼交互。
參看:https://github.com/alinz/react-native-webview-bridge
實現(xiàn)起來,稍微復(fù)雜些,安卓IOS端都需引入依賴。
其通過注入js的方式,在html中注入 WebViewBridge.onMessage函數(shù),實現(xiàn)了html與RN之間的雙向交互,功能強大,具體看其api。
在Android原生代碼中對ReactNative WebView控件進行初始設(shè)置
翻開Android端橋接WebView的源碼ReactWebViewManager,發(fā)現(xiàn)其有兩個構(gòu)造參數(shù):
public ReactWebViewManager() {
mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
};
}
public ReactWebViewManager(WebViewConfig webViewConfig) {
mWebViewConfig = webViewConfig;
}
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
//...
mWebViewConfig.configWebView(webView);
//...
return webView;
}
再打開WebViewConfig接口:
public interface WebViewConfig {
void configWebView(WebView webView);
}
這里就可以看出,其實咱可以傳入個WebViewConfig實例,通過webView.getSettings()對WebView進行Setting,下面簡單實現(xiàn)下。
由于MainReactPackage.java里已經(jīng)將ReactWebViewManager進行無參實例化,并加入集合通過createViewManagers()返回。所以我覺得可通過繼承的方式重寫createViewManagers()返回值。
public class CusMainReactPackage extends MainReactPackage {
private WebViewConfig webViewConfig = new WebViewConfig() {
@Override
public void configWebView(WebView webView) {
WebSettings settings = webView.getSettings();
//do settings...
}
};
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = handleRepleaceRTCWebView(super.createViewManagers(reactContext));
return viewManagers;
}
/**
* 替換viewManager中默認(rèn)的RCTWebViewManager
*/
private List<ViewManager> handleRepleaceRTCWebView(List<ViewManager> viewManagers) {
List<ViewManager> _viewManagers = new ArrayList<>(viewManagers);
for (int i = 0; i < _viewManagers.size(); i++)
if (_viewManagers.get(i).getName().equals("RCTWebView")) {
_viewManagers.set(i, new ReactWebViewManager(webViewConfig));
break;
}
return _viewManagers;
}
}
之后進行相應(yīng)調(diào)用修改即可。
結(jié)束
以上都是結(jié)合ReactNative Andorid端對WebView組件進行學(xué)習(xí)研究的總結(jié),由于對RN接觸不久,所以肯定有些理解錯誤的地方,望指正建議,謝謝!
參考:
- http://reactnative.cn/docs/0.42/webview.html
- http://facebook.github.io/react-native/releases/0.25/docs/webview.html#webview
- https://github.com/alinz/react-native-webview-bridge
- http://blog.csdn.net/codetomylaw/article/details/52490378
- https://developer.android.com/reference/android/webkit/WebView.html