React-Native之Android: WebView(Html)與原生端通訊

/**
2016.12.23 很久沒上來了,今天看郵箱發現有人還在看這篇文章。這里提醒大家一下:好像是從0.37還是0.36版本開始,官方組件WebView多了onMessage屬性,可以用來做這個兩端通訊了。
另外就是官方組件一直在更新,版本不同時可能會有問題,看懂原理然后自己結合當前版本來研究比較合適
*/

由于RN本身即為利用原生為JS所調用,Web里的大頭Html與JS和原生的通訊肯定是逃不過的。另外就是正常商業化的APP里難免要有活動推廣的H5頁面,會有直接在H5頁面里直接調用如分享/頁面跳轉等的功能需求。

這里所做的實踐是在Mac上,編程工具是IDEA和AS,RN版本是0.24.1。windows平臺尚未嘗試,不過也應該是一樣的,因為沒有涉及到如編譯環境等問題。

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

開始的前提是已經新建好一個正常的RN工程。

復制官方組件WebView。

  1. 復制Java代碼。
    從MainActivity里的getPackages函數里找到MainReactPackage,點進去再找ReactWebViewManager,找到后再點進去,將ReactWebViewManager路徑里的webview文件夾下的events和ReactWebViewManager.java的內容復制出來到自己的目錄下。我這里將復制過來的命名為MyWebViewManager.java.

需要修改MyWebViewManager里的REACT_CLASS值,以便區別于官方組件,我這里改為MyWebView.

  1. 復制JS代碼。
    路徑為根目錄下
    node_modules/react-native/Libraries/Components/WebView/WebView.android.js,
    復制文件到自己的目標目錄,為便于區別更改文件名稱為MyWebView.android.js。
    需要修改此文件里的部分代碼以匹配java代碼

    var RCTWebView = requireNativeComponent('RCTWebView', WebView);

    var MyWebView = requireNativeComponent('MyWebView', WebView);

    同時需要把此文件里其他地方的RCTWebView全部改成MyWebView。

做完上面兩步,可以試著在RN中用一下自己復制過來的MyWebView,以確保后面的步驟是在正確的前提下修改的,按WebViewExample里方式引用即可。

    var MyWebView =require('./MyWebView');//這一步注意確保引用的文件路徑是正確的
    
    <MyWebView       
      style={{backgroundColor: BGWASH, height: 100,}}       
      source={require('./helloworld.html')}       
      scalesPageToFit={true}    />

運行react-native run-android后如果是正常顯示的,可進行下面步驟。
如果有報錯誤說有Module命名錯誤,需要你檢查下剛才的文件里是否有修改到位。另外如果有提示providesModule這個詞的話,需要修改的地方在MyWebView.android.js的開始的注釋里,將
@providesModule WebView修改為 @providesModule MyWebView

Html調用原生代碼。

在android中,WebView可以通過addJavascriptInterface方法添加原生代碼為html中的JS調用;同時java又可以用WebView的loadUrl方法調用Html中的定義的JS方法。

在RN中,上面已封裝好的組件正是封裝的原生WebView,那么實現Html調用原生代碼就很容易了。
在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(){   
//調用android本地方法  
 JavaSObject.showToast('hahahaha');   
return false; }, false);
</script>
<p>
</p>
<div id="helloweb">
</div>
</body>
</html>

運行后點擊界面上出現的小方塊,會有toast彈出的話即說明從Html端調用Java端代碼成功了。

Html與React端的通訊

做完2就可以實現RN中的WebView載入的Html里的js調用java端方法,比較下就知道:這樣同在原生android中的WebView與Html的相互調用是完全一樣的。但我們畢竟是在RreactNative構建的APP中,更理想的應該是在React端控制所有的東西,也就是說我們需要做到Html中能否調用,如何調用哪些東西應該交由React端直接控制,否則每次Html有不同的調用需求時都需要去更改java代碼,不夠便捷和統一。

問題再具體化下就是:在Html中發起調用請求時,應該由載入Html的WebView響應這個請求。
那么問題來了,應該如何實現這樣的效果?

結合前面我們封裝原生View的經驗,我們可以為封裝出來的組件以@ReactProp添加屬性,但直接添加屬性恐怕不行:因為屬性都是設置了即執行,不會存在接收到響應后才執行的屬性。還有什么可以作為組件通訊用呢,那就是事件綁定:事件本身也匹配請求響應這樣的特性。

接下來思路就很明確了,Html是WebView通過addJavascriptInterface方法添加原生代碼為html中的JS調用,我們需要在這個被添加的java類中添加WebView的事件綁定,同時需要修改相應的React端代碼。

這里沒有用到新的知識點,只是將前面封裝原生View中的步驟重新執行一遍,不懂步驟或者不理解的請參照前面的封裝原生UI組件。
接著前面的代碼繼續,首先是完善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中添加注冊的事件,只寫出需更改的地方。

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時。

...
_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');
再次運行后點擊小方塊,這次如果彈出的alert,就說明試驗成功了。

回顧一下上面的步驟會發現我們并沒有對事件做具體的限制,對調用何種需求也沒有具體限制,例如你可以在傳遞的string里設置一個如type的值來標明html此次調用的類型,后面附上參數。所以說以這樣的步驟下來幾乎可以滿足所有的Html調用本地方法的需求,而由React端來控制。
不過呢,我覺得這樣的步驟還是有點麻煩的,看官方的代碼也發現WebView還留有很多東西等待完善補充,相信未來官方會把WebView的功能完善的更好更強大的。

最后總結的話就是:WebView(Html)與原生端的通訊只是又實踐了一遍之前的封裝原生UI組件,沒有什么新東西的。
PS:感覺最近升級的0.25.+版本略坑,bug又開始多了。真希望0.26趕緊來啊。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容