Android混合開發之WebViewJavascriptBridge實現JS與java安全互動

總李寫程式碼發表於2016-11-29

前言:

     為了加快開發效率,目前公司一些功能使用H5開發,這裡難免會用到Js與Java函式互相呼叫的問題,這個Android是提供了原生支援的,不過存在安全隱患,今天我們來學習一種安全方式來滿足Js與java互相呼叫的需求。它就是WebViewJavascriptBridge。

學習動機:

    先看下之前的解決辦法:Android混合開發之WebView與Javascript互動

    最近棒棒安全的一個市場推廣來我們公司推廣他們的產品,當時也沒太引起我的注意,後來這個市場推廣人員把我們的app的進行了他們的安全驗證,然後發給我一份檢測報告,關於WebView的檢測內容大致如下:

其實目前公司採用H5的業務都是相對不是很重要的一些業務,而且安全性要求相對比較低,不過作為技術負責人的我,覺得現在很有必要儘快尋找一個相對安全的方式來解決這個問題,算是未雨綢繆吧。經過搜過資料尋找的解決辦法就是使用WebViewJavascriptBridge來實現Js與Java的互相呼叫。

WebViewJavascriptBridge介紹:

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

 目前實現JSBridge的開源框架很多,這裡採用的hi大頭鬼hi寫的開源框架:https://github.com/lzyzsd/JsBridge

WebViewJavascriptBridge使用方式:

1.)新增配置資訊

project的build.gradle中新增如下配置

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

在module的build.pradle中新增如下配置

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

2.)用BridgeWebView替換WebView

 <com.github.lzyzsd.jsbridge.BridgeWebView
        android:id="@+id/test_bridge_webView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

3.)Js呼叫Java方法並傳遞資料

 可以通過registerHandler()用來註冊一個java函式,來實現js回撥的handler

//必須和js同名函式,註冊具體執行函式,類似java實現類。
        //第一引數是訂閱的java本地函式名字 第二個引數是回撥Handler , 引數返回js請求的resqustData,function.onCallBack()回撥到js,呼叫function(responseData)
        mBridgeWebView.registerHandler("submitFromWeb", new BridgeHandler() {

            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e(TAG, "指定Handler接收來自web的資料:" + data);
                function.onCallBack("指定Handler收到Web發來的資料,回傳資料給你");
            }
        });

Js呼叫指定函式並傳遞引數

 function testClick1() {
           //呼叫本地java方法
           //第一個引數是 呼叫java的函式名字 第二個引數是要傳遞的資料 第三個引數js在被回撥後具體執行方法,responseData為java層回傳資料
           var data='傳送訊息給java程式碼指定接收';
           window.WebViewJavascriptBridge.callHandler(
               'submitFromWeb'
               ,data
               , function(responseData) {
                   bridgeLog('來自Java的回傳資料: ' + responseData);
               }
           );
       }

也可以mBridgeWebView.setDefaultHandler()設定DefaultHandler,這樣可以接收Js通過window.WebViewJavascriptBridge通過send的所有資料

mBridgeWebView.setDefaultHandler(new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e(TAG, "DefaultHandler接收全部來自web的資料:"+data);
                function.onCallBack("DefaultHandler收到Web發來的資料,回傳資料給你");
            }
        });

js實現向java傳送資料

       function testClick() {
           //傳送訊息給java程式碼
           var data = '傳送訊息給java程式碼全域性接收';

           window.WebViewJavascriptBridge.send(
               data
               , function(responseData) {
                  bridgeLog('來自Java的回傳資料: ' +responseData);
               }
           );
       }

4.)Java呼叫Js方法並傳遞引數

       //註冊事件監聽
       function connectWebViewJavascriptBridge(callback) {
           if (window.WebViewJavascriptBridge) {
               callback(WebViewJavascriptBridge)
           } else {
               document.addEventListener(
                   'WebViewJavascriptBridgeReady'
                   , function() {
                       callback(WebViewJavascriptBridge)
                   },
                   false
               );
           }
       }

在使用WebViewJavaScriptBridge的時候需要首先判斷一下WebViewJavaScriptBridge是否存在,如果不存在需要通過新增監聽'WebViewJavascriptBridgeReady'來監聽

  //註冊回撥函式,第一次連線時呼叫 初始化函式
       connectWebViewJavascriptBridge(function(bridge) {
           bridge.init(function(message, responseCallback) {
               bridgeLog('預設接收收到來自Java資料: ' + message);
               var responseData = '預設接收收到來自Java的資料,回傳資料給你';
               responseCallback(responseData);
           });

           bridge.registerHandler("functionInJs", function(data, responseCallback) {
               bridgeLog('指定接收收到來自Java資料: ' + data);
               var responseData = '指定接收收到來自Java的資料,回傳資料給你';
               responseCallback(responseData);
           });
       })

通過上面的連結WebViewJavascriptBridge可以得到一個可用WebViewJavascriptBridge,可以通過init方法來設定一個預設接收所以java發來的資料的回撥,也可以通過registerHandler設定指定接收方法。

