1. 程式人生 > >Android——JsBridge實戰(一)

Android——JsBridge實戰(一)

Android開發目前現狀來說,開發者大部分時間花在UI的螢幕適配上,使用原生控制元件開發成本已不是那麼的理想,鑑於很多專案保持和iOS一致的UI介面風格,至使移動UI開發成本花費更大的代價,因此目前結合H5和原生控制元件混合開發是解決UI適配的一種很好的選擇, 因此基於網頁形式的外掛更新業務功能出現了,處於APP效能的考慮,Android也會使用java和native層(C,C++)進行結合。無論是哪種結合,其實原理都差不多,只要按照它的協議來是很容易的,今天我們僅對於H5和Java層結合的混合開發,瞭解WebViewJavascriptBridge的使用。

什麼是JsBridge

WebViewJavascriptBridge是移動UIView和Html互動通訊的橋樑,用作者的話來說就是實現java(ios為oc)和js的互相呼叫的橋樑。替代了WebView的自帶的JavascriptInterface的介面,使得開發者更方便的讓js和native靈活互動,使我們的開發更加靈活和安全。

JSBridge的優點

Android API 4.4以前,谷歌的webview存在安全漏洞,網站可以通過js注入就可以隨便拿到客戶端的重要資訊,甚至輕而易舉的呼叫原生代碼進行流氓行為,谷歌後來發現有此漏洞後,在API 4.4以後增加了防禦措施,如果用js呼叫原生代碼,開發者必須在程式碼申明JavascriptInterface, 列如在4.0之前我們要使得webView載入js只需如下程式碼:

mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");

4.4之後使用時需要在呼叫Java方法加入@JavascriptInterface註解,如果程式碼無此申明,那麼也就無法使得js生效,也就是說這樣就可以避免惡意網頁利用js對客戶端的進行竊取和攻擊。 但是即使這樣,我們很多時候需要在js呼叫本地java程式碼的時候,要做一些判斷和限制,或者有的場景也會做些過濾或者對使用者友好提示,甚至更復雜的Hybrid模式下,需要js和native之間進行互動通訊,拍照上傳,因此原生的JavascriptInterface 就比較維護了,特此有了基於JavascriptInterface 封裝的WebViewJavascriptBridge框架。

使用JsBridge正確姿勢

1 新增依賴

  • Eclipse:
    匯入jar包
    直接copy jar的原始碼到工程
    JsBridge. jar可以到gitHub上直接下載。

  • Android Studio:
    配置gradle

 repositories {
     //...
     maven { url "https://jitpack.io" }
 }

 dependencies {
     compile 'com.github.lzyzsd:jsbridge:1.0.4'
 }

2 xml中使用jsBridge的webView

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

3 JS和Native互動:

JSBridge是基於訂閱和回撥來實現Js和native互動的,我們需要在java中訂閱,然後Js中回撥,反之也可以。

3.1 Java中使用JsBridge

  • registerHandler()用來註冊一個java函式,來實現js回撥的handler,
    引數說明:
    第一個 : 訂閱的方法名
    第二個 : 回撥Handler , 引數返回js請求的resqustData,function.onCallBack()回撥到js,呼叫function(responseData)
    //必須和js同名函式,註冊具體執行函式,類似java實現類
webView.registerHandler("submitFromWeb", new BridgeHandler() {
    @Override
    public void handler(String data, CallBackFunction function) {
        function.onCallBack( data + “java”);
    }
});
  • CallHandler( )用來呼叫js中的註冊的方法
    引數說明:
    第一個 : js中註冊的方法名
    第二個 : java發給js的引數,json格式的string
    第三個 : 用來處理js回覆給java的資訊的回撥方法
 mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                showToast(data);
            }
        });
  • send()方法用來向js傳送訊息
mWebView.send("Hello!");

3.2 JS中使用JsBridge

  • window.WebViewJavascriptBridge.callHandler:呼叫java層註冊的同名函式,方法名必須和Java層保持一致

引數說明:
第一個 : 方法名
第二個 : js呼叫native的請求引數
第三個:js在被回撥後具體執行方法,responseData為java層回傳jsonStr.

window.WebViewJavascriptBridge.callHandler('submitFromWeb',
{'param': requsetData }, function(responseData) {
    // do something
});

  • bridge.registerHandler:註冊一個js方法,java層可呼叫此方法
引數說明:
第一個:方法名
第二個:回撥方法,用於接收java層回覆給js的訊息;responseCallback()用於回調回java層
bridge.registerHandler("functionInJs",function(data,responseCallback){
    alert("data from Java = " + data);
                             document.getElementById("msgFromJava").innerHTML = "data from Java = " + data;
    var responseData = "Javascript Says Right back aka!";  
    responseCallback(responseData);
});

  • window.WebViewJavascriptBridge.send:js向java層傳送訊息
引數說明:
第一個:js發給java的訊息
第二個:回撥方法,接收java回覆給js的訊息
window.WebViewJavascriptBridge.send(data,function(responseData){
    alert(responseData);
});

程式碼演示

