1. 程式人生 > >簡化Android與JS互動,JsBridge框架全面解析

簡化Android與JS互動,JsBridge框架全面解析

640?wx_fmt=png&wxfrom=5&wx_lazy=1

今日科技快訊

近日,滴滴順風車披露了一組數字,預測春運前後,跨城出行以7天為一個週期,呈“潮汐式”變化。2月13號,有將近100萬人乘滴滴順風車踏上歸途;除夕當天,還有近40萬在回家的順風車上。返程巔峰集中在2月23日前後,23日預計有超過110萬人乘坐滴滴順風車返回工作崗位。

作者簡介

本篇文章來自 juexingzhe 的投稿。主要介紹了Android與JS的互動框架JsBridge原始碼分析的相關知識,希望對大家有所幫助!

juexingzhe 的部落格地址

https://www.jianshu.com/u/ea71bb3770b4

前言

在Android開發中,由於Native開發的成本較高,H5頁面的開發更靈活,修改成本更低,因此前端網頁JavaScript(下面簡稱JS)與Java之間的互相呼叫越來越常見。

JsBridge就是一個簡化Android與JS通訊的框架,原始碼地址:

https://github.com/lzyzsd/JsBridge

正文

我們今天通過一個簡單例子來分析下開源框架JsBridge的原始碼。例子的程式碼我也放在Github,有需要的可以seesee:

https://github.com/juexingzhe/Android_JS

例子很簡單,隨便輸入資訊登陸,會載入一個H5頁面,在H5介面點選按鈕,Java執行getUserInfo()然後將UserInfo回傳給JS,H5頁面再顯示UserInfo。

640?wx_fmt=png

1.png

640?wx_fmt=png

2.png

640?wx_fmt=png

3.png

JS呼叫Android基本有下面三種方式

  • webView.addJavascriptInterface()

  • WebViewClient.shouldOverrideUrlLoading()

  • WebChromeClient.onJsAlert()/onJsConfirm()/onJsPrompt() 方法分別回撥攔截JS對話方塊alert()、confirm()、prompt()訊息

Android呼叫JS

  • webView.loadUrl();

  • webView.evaluateJavascript()

常用方法的使用後面例子中會用到,更細節的介紹各位同學可以去網上搜搜看看。

JsBridge使用

我們先來看下Java層的程式碼,首先引入依賴和倉庫

dependencies { 
   …… 
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
   compile 'com.google.code.gson:gson:2.7' } repositories {    jcenter()    maven { url "https://jitpack.io" } }

準備工作就是這樣,下面可以開始擼程式碼,首先就是點選按鈕登陸,這個簡單:

Intent intent = new Intent(LoginActivity.this, WebActivity.class); 
intent.putExtra("email", mEmailView.getText().toString()); 
startActivity(intent);

佈局檔案中要使用BridgeWebView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical"> 

    <com.github.lzyzsd.jsbridge.BridgeWebView 
        android:id="@+id/web_view" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" /> 
</LinearLayout>

在跳轉後的頁面,獲取登陸資訊並存儲,再通過loadUrl載入H5頁面:

Intent intent = this.getIntent(); 
String email = intent.getStringExtra("email"); 

mUserInfo = new UserInfo(email); 

mBridgeWebView = (BridgeWebView) findViewById(R.id.web_view); 
mBridgeWebView.setDefaultHandler(new DefaultHandler()); 
mBridgeWebView.loadUrl("file:///android_asset/getuserinfo.html"); 

registerHandler();

主要是要註冊Handler,供JS呼叫,getUserInfo就是註冊供JS呼叫的Handler的id,data是JS傳過來的引數,CallBackFunction 函式中需要把JS需要的response返回給JS

private void registerHandler() { 
    mBridgeWebView.registerHandler("getUserInfo", new BridgeHandler() { 
        @Override 
        public void handler(String data, CallBackFunction function) { 
            Log.i(TAG, "handler = getUserInfo, data from web = " + data); 
            function.onCallBack(new Gson().toJson(mUserInfo)); 
        } 
    }); 
}

Java層的程式碼就這麼簡單,下面看下JS層工作:

首先需要一個js檔案,我們寫一個getuserinfo.html檔案放在assets目錄下,檔案內容,不建議把js程式碼直接放在html檔案中,我為了方便直接就寫在這了。程式碼放了兩個段落,一個類似於TextView用來顯示使用者資訊,一個Button。點選按鈕會呼叫callHandler,三個引數和Java層一一對應,在Java層返回的時候,會呼叫function(responseData)函式,顯示用於資訊。

