/**
2016.12.23 很久沒上來了,今天看郵箱發(fā)現(xiàn)有人還在看這篇文章。這里提醒大家一下:好像是從0.37還是0.36版本開始,官方組件WebView多了onMessage屬性,可以用來做這個(gè)兩端通訊了。
另外就是官方組件一直在更新,版本不同時(shí)可能會(huì)有問題,看懂原理然后自己結(jié)合當(dāng)前版本來研究比較合適
*/
由于RN本身即為利用原生為JS所調(diào)用,Web里的大頭Html與JS和原生的通訊肯定是逃不過的。另外就是正常商業(yè)化的APP里難免要有活動(dòng)推廣的H5頁面,會(huì)有直接在H5頁面里直接調(diào)用如分享/頁面跳轉(zhuǎn)等的功能需求。
這里所做的實(shí)踐是在Mac上,編程工具是IDEA和AS,RN版本是0.24.1。windows平臺(tái)尚未嘗試,不過也應(yīng)該是一樣的,因?yàn)闆]有涉及到如編譯環(huán)境等問題。
RN目前已有官方組件WebView,
官方教程:http://facebook.github.io/react-native/docs/webview.html#content
中文版: http://reactnative.cn/docs/0.25/webview.html#content
使用示例:
https://github.com/facebook/react-native/blob/a80dd9a92a8d7c201972d1a8b2392e9f893147c8/Examples/UIExplorer/WebViewExample.js
開始的前提是已經(jīng)新建好一個(gè)正常的RN工程。
復(fù)制官方組件WebView。
- 復(fù)制Java代碼。
從MainActivity里的getPackages函數(shù)里找到MainReactPackage,點(diǎn)進(jìn)去再找ReactWebViewManager,找到后再點(diǎn)進(jìn)去,將ReactWebViewManager路徑里的webview文件夾下的events和ReactWebViewManager.java的內(nèi)容復(fù)制出來到自己的目錄下。我這里將復(fù)制過來的命名為MyWebViewManager.java.
需要修改MyWebViewManager里的REACT_CLASS值,以便區(qū)別于官方組件,我這里改為MyWebView.
-
復(fù)制JS代碼。
路徑為根目錄下
node_modules/react-native/Libraries/Components/WebView/WebView.android.js,
復(fù)制文件到自己的目標(biāo)目錄,為便于區(qū)別更改文件名稱為MyWebView.android.js。
需要修改此文件里的部分代碼以匹配java代碼var RCTWebView = requireNativeComponent('RCTWebView', WebView);var MyWebView = requireNativeComponent('MyWebView', WebView);同時(shí)需要把此文件里其他地方的RCTWebView全部改成MyWebView。
做完上面兩步,可以試著在RN中用一下自己復(fù)制過來的MyWebView,以確保后面的步驟是在正確的前提下修改的,按WebViewExample里方式引用即可。
var MyWebView =require('./MyWebView');//這一步注意確保引用的文件路徑是正確的
<MyWebView
style={{backgroundColor: BGWASH, height: 100,}}
source={require('./helloworld.html')}
scalesPageToFit={true} />
運(yùn)行react-native run-android后如果是正常顯示的,可進(jìn)行下面步驟。
如果有報(bào)錯(cuò)誤說有Module命名錯(cuò)誤,需要你檢查下剛才的文件里是否有修改到位。另外如果有提示providesModule這個(gè)詞的話,需要修改的地方在MyWebView.android.js的開始的注釋里,將
@providesModule WebView修改為 @providesModule MyWebView
Html調(diào)用原生代碼。
在android中,WebView可以通過addJavascriptInterface方法添加原生代碼為html中的JS調(diào)用;同時(shí)java又可以用WebView的loadUrl方法調(diào)用Html中的定義的JS方法。
在RN中,上面已封裝好的組件正是封裝的原生WebView,那么實(shí)現(xiàn)Html調(diào)用原生代碼就很容易了。
在MyWebViewManager中的createViewInstance方法中,在return前添加一行代碼
webView.addJavascriptInterface(new JSObject(reactContext,webView),"JavaSObject");
JSObject的代碼為
public class JSObject {
private ReactContext mReactContext;
private WebView mWebView;
public JSObject(ReactContext reactContext,WebView webView){
mReactContext=reactContext;
mWebView=webView;
}
@JavascriptInterface
public void showToast(String text){
Toast.makeText(mReactContext,text,Toast.LENGTH_SHORT).show();
}
}
helloworld.html的代碼為
<!DOCTYPE html><html>
<head> <meta chatset="utf-8" />
<title>This is a test</title>
<style>
*{ margin: 0; padding: 0; }
a{ display: block; width: 100px; padding: 1em; margin: 0 auto; font-size: 1em; color: #FFF; background-color: highlight; text-decoration: none; }
</style>
</head>
<body>
<a>js test</a>
<script>
function funFromjs(){
document.getElementById("helloweb").innerHTML="HelloWebView,i'm from js"; }
var aTag = document.getElementsByTagName('a')[0];
aTag.addEventListener('click', function(){
//調(diào)用android本地方法
JavaSObject.showToast('hahahaha');
return false; }, false);
</script>
<p>
</p>
<div id="helloweb">
</div>
</body>
</html>
運(yùn)行后點(diǎn)擊界面上出現(xiàn)的小方塊,會(huì)有toast彈出的話即說明從Html端調(diào)用Java端代碼成功了。
Html與React端的通訊
做完2就可以實(shí)現(xiàn)RN中的WebView載入的Html里的js調(diào)用java端方法,比較下就知道:這樣同在原生android中的WebView與Html的相互調(diào)用是完全一樣的。但我們畢竟是在RreactNative構(gòu)建的APP中,更理想的應(yīng)該是在React端控制所有的東西,也就是說我們需要做到Html中能否調(diào)用,如何調(diào)用哪些東西應(yīng)該交由React端直接控制,否則每次Html有不同的調(diào)用需求時(shí)都需要去更改java代碼,不夠便捷和統(tǒng)一。
問題再具體化下就是:在Html中發(fā)起調(diào)用請(qǐng)求時(shí),應(yīng)該由載入Html的WebView響應(yīng)這個(gè)請(qǐng)求。
那么問題來了,應(yīng)該如何實(shí)現(xiàn)這樣的效果?
結(jié)合前面我們封裝原生View的經(jīng)驗(yàn),我們可以為封裝出來的組件以@ReactProp添加屬性,但直接添加屬性恐怕不行:因?yàn)閷傩远际窃O(shè)置了即執(zhí)行,不會(huì)存在接收到響應(yīng)后才執(zhí)行的屬性。還有什么可以作為組件通訊用呢,那就是事件綁定:事件本身也匹配請(qǐng)求響應(yīng)這樣的特性。
接下來思路就很明確了,Html是WebView通過addJavascriptInterface方法添加原生代碼為html中的JS調(diào)用,我們需要在這個(gè)被添加的java類中添加WebView的事件綁定,同時(shí)需要修改相應(yīng)的React端代碼。
這里沒有用到新的知識(shí)點(diǎn),只是將前面封裝原生View中的步驟重新執(zhí)行一遍,不懂步驟或者不理解的請(qǐng)參照前面的封裝原生UI組件。
接著前面的代碼繼續(xù),首先是完善JSObject的代碼,添加事件綁定
public class JSObject {
private ReactContext mReactContext;
private WebView mWebView;
public JSObject(ReactContext reactContext,WebView webView){
mReactContext=reactContext;
mWebView=webView;
}
@JavascriptInterface
public void showToast(String text){
Toast.makeText(mReactContext,text,Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void startShare(String json) {
WritableMap nativeEvent = Arguments.createMap();
nativeEvent.putString("message", json);
mReactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
mWebView.getId(), "topChange", nativeEvent);
}
}
再在MyWebView.android.js中添加注冊(cè)的事件,只寫出需更改的地方。
var WebView = React.createClass({
...
propTypes: {
....
onShare:React.PropTypes.func,
.....
}
_onShare(event:Event){
if(!this.props.onShare){
return;
}
if(event.nativeEvent.message!=null){
this.props.onShare(event.nativeEvent.message);
return;
}},
render: function() {
....
var webView2 =
<CMWebView
......
onChange={this._onShare}
/>;
...
}
...
}
最后在使用MyWebView時(shí)。
...
_onShareTest(result){
alert(result);},
...
<MyWebView
style={{backgroundColor: BGWASH, height: 100,}}
source={require('./helloworld.html')}
scalesPageToFit={true}
onShare={(json)=>this._onShareTest(json)} />
之前在helloworld.html中寫的 JavaSObject.showToast('hahahaha');
就要變成 JavaSObject.startShare('hahahaha');
再次運(yùn)行后點(diǎn)擊小方塊,這次如果彈出的alert,就說明試驗(yàn)成功了。
回顧一下上面的步驟會(huì)發(fā)現(xiàn)我們并沒有對(duì)事件做具體的限制,對(duì)調(diào)用何種需求也沒有具體限制,例如你可以在傳遞的string里設(shè)置一個(gè)如type的值來標(biāo)明html此次調(diào)用的類型,后面附上參數(shù)。所以說以這樣的步驟下來幾乎可以滿足所有的Html調(diào)用本地方法的需求,而由React端來控制。
不過呢,我覺得這樣的步驟還是有點(diǎn)麻煩的,看官方的代碼也發(fā)現(xiàn)WebView還留有很多東西等待完善補(bǔ)充,相信未來官方會(huì)把WebView的功能完善的更好更強(qiáng)大的。
最后總結(jié)的話就是:WebView(Html)與原生端的通訊只是又實(shí)踐了一遍之前的封裝原生UI組件,沒有什么新東西的。
PS:感覺最近升級(jí)的0.25.+版本略坑,bug又開始多了。真希望0.26趕緊來啊。