java傳送資料給Js預設接收

   mBridgeWebView.send("傳送資料給web預設接收",new CallBackFunction(){
                    @Override
                    public void onCallBack(String data) {
                        Log.e(TAG, "來自web的回傳資料:" + data);
                    }
                });

java傳送資料給Js指定方法接收

  mBridgeWebView.callHandler("functionInJs","傳送資料給web指定接收",new CallBackFunction(){
                    @Override
                    public void onCallBack(String data) {
                        Log.e(TAG, "來自web的回傳資料:" + data);
                    }
                });

 5.)整個示例

為了方便學習,貼出整個示例

 MainActivity

public class MainActivity extends AppCompatActivity {
private static  final  String TAG=MainActivity.class.getSimpleName();
    private BridgeWebView mBridgeWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void  initViews(){
        mBridgeWebView= (BridgeWebView) findViewById( R.id.test_bridge_webView);
        mBridgeWebView.loadUrl("file:///android_asset/wx.html");

        mBridgeWebView.setDefaultHandler(new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e(TAG, "DefaultHandler接收全部來自web的資料:"+data);
                function.onCallBack("DefaultHandler收到Web發來的資料,回傳資料給你");
            }
        });

        //必須和js同名函式,註冊具體執行函式,類似java實現類。
        //第一引數是訂閱的java本地函式名字 第二個引數是回撥Handler , 引數返回js請求的resqustData,function.onCallBack()回撥到js,呼叫function(responseData)
        mBridgeWebView.registerHandler("submitFromWeb", new BridgeHandler() {

            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e(TAG, "指定Handler接收來自web的資料:" + data);
                function.onCallBack("指定Handler收到Web發來的資料,回傳資料給你");
            }
        });
        findViewById(R.id.to_web_default).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mBridgeWebView.send("傳送資料給web預設接收",new CallBackFunction(){
                    @Override
                    public void onCallBack(String data) {
                        Log.e(TAG, "來自web的回傳資料:" + data);
                    }
                });
            }
        });
        findViewById(R.id.to_web).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mBridgeWebView.callHandler("functionInJs","傳送資料給web指定接收",new CallBackFunction(){
                    @Override
                    public void onCallBack(String data) {
                        Log.e(TAG, "來自web的回傳資料:" + data);
                    }
                });
            }
        });
    }
}
View Code
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.whoislcj.jsbridge.MainActivity">

    <com.github.lzyzsd.jsbridge.BridgeWebView
        android:id="@+id/test_bridge_webView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
    <Button
        android:id="@+id/to_web_default"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:text="預設傳遞資料給Web"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/to_web"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:text="指定傳遞資料給Web"
        android:layout_height="wrap_content"/>
</LinearLayout>
View Code

wx.html

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
    <script >

       function testClick() {
           //傳送訊息給java程式碼
           var data = '傳送訊息給java程式碼全域性接收';
           //第一個引數要傳送的資料 第二個引數js在被回撥後具體執行方法,responseData為java層回傳資料
           window.WebViewJavascriptBridge.send(
               data
               , function(responseData) {
                  bridgeLog('來自Java的回傳資料: ' +responseData);
               }
           );
       }

       function testClick1() {
           //呼叫本地java方法
           //第一個引數是 呼叫java的函式名字 第二個引數是要傳遞的資料 第三個引數js在被回撥後具體執行方法,responseData為java層回傳資料
           var data='傳送訊息給java程式碼指定接收';
           window.WebViewJavascriptBridge.callHandler(
               'submitFromWeb'
               ,data
               , function(responseData) {
                   bridgeLog('來自Java的回傳資料: ' + responseData);
               }
           );
       }

       function bridgeLog(logContent) {
           document.getElementById("log_msg").innerHTML = logContent;
       }

       //註冊事件監聽
       function connectWebViewJavascriptBridge(callback) {
           if (window.WebViewJavascriptBridge) {
               callback(WebViewJavascriptBridge)
           } else {
               document.addEventListener(
                   'WebViewJavascriptBridgeReady'
                   , function() {
                       callback(WebViewJavascriptBridge)
                   },
                   false
               );
           }
       }
       //註冊回撥函式,第一次連線時呼叫 初始化函式
       connectWebViewJavascriptBridge(function(bridge) {
           bridge.init(function(message, responseCallback) {
               bridgeLog('預設接收收到來自Java資料: ' + message);
               var responseData = '預設接收收到來自Java的資料,回傳資料給你';
               responseCallback(responseData);
           });

           bridge.registerHandler("functionInJs", function(data, responseCallback) {
               bridgeLog('指定接收收到來自Java資料: ' + data);
               var responseData = '指定接收收到來自Java的資料,回傳資料給你';
               responseCallback(responseData);
           });
       })
   </script>

</head>
<body>
<p>WebViewJsBridge</p>
<div>
    <button onClick="testClick()">傳送資料給預設Handler接收</button>
</div>
<br/>
<div>
    <button onClick="testClick1()">傳送資料給指定Handler接收</button>
</div>
<br/>
<div id="log_msg">呼叫列印資訊</div>
</body>
</html>
View Code

 總結:

    這裡僅僅是先找到了一種安全的呼叫方式,並沒有進行真正的商用驗證,接下來會對這個框架進一步瞭解,然後推廣使用。

相關文章