<html> 
<head>    <meta content="text/html; charset=utf-8" http-equiv="content-type">    <title>        js呼叫java    </title>
</head>
<body>
<p>    <xmp id="show">    </xmp>
</p>
<div align="center">    <p>        <input type="button" id="enter" value="獲取使用者資訊" onclick="getUserInfo();"        />    </p>
</div>
</body>
<script>    function getUserInfo(){        window.WebViewJavascriptBridge.callHandler(            'getUserInfo',            {'info': 'I am JS, want to get UserInfo from Java'},            function(responseData) {                document.getElementById("show").innerHTML = "repsonseData from java,\ndata = " + responseData;            }        )    } </script>
</html>

使用基本就是這樣了,可以看出來JsBridge通過封裝,JS和Java之間的通訊只需要實現兩個步驟,使用起來很方便。

JsBridge原始碼分析

分析之前我把JS呼叫Java畫了個簡易互動圖,Java呼叫JS的過程類似:

640?wx_fmt=png

4.png

是不是感覺反而更復雜了???其實只要捉住主要的三點,JsBridge就原形畢露:

  1. Android呼叫JS是通過loadUrl(url),url中可以拼接要傳給JS的物件

  2. JS呼叫Android是通過shouldOverrideUrlLoading

  3. JsBridge將溝通資料封裝成Message,然後放進Queue,再將Queue進行傳輸

接下來我們來一步一步跟蹤上面例子的呼叫過程:

  • JS層點選按鈕呼叫callHandler

handlerName,Java和JS要一致,data是Java層handlerName函式執行的引數。responseCallback是Java執行完handlerName返回時,JS回撥的介面,是JS執行

onclick="getUserInfo();" 

function getUserInfo(){    window.WebViewJavascriptBridge.callHandler(            'getUserInfo',            {'info': 'I am JS, want to get UserInfo from Java'},            function(responseData) {                document.getElementById("show").innerHTML = "repsonseData from java,\ndata = " + responseData;            }    ) }

callHandler會呼叫_doSend。如果JS需要回調,就將回調的callbackId放進message中,Java執行完會傳回callbackId,這裡是cb_1_1495182409011,構造完message放進佇列sendMessageQueue。通過iframe屬性給Java傳送通知訊息,訊息結構為:

yy://QUEUE_MESSAGE/

function callHandler(handlerName, data, responseCallback) { 
    _doSend({ 
        handlerName: handlerName, 
        data: data 
    }, responseCallback); 
} 

function _doSend(message, responseCallback) { 
        if (responseCallback) { 
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); 
            responseCallbacks[callbackId] = responseCallback; 
            message.callbackId = callbackId; 
        } 

        sendMessageQueue.push(message); 
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 
}
  • Java收到通知訊息

WebView在shouldOverrideUrlLoading攔截到url:yy://QUEUE_MESSAGE/

然後會執行webView.flushMessageQueue(),在主執行緒執行loadUrl通知JS層推送佇列到Java;

void flushMessageQueue() { 
      if (Thread.currentThread() == Looper.getMainLooper().getThread()) { 
           loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { 
                @Override 
                public void onCallBack(String data) { 
                    // 
                }); 
      } 
} 

public void loadUrl(String jsUrl, CallBackFunction returnCallback) { 
      this.loadUrl(jsUrl); 
      responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); 
}
  • JS 傳送Request Queue

執行_fetchQueue,將sendMessageQueue轉化成JSON。通過iframe屬性給Java傳送通知訊息。

function _fetchQueue() { 
    var messageQueueString = JSON.stringify(sendMessageQueue); 
    sendMessageQueue = []; 
    //android can't read directly the return data, so we can reload iframe src to communicate with java 
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); 
}
  • Java收到呼叫通知

進行處理併發送Response Queue到JS,WebView在shouldOverrideUrlLoading會攔截到url,執行webView.handlerReturnData(url);根據函式名_fetchQueue拿到之前註冊的回撥函式CallBackFunction returnCallback,執行回撥函式,並且從註冊中移除。

void handlerReturnData(String url) { 
      String functionName = BridgeUtil.getFunctionFromReturnUrl(url); 
      CallBackFunction f = responseCallbacks.get(functionName); 
      String data = BridgeUtil.getDataFromReturnUrl(url); 
      if (f != null) { 
           f.onCallBack(data); 
           responseCallbacks.remove(functionName); 
           return; 
      } 
}