1.Activity

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.mWebView)
    BridgeWebView mWebView;

    private String webUrl = "file:///android_asset/demo.html";
    //private String webUrl = "https://www.baidu.com";
    private static CallBackFunction mfunction;
    int RESULT_CODE = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ButterKnife.bind(this);

        initWebView();
    }

    private void initWebView() {
        mWebView.setWebViewClient(new MyWebViewClient(mWebView));
        mWebView.setDefaultHandler(new MyHandlerCallback());
        mWebView.setWebChromeClient(new WebChromeClient());
        //載入伺服器網頁或者本地網頁
        mWebView.loadUrl(webUrl);

        //必須和js同名函式,註冊具體執行函式,類似java實現類。
        mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                //對原始資料進行處理
                String str = "這是html返回給java的資料:" + data;
                str += ",對原始資料進行了擷取操作:" + str.substring(0, 5);
                showToast(str);
                //回撥返回給JS
                function.onCallBack(str);
            }
        });

        //開啟本地檔案
        mWebView.registerHandler("openFile", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                pickFile();
                mfunction = function;
            }
        });

        //模擬使用者獲取本地位置
        User user = new User();
        user.name = "alice";
        Location location = new Location();
        location.address = "上海";
        user.location = location;
        //webView呼叫js的方法
        mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                showToast(data);
            }
        });
        mWebView.send("Hello!");
    }

    private void showToast(String string) {
        Toast.makeText(MainActivity.this, string, Toast.LENGTH_SHORT).show();
    }

     public void pickFile() {
        Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
        chooserIntent.setType("image/*");
        startActivityForResult(chooserIntent, RESULT_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == RESULT_CODE) {
            Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
            mfunction.onCallBack(result.toString());
        }
    }

    class MyWebViewClient extends BridgeWebViewClient {

        public MyWebViewClient(BridgeWebView webView) {
            super(webView);
        }
    }

    class MyHandlerCallback extends DefaultHandler {
        @Override
        public void handler(String data, CallBackFunction function) {
            super.handler(data, function);
            if (null != function) {
                showToast(data);
                function.onCallBack("收到JS訊息回覆。");
            }
        }
    }

    static class Location{
        String address;
    }

    static class User{
        String name;
        Location location;
        String testStr;
    }

}

2.JS

<html>
<head>
    <meta content="text/html; charset=utf-8" http-equiv="content-type">
    <title>js呼叫java</title>
    <script>
            //註冊事件監聽
            function connectWebViewJavascriptBridge(callback){
                if(window.WebViewJavascriptBridge){
                    callback(WebViewJavascriptBridge);
                }else{
                    document.addEventListener(
                        "WebViewJavascriptBridgeReady",
                        function(){
                            callback(WebViewJavascriptBridge);
                        },
                        false
                    );
                }
            }

             //註冊回撥函式,第一次連線時呼叫 初始化函式
            connectWebViewJavascriptBridge(function(bridge){
                bridge.init(function(message, responseCallback){
                    alert("JS get a message : " + message);
                    var data = {
                        "JavaScript Responds" : "Wee!" 
                    };
                    //alert("Js response data : " + data);
                    responseCallback("Js response data wee ");
                });

                            bridge.registerHandler("functionInJs",function(data,responseCallback){
                    alert("data from Java = " + data);
                    document.getElementById("msgFromJava").innerHTML = "data from Java = " + data;
                    var responseData = "Javascript Says Right back aka!";  
                    responseCallback(responseData);
                });

            })

            function sendMsgToNative(){
                var name = document.getElementById("username").value;
                var pwd = document.getElementById("password").value;

                //呼叫本地java方法
                window.WebViewJavascriptBridge.callHandler("submitFromWeb",
                {"name":name},
                function(responseData){
                    alert("data from Java = " + responseData);
                    document.getElementById("msgFromJava").innerHTML = "data from Java = " + responseData;
                });

                var data = "name=" + name + ",pwd=" + pwd;
                //傳送訊息給java程式碼
                window.WebViewJavascriptBridge.send(data,function(responseData){
                     alert(responseData);
                });
            }

            function openFile(){               window.WebViewJavascriptBridge.callHandler("openFile","",function(responseData){
                    //alert("openFile response : " + responseData);
                    document.getElementById("pic").src = responseData;
                });
            }
    </script>
</head>

<body>
<p>使用者名稱:<input type="text" id="username"/></p>
<p>密碼:<input type="text" id="password"/></p>
<p>Java資訊測試:<input type="text" id="msgFromJava"/></p>
<p><input type="button" value="發訊息給Native" onclick="sendMsgToNative();"/></p>
<p><input type="button" value="呼叫Native方法"/></p>
<p><input type="button" value="顯示html"/></p>
<p><input type="file" value="選擇檔案" onclick="openFile();"/></p>
<img id="pic" src="" width="100%" height="auto"/>
</body>
</html>

效果圖:
這裡寫圖片描述
注意事項:
無論怎樣形式的互動,Js 必須要初始化jsBridge

四 總結

通過以上的API介紹,程式碼示例,不難發現此框架的優雅和簡便,對js和java雙方來說,如果Html中的js需要呼叫java程式碼,而java程式碼沒做任何實現,那麼js中方法也是無效的,反之java程式碼註冊的函式,沒在js裡去回撥實現,那麼Java層也是無法獲取js中資料的,由此可見,此通訊是雙方支援的,必須由雙方來約定,這樣就避免了Android之前存在的js注入漏洞,也很大的提高了安全性,也可以保證我們的網頁資料不被第三方的APP獲取,具體來講,列如我們的專案某一個web的h5介面,被系統瀏覽器或者其他第三方App的惡意載入,那麼它的java程式碼想呼叫你的js函式,實現需要你的H5的Js先註冊,不然跟本無法呼叫你的h5資訊。這樣保證了這個html資料的安全性,,第三方的瀏覽器可以載入預覽你的網頁,但是第三方java無法和你的的h5中的js互動通訊的。同樣載入我們自己的APP載入第三方的網頁時候,我們可以對第三方網頁進行一些行為的過濾,方便保護我們手機的安全,列如第三方可以獲取本機地址時我們可以提示使用者授權。雖然H5並不屬於外掛的一種,但是藉助h5我可以方便的去更新一些運營活動,和某些需要經常需要更換UI的業務功能的地方,以上只JSBridge的使用姿勢,,以後再給大家解剖下JsBridge的內部實現。