hybrid(H5)与客户端通信

4,362 阅读3分钟

什么是hybrid?

就是嵌入在客户端的H5页面, 也就是我们常说的webview.它跟浏览器中的html页面区别不大.因为大都是由webkit渲染引擎渲染出来的.

webkit渲染引擎主要由下面几个部分组成

  1. js引擎线程
  2. GUI渲染线程
  3. 定时器线程
  4. 异步http线程
  5. 事件触发线程

异步事件处理都是通过Event Loop. 所以webview浏览器页面对于前端开发者来说根本没啥区别。但是webview是运行在AndroidIOS系统里面,那它们之间是如何进行通信的呢?下面我将以Android为例来探究他们之间的通信方式

先搭建环境

第一步

android新建一个简单的项目

第二步

建一个简单html页面如下,放在android项目的src/main/assets目录里面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>shop</title>
</head>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript">
</script>
<body>
<div>我是webview</div>
<div id="cs">测试JavascriptInterface</div>
</body>
<script>
    let i = 0;
    $("#cs").on("click", function(){
        android.showToast("我是弹窗");
    })
</script>
</html>

第三步

载入webview

 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 开始webview调试模式
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        WebView.setWebContentsDebuggingEnabled(true);
    }
    WebView webView = (WebView)findViewById(R.id.shop);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.loadUrl("file:///android_asset/shop.html");
}

页面就出来了

hybridAndroid通信方式

第一种 JavascriptInterface

就是在js上下文注入一个JavascriptInterface.假设这个JavascriptInterface名为AndroidIn. 那么在webview里面可以直接调用Android

android代码

WebView webView = (WebView)findViewById(R.id.shop);
webView.getSettings().setJavaScriptEnabled(true);
 class JsInterface {
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
    }
}
webView.addJavascriptInterface(new JsInterface(), "AndroidIn");

html代码

<div>我是webview</div>
<div id="cs">测试JavascriptInterface</div>
</body>
<script>
    let i = 0;
    $("#cs").on("click", function(){
        AndroidIn.showToast("我是弹窗");
    })
</script>

点击之后出现我是弹窗

第二种 onJsAlert, onJsConfirm, onJsPrompt

android可以拦截js调用警告框,输入框,和确认框

android代码

WebView webView = (WebView)findViewById(R.id.shop);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient(){
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        new AlertDialog.Builder(MainActivity.this)
                .setTitle("JsAlert")
                .setMessage(message)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                })
                .setCancelable(false)
                .show();
        return true;
    }
});

html代码

<div>我是webview</div>
<div id="cs">测试JavascriptInterface</div>
</body>
<script>
    let i = 0;
    $("#cs").on("click", function(){
        alert("我是弹窗");
    })
</script>

点击之后弹窗已经换成了原生的弹窗了

第三种 URL参数

android载入h5文件的时候在地址上加一些参数

 webView.loadUrl("file:///android_asset/shop.html?a=12&b=12");

h5页面通过href链接可以拿到a,b

第四种loadUrl

android可以通过loadUrl直接执行js的方法 H5代码

window.Bridge = {
    callJS(){
      alert("我是callJS")
    }
}

android代码

webView.loadUrl("javascript:Bridge.callJS()");

第五种evaluateJavascript

跟上面差不多,只不过android 4.4以上才能用

webView.evaluateJavascript("javascript:Bridge.callJS()", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        //此处为 Bridge.callJS返回的结果
    }
});

第六种 JsBridge

什么是Jsbridge? JsBridge并不是Android或者H5直接就有的API.它是一种通过上面五种通信方式(遵循某种协议)来实现的一个双向通信桥. 下面来实现一个简单的 JsBridge

  1. 每次调用原生方法都会生成一个对应callBack唯一id.客户端回调会告诉返回对应的id来执行对应的callback
  2. callNativeH5调用Android原生的方法,是通过JavascriptInterface注入一个nativeBridge来实现的.
  3. receiveNativeAndroid原生调用H5的方法,是通过webView.loadUrl("javascript:window.Bridge.receiveNative("")来实现.

你能调用我,我能调用你,然后通过某个协议(id).一个双工通信就实现了

H5代码如下

<body>
    <div>我是webview</div>
    <div id="cs">测试JavascriptInterface</div>
</body>
<script>
    let cid = 0;
    let callbacks = {};
    window.Bridge = {
        // 获取用户的登录信息
        getUserInfo(data = {}){
            window.Bridge.callNative("getUserInfo", data)
        },
        // 获取网络情况
        getNetInfo(data = {}){
            window.Bridge.callNative("getNetInfo", data)
        },
        callNative: function(bridgeName, data) {
            cid++;
            if (data.onSuccess) {
                callbacks[cid] = data.onSuccess;
            }
            nativeBridge.postMessage(JSON.stringify({
                cid,
                bridgeName: bridgeName,
                data: data.params || {}
            }));
         },
        receiveNative: function(msg) {
            callbacks[msg.cid] && callbacks[msg.cid](msg);
            if (msg.bridgeName != "getNetInfo") {
                Reflect.deleteProperty(callbacks, msg.cid);
            }
        }
    }
    document.getElementById("cs").onclick = function(){
        window.Bridge.getUserInfo({
            params:{},
            onSuccess(res){
                alert(res.name)
            }
        })
    }
</script>

Android代码如下

final WebView webView = (WebView)findViewById(R.id.h5_shop);
final Handler handler = new Handler(){
    public void handleMessage(Message msg) {
        if (msg.what == 1) {
            webView.loadUrl("javascript:window.Bridge.receiveNative("+resObj.toString()+")");
        }
    }
};
webView.getSettings().setJavaScriptEnabled(true);
class JsInterface {
    @JavascriptInterface
    public void postMessage(String a) throws JSONException{
        JSONObject jsonObj = new JSONObject(a);
        String bridgeName = jsonObj.getString("bridgeName");
        if ( bridgeName.equals("getUserInfo") ){
            resObj.put("cid", jsonObj.get("cid"));
            resObj.put("name", "leiwuyi");
            Message msg = new Message();
            msg.what = 1;
            handler.sendMessage(msg);
        } 

    }
}
webView.addJavascriptInterface(new JsInterface(), "nativeBridge");
webView.loadUrl("file:///android_asset/shop.html");

点击之后效果如下

demo地址如下github