Android H5與Native混合開發(fā)之JsBridge

電商或者內容類APP中仑荐,H5通常都會占據一席之地,Native跟H5通信會必不可少荚坞,比如某些場景H5通知native去分享,native通知H5局部刷新等蚁鳖,Android本身也提供這樣的接口届囚,比如addJavascriptInterface有梆、loadUrl("javascript:..."),而需要支持的能力也要是雙工的奖亚。

  • 1:H5通知Native(可能需要處理回調),
  • 2:Native通知H5(也可能需要處理回調

實現這種機制的方式并不唯一析砸,但使用不當經常會引入很多問題昔字,比如:H5同Native需要一個中間js文件,實現簡單的通信協(xié)議首繁,這個js文件有的產品做法是讓前端自己加載作郭,有的做法是客戶端注入,也就是通過loadUrl("javascript:...")注入弦疮。采用客戶端注入這種方式就多少有問題夹攒,因為沒有一個很合適的時機既保證注入成功,又保證注入及時胁塞。如果在onPageStarted時注入咏尝,很多手機會注入失敗,如果onPageFinished時注入啸罢,又太遲编检,導致很多功能打折扣。再比如:有些人通過prompt方式實現H5通知Native扰才,而prompt是一個可能產生問題的同步方法允懂,一旦無法返回,整個js環(huán)境就會掛掉衩匣,導致所有H5頁面都無法打開蕾总,下面簡單說下兩種實現粥航,一是通過addJavascriptInterface,另一種就
是通過prompt生百。

方案一:借助WebView.addJavascriptInterface實現H5與Native通信

WebView的addJavascriptInterface方法允許Natvive向Web頁面注入Java對象递雀,之后,在js中便可以直接訪問該對象置侍,使用@JavascriptInterface注解的方法映之。比如通過如下代碼向前端注入一個名字為mJsMethodApi的java對象

class JsMethodApi {
     
    /**
     * js調用native,可能需要回調
     */
    @JavascriptInterface
    public void callNative(String jsonString) {
        ...
    }
}

webView.addJavascriptInterface(new JsMethodApi(), "mJsMethodApi");

在前端的js代碼中蜡坊,是可以直接通過mJsMethodApi.callNative(jsonString)通知Native的杠输,而且通過addJavascriptInterface注入的對象在H5的任何地方都可以調用,不存在注入時機跟注入失敗的問題秕衙,在H5的head里調用都沒問題蠢甲。

<head>
    <script type="text/javascript"  >
       JsMethodApi.callNative('頭部就可以回調');
    </script>
</head>

經測試,其實是可以通知到Native的据忘,不過有一點需要注意callNative是這JavaBridge這個線程中執(zhí)行的鹦牛,雖然不提清楚它跟JS線程的關系,但JS會阻塞等待callNative函數執(zhí)行完畢再往下走勇吊,所以 @JavascriptInterface注解的方法里面最好也不要做耗時操作曼追,最好利用Handler封裝一下,讓每個任務自己處理汉规,耗時的話就開線程自己處理礼殊。

如果前端通知Native時需要回調怎么辦?可以抽離到一個中間的js针史,為每個任務設置一個ID晶伦,暫存回調函數,等到Native處理結束后啄枕,先走這個中間的js婚陪,找到對應的js回調函數執(zhí)行即可,

 var _callbacks = {};
 
 function callNative(method, params, success_cb, error_cb) {

     var request = {
         version: jsRPCVer,
         method: method,
         params: params,
         id: _current_id++
     };
  <!--暫存回調函數-->
     if (typeof success_cb !== 'undefined') {
         _callbacks[request.id] = {
             success_cb: success_cb,
             error_cb: error_cb
         };
     }
     <!--利用JsMethodApi通知Native-->
    JsMethodApi.callNative(JSON.stringify(request));
 };

以上js代碼完成回調的暫存频祝、通知native執(zhí)行泌参,native那邊會收到js消息,同時里面包含著id常空,等到native執(zhí)行完畢后及舍,將執(zhí)行結果與消息id通知到這個中間層js,找到對應的回調函數執(zhí)行即可窟绷,如下:

 jsRPC.onJsCallFinished = function(message) {
        var response = message;
             <!--找到回調函數-->
             var success_cb = _callbacks[response.id].success_cb;
             <!--刪除-->
             delete _callbacks[response.id];
             <!--執(zhí)行回調函數-->
             success_cb(response.result);
 };

這樣就完成H5通知Native锯玛,同時Native將結果回傳給H5,并完成回調這樣一條通路。Native通知H5,這條路怎么辦攘残?流程大概類似拙友,同樣可以基于一個消息ID完成回調,不過更加靈活歼郭,因為Native通知前端的接口不太好統(tǒng)一遗契,具體使用自己把握。

參考工程 https://github.com/happylishang/CMJsBridge

注意不要混淆

如果混淆了病曾,@JavascriptInterface注解的方法可能就沒了牍蜂,結果是,JS就沒辦法知己調用對應的方法泰涂,導致通信失敗鲫竞。

關于漏洞問題

4.2以后,WebView會禁止JS調用沒有添加@JavascriptInterface方法, 解決了安全漏洞逼蒙,而且很少APP兼容到4.2以前从绘,安全問題可以忽略。

關于阻塞問題

JavascriptInterface注入的方法被js調用時是牢,可以看做是一個同步調用僵井,雖然兩者位于不同線程,但是應該存在一個等待通知的機制來保證驳棱,所以Native中被回調的方法里盡量不要處理耗時操作批什,否則js會阻塞等待較長時間,如下圖

801573097289_.pic.jpg

方案二:通過prompt實現H5與Native的通信

日常使用Webview的時候一般都會設置WebChromeClient社搅,用來處理一些進度驻债、title之類的事件,除此之外罚渐,WebChromeClient還提供了幾個js回調的入口却汉,如onJsPrompt驯妄,onJsAlert等荷并,在前端調用?window.alert?,?window.confirm?青扔,?window.prompt?時源织,

  public boolean onJsAlert(WebView view, String url, String message,
            JsResult result) {
        return false;
    }
 
    public boolean onJsConfirm(WebView view, String url, String message,
            JsResult result) {
        return false;
    }

 
    public boolean onJsPrompt(WebView view, String url, String message,
            String defaultValue, JsPromptResult result) {
        return false;
    }

在js調用?window.alert?,?window.confirm?微猖,?window.prompt?時谈息,?會調用WebChromeClient?對應方法,可以此為入口凛剥,作為消息傳遞通道侠仇,考慮到開發(fā)習慣,一般不會選擇alert跟confirm,?通常會選promopt作為入口逻炊,在App中就是onJsPrompt作為jsbridge的調用入口互亮。由于onJsPrompt是在UI線程執(zhí)行,所以盡量不要做耗時操作余素,可以借助Handler靈活處理豹休。對于回調的處理跟上面的addJavascriptInterface的方式一樣即可,采用消息ID方式做暫存區(qū)分桨吊,區(qū)別就是這里采用 prompt(JSON.stringify(request));通知native威根,如下:

 function callNative(method, params, success_cb, error_cb) {

     var request = {
         version: jsRPCVer,
         method: method,
         params: params,
         id: _current_id++
     };

     if (typeof success_cb !== 'undefined') {
         _callbacks[request.id] = {
             success_cb: success_cb,
             error_cb: error_cb
         };
     }
    prompt(JSON.stringify(request));
 };

同之前JavaBridge線程類似,這里prompt的js線程必須要等待UI線程中onJsPrompt返回才會喚醒视乐,可以認為是個同步阻塞調用(應該是通過線程等待來做的)洛搀。

public class JsWebChromeClient extends WebChromeClient {

    JsBridgeApi mJsBridgeApi;

    public JsWebChromeClient(JsBridgeApi jsBridgeApi) {
        mJsBridgeApi = jsBridgeApi;
    }

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            try {
            if (mJsBridgeApi.handleJsCall(message)) {
            <!--如果睡眠10s js就會等待10s-->
                //    Thread.sleep(10000);
                result.confirm("sdf");
                return true;
            }
        } catch (Exception e) {
            return true;
        }
        //   未處理走默認邏輯
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
}

如果在onJsPrompt睡眠10s,js的prompt函數一定會阻塞等待10s才返回炊林,這個設計就要求我們不能在onJsPrompt中做耗時操作姥卢,systrace中可以驗證。

image.png

上圖中渣聚,chrome_iothread看做js線程独榴。

prompt的一個坑導致js掛掉

從表現上來看,onJsPrompt必須執(zhí)行完畢奕枝,prompt函數才會返回棺榔,否則js線程會一直阻塞在這里。實際使用中確實會發(fā)生這種情況隘道,尤其是APP中有很多線程的場景下症歇,懷疑是這么一種場景:

  • 第一步:js線程在執(zhí)行prompt時被掛起,
  • 第二部 :UI線程被調度谭梗,恰好銷毀了Webview忘晤,調用了 (webview的detroy),detroy之后激捏,導致 onJsPrompt不會被回調设塔,prompt一直等著,js線程就一直阻塞远舅,導致所有webview打不開闰蛔,一旦出現可能需要殺進程才能解決。

如果不主動destroy webview图柏,可以很大程度避免這個問題序六,具體Chrome的實現如何,還沒分析過蚤吹,這里只是根據現象推測如此例诀。而WebView.addJavascriptInterface并不會有這個問題,無論是否主動destroy Webview,都不會上述問題繁涂,可能chrome對addJavascriptInterface這種方式做了額外處理暮刃,在自己銷毀的時候,主動喚起JS線程爆土,但是onJsPrompt所在的UI線程顯然沒處理這種場景椭懊。

參考工程 https://github.com/happylishang/CMJsBridge

總結

  • 最好通過前端注入,這樣就可以避免注入失敗與注入時機不好把握的問題
  • 建議采用WebView.addJavascriptInterface實現步势,可以避免prompt掛掉js環(huán)境的問題
  • 通過@JavascriptInterface的方法中不要同步處理耗時操作氧猬,需要返回值的方法需要阻塞調用(盡量減少)
  • 如果非要用prompt,盡量不要自己destroy webview坏瘩,很容導致js環(huán)境掛了盅抚,所有webview打不開網頁
  • 如論哪種實現,都不要直接處理耗時操作倔矾,會阻塞js線程妄均。

作者:看書的小蝸牛
原文鏈接:Android 混合開發(fā)之JsBridge

僅供參考,歡迎指正

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末哪自,一起剝皮案震驚了整個濱河市丰包,隨后出現的幾起案子,更是在濱河造成了極大的恐慌壤巷,老刑警劉巖邑彪,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異胧华,居然都是意外死亡寄症,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門矩动,熙熙樓的掌柜王于貴愁眉苦臉地迎上來有巧,“玉大人,你說我怎么就攤上這事悲没±河” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵檀训,是天一觀的道長柑潦。 經常有香客問我享言,道長峻凫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任览露,我火速辦了婚禮荧琼,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己命锄,他們只是感情好堰乔,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脐恩,像睡著了一般镐侯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驶冒,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天苟翻,我揣著相機與錄音,去河邊找鬼骗污。 笑死崇猫,一個胖子當著我的面吹牛,可吹牛的內容都是我干的需忿。 我是一名探鬼主播诅炉,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屋厘!你這毒婦竟也來了涕烧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汗洒,失蹤者是張志新(化名)和其女友劉穎澈魄,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體仲翎,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡痹扇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了溯香。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲫构。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玫坛,靈堂內的尸體忽然破棺而出结笨,到底是詐尸還是另有隱情,我是刑警寧澤湿镀,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布炕吸,位于F島的核電站,受9級特大地震影響勉痴,放射性物質發(fā)生泄漏赫模。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一蒸矛、第九天 我趴在偏房一處隱蔽的房頂上張望瀑罗。 院中可真熱鬧胸嘴,春花似錦、人聲如沸斩祭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摧玫。三九已至耳奕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诬像,已是汗流浹背吮铭。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颅停,地道東北人谓晌。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像癞揉,于是被迫代替她去往敵國和親纸肉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355