接下來就是對Request Queue的解析然後找到JS希望呼叫Handler並且執行,程式碼中我寫了註釋,可以直接看:

//回撥介面執行onCallBack函式 
//其中data [{"handlerName":"getUserInfo","data":{"info":"I am JS, want to get UserInfo from Java"},"callbackId":"cb_1_1495180503779"}]
void flushMessageQueue() {    if (Thread.currentThread() == Looper.getMainLooper().getThread()) {         loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {            @Override            public void onCallBack(String data) {                 // deserializeMessage                 List<Message> list = null;                 try {                      //將JSON陣列轉化成Java list                      list = Message.toArrayList(data);                 } catch (Exception e) {                      e.printStackTrace();                      return;                 }                 if (list == null || list.size() == 0) {                      return;                 }                 for (int i = 0; i < list.size(); i++) {                      //從list中取出Message                      Message m = list.get(i);                      //在我們的例子中沒有responseId,因此到else分支                      String responseId = m.getResponseId();                       // 是否是response                      if (!TextUtils.isEmpty(responseId)) {                           CallBackFunction function = responseCallbacks.get(responseId);                           String responseData = m.getResponseData();                           function.onCallBack(responseData);                           responseCallbacks.remove(responseId);                      } else {                           CallBackFunction responseFunction = null;                           // if had callbackId                           //如果有callbackId就說明JS需要回調,因此Java層需要構造responseMsg

                           //從message中取出callbackId,放進responseMsg                           final String callbackId = m.getCallbackId();                           if (!TextUtils.isEmpty(callbackId)) {                                responseFunction = new CallBackFunction() {                                             @Override                                             public void onCallBack(String data) {                                                  Message responseMsg = new Message();                                                  responseMsg.setResponseId(callbackId);                                                  responseMsg.setResponseData(data);                                                  queueMessage(responseMsg);                                             }                                        };                           } else {                                responseFunction = new CallBackFunction() {                                             @Override                                             public void onCallBack(String data) {                                                  // do nothing                                             }                                        };                           }                           BridgeHandler handler;                           //從message中取出Handler名字,再從messageHandlers中取                           //如果沒有就使用預設的Handler                           if (!TextUtils.isEmpty(m.getHandlerName())) {                                handler = messageHandlers.get(m.getHandlerName());                           } else {                                handler = defaultHandler;                           }                            if (handler != null){                                 //執行handler                                 handler.handler(m.getData(), responseFunction);                           }                              }                         }                    }               });          } }

那麼這個handler是什麼?就是Java呼叫registerHandler註冊的getUserInfo

private void registerHandler() { 
        mBridgeWebView.registerHandler("getUserInfo", new BridgeHandler() { 
            @Override 
            public void handler(String data, CallBackFunction function) { 
                Log.i(TAG, "handler = getUserInfo, data from web = " + data); 
                function.onCallBack(new Gson().toJson(mUserInfo)); 
            } 
}

上面的function就是在flushMessageQueue 解析時構造的responseFunction,在message中包括JS層需要回調的函式Id,然後就是getUserInfo執行的結果。呼叫queueMessage

responseFunction = new CallBackFunction() { 
          @Override 
          public void onCallBack(String data) { 
                    Message responseMsg = new Message(); 
                    responseMsg.setResponseId(callbackId); 
                    responseMsg.setResponseData(data); 
                    queueMessage(responseMsg); 
          } 
};

通過構造String指令,然後loadUrl執行JS程式碼,注意物件也是通過這樣方式傳遞過去的,就類似呼叫本地函式,不發起網路請求

void dispatchMessage(Message m) { 
        String messageJson = m.toJson(); 
        //escape special characters for json string 
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); 
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); 
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); 
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) { 
            this.loadUrl(javascriptCommand); 
        } 
}

其中

BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA ="javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');" 
javascriptCommand = javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"responseData\":\"{\\\"email\\\":\\\"[email protected]\\\"}\",\"responseId\":\"cb_1_1495182558893\"}'); //data = {\"responseData\":\"{\\\"email\\\":\\\"[email protected]\\\"}\",\"responseId\":\"cb_1_1495182409011\"}
  • JS收到Response JSON

來到_handleMessageFromNative

function _handleMessageFromNative(messageJSON) { 
        console.log(messageJSON); 
        if (receiveMessageQueue && receiveMessageQueue.length > 0) { 
            receiveMessageQueue.push(messageJSON); 
        } else { 
            _dispatchMessageFromNative(messageJSON); 
        } 
}

最後都會呼叫到_dispatchMessageFromNative,由於是JS主動呼叫Java,因此有responseId,執行registerHandler時傳入的CallBack,也就是顯示使用者資訊。我在程式碼里加了註釋,很容易看懂。

function _dispatchMessageFromNative(messageJSON) { 
        setTimeout(function() { 
             //將資料解析成JSON 
            var message = JSON.parse(messageJSON); 
            var responseCallback; 
            //java call finished, now need to call js callback function 
            //根據responseId:cb_1_1495182409011拿到responseCallback,就是我們前門註冊的alert 
            if (message.responseId) { 
                responseCallback = responseCallbacks[message.responseId]; 
                if (!responseCallback) { 
                    return; 
                } 
                responseCallback(message.responseData); 
                delete responseCallbacks[message.responseId]; 
            } else { 
                //直接傳送 
                if (message.callbackId) { 
                    var callbackResponseId = message.callbackId; 
                    responseCallback = function(responseData) { 
                        _doSend({ 
                            responseId: callbackResponseId, 
                            responseData: responseData 
                        }); 
                    }; 
                } 

                var handler = WebViewJavascriptBridge._messageHandler; 
                if (message.handlerName) { 
                    handler = messageHandlers[message.handlerName]; 
                } 
                //查詢指定handler 
                try { 
                    handler(message.data, responseCallback); 
                } catch (exception) { 
                    if (typeof console != 'undefined') { 
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); 
                    } 
                } 
            } 
        }); 
}

原始碼的分析就到這結束了,程式碼不多,但是封裝的介面很是好用。

總結

最後總結下,使用上很方便主要兩個步驟

被呼叫方註冊Handler

registerHandler(String handlerName, BridgeHandler handler)

呼叫方呼叫Handler

callHandler(String handlerName, String data, CallBackFunction callBack)

原理上還是那三句話,請原諒我從上面直接copy過來:

  1. Android呼叫JS是通過loadUrl(url),url中可以拼接要傳給JS的物件

  2. JS呼叫Android是通過shouldOverrideUrlLoading

  3. JsBridge將溝通資料封裝成Message,然後放進Queue,再將Queue進行傳輸

好了,今天我們JsBridge的使用和原始碼分析就到這了,謝謝!

文中例子的連結:

https://github.com/juexingzhe/Android_JS

歡迎長按下圖 -> 識別圖中二維碼

或者 掃一掃 關注我的公眾號

640.png?

640?wx_fmt=jpeg

相關推薦

簡化AndroidJS互動JsBridge框架全面解析

今日科技快訊近日,滴滴順風車披露了一組數字,預測春運前後,跨城出行以7天為一個週期,呈“潮汐式”

Android webViewjs 互動以及jsbridge框架原始碼分析

1.簡單篇 如何實現簡單的android 呼叫js 與js呼叫android 讓webview做一下操作 private void init(Context context){ WebSettings setting =

Androidjs互動帶進度條的載入H5頁面

private void initWebView() { WebSettings settings = wvResumeDetail.getSettings(); //支援JavaScript指令碼語言 settings

AndroidJS互動篇--JSBridge的使用

在android日常開發中,大家或多或少都會碰到原生巢狀web頁面,大家可以使用傳統的方式實現Native與JS的互動,這裡就不多介紹了,現在我們簡單介紹下網上目前比較流行的已經封裝好的框架JsBridge。可參考官網github地址:點選開啟連結先看下接下來將要實現的效果圖

安卓混淆之後androidjs互動異常原因

解決方案:需要js互動程式碼不被混淆掉,加入以下程式碼即可 -keepclassmembers class com.taohaohuo365.taohaohuo.activity.H5Activity$AndroidAndJSInterface { public *; } -keepcla

qtjs互動並在百度地圖上繪製軌跡

前言: 獲得了照片的GPS資訊後,我們現在要做的就是把GPS資訊傳入JS中,然後通過百度地圖API將軌跡繪製出來。 一:在程式頁面載入地圖 qt5.6及之後取消了QWebkits,轉為使用QWebEngine. 在qt5.6的額ui設計師介面中,並不能直

AndroidJS互動

Android與JS的方法互調 在Android的開發過程中、遇到一個新需求、那就是讓Java程式碼和Javascript程式碼進行互動、在IOS中實現起來很麻煩、而在Android中相對來說容易多了、Android對這種互動進行了很好的封裝、我們可以很簡單的用

C# ActiveX JS 互動只介紹JS呼叫ActiveX的情況

關於如何編寫ActiveX程式,請參考我的部落格 C#編寫ActiveX控制元件 http://blog.csdn.net/hndkbadxv/article/details/9084167 第一步:在控制元件類中加入一個方法和一個屬性 public string Sho

android js互動

年後請了幾天假,今天第一天上班,這邊部落格本來應該年前寫的,唉。。太懶了 先扯點沒用的,總結下17年吧。 外包公司,專案倒是很多很雜,但總覺得論起細節、深度不夠,但也學習到了很多的東西,而且慶幸的是和三個android同事一起進步、成長,成了很好的朋友。空閒時間

Androidjs互動(四)自定義cordova外掛

首先昨天我做測試的時候js端一直無法呼叫寫在js的方法後來查了資料才知道是在index.html中使用js時 需要刪除這一行 <meta http-equiv="Content-Security-Policy" content="default-src 'self'

UIWebView JS互動JSContext注入時機

我們做WbeView與js互動,很多時候是使用JavaScriptCore來進行操作,但是使用JavaScriptCore,有時候方法注入時機不對,可能會導致無法正確呼叫。 比如在html頁面剛剛載入的時候js需要呼叫OC的某個方法,有時候將方法註冊寫在-(v

Android前端互動JSBridge

    在app中,經常會遇到一些活動推廣的頁面,大多數活動具備時效性強、運營時間短的特徵,這些活動一般都是通過H5頁面快速投放到產品的活動模組,來和使用者進行互動。如何建立web頁面和本地Native頁面的深度互動,這就接下來要重點介紹的JSBridge,先看一個效果圖:圖

WebView---Androidjs互動例項

Android 中可以通過webview來實現和js的互動,在程式中呼叫js程式碼,只需要將webview控制元件的支援js的屬性設定為true Android(Java)與JavaScript(HTML)互動有四種情況: 1) Android(Java)呼叫HTML中j

iOSJS互動OC呼叫JS方法JS呼叫OC方法URL解碼

 首先 iOS7開始 蘋果公佈了JavaScriptCore.framework 它使得JS與OC的互動更加方便了。  第一步:匯入framework  OC 呼叫JS方法 :(一句話即可) -(void)webViewDidFinishLoad:(UIWebV

AndroidJS互動監聽,url實時變化的方法.

private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view,

androidjs互動的方式(包括三種)

關於android月js或者說html互動的方式,在很早的版本中是通過android端新增js支援,然後傳遞一個js操作本地方法的物件,然後就可以呼叫本地的方法。在後邊的版本為了統一管理,添加了@JavascriptInterface  ,只有添加了這個標誌的方法才能被js

c#兩種方式呼叫google地球,呼叫COM API以及呼叫GEPLUGIN js互動載入kml檔案dae檔案。將二維高德地圖覆蓋到到三維谷歌地球表面。

網路上資源很多不全面,自己在開發的時候走了不少彎路,在這裡整理了最全面的google全套開發,COM互動,web端互動。封裝好了各種模組功能。 直接就可以呼叫。 第一種方式:呼叫COMAPI實現呼叫google地球 1、安裝googleearth客戶端。傳送門:https://pan.baidu.com/

androidjs的交互之jsbridge使用

default ast sage 兩個 微信 handler adl tle view 眾所周知,app的一些功能可能會使用到H5開發,這就難免會遇到java與js 的相互調用,android 利用WebViewJavascriptBridge 實現js和java的交互,這

AndroidJSJsBridge使用原始碼分析

在Android開發中,由於Native開發的成本較高,H5頁面的開發更靈活,修改成本更低,因此前端網頁JavaScript(下面簡稱JS)與Java之間的互相呼叫越來越常見。 JsBridge就是一個簡化Android與JS通訊的框架,原始碼:https://github.com/lzyzsd

Android開發之Webview中原生JS互動

文章目錄 概述 使用場景 互動方式 Java呼叫JS程式碼 JS呼叫Java程式碼 總結 概述 由於手機硬體資源的快速提升,使得采用混合開發的可能性逐漸成為現實並且流行起來。Android開發中