Native(以Android为例)和H5通讯,基本原理:

  • Android调用H5:通过webview类的loadUrl方法可以直接执行js代码,类似浏览器地址栏输入一段js一样的效果
String javascriptCommand = String.format("javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');", messageJson);webview.loadUrl(javascriptCommand);
  • H5调用Android:webview可以拦截H5发起的任意url请求,webview通过约定的规则对拦截到的url进行处理(消费),即可实现H5调用Android
  var WVJBIframe = document.createElement('iframe');  WVJBIframe.style.display = 'none';  WVJBIframe.src = 'ddjsscheme://***';  document.documentElement.appendChild(WVJBIframe);

实现步骤

1. H5调用原生方法实现步骤

h5调用原生方法有4个关键点:

1. 通讯媒介——原生通讯协议
ddjsscheme://return/_fetchQueue/[{"handlerName":"dd.native.call","data":{"handlerName":"biz.navigation.setTitle","params":{"title":"标题"}},"callbackId":"cb_1_1504142561851"}]

通过webview拦截到自定义约定的协议,就可以做相应的处理。

2. 通讯行为——触发

能被原生监听并捕获截拦的H5行为,都可以作为原生通讯协议的触发行为。Hybrid H5 的这类行为有 console.log、alert、confirm、prompt、location.href 等。将原生协议内容通过其中的某一行为触发,即可被原生正确捕获并解析。本文通过:WVJBIframe.src = 'ddjsscheme://';触发。

触发详细流程

当h5页面调用如下接口时

dd.biz.navigation.setTitle({    title: '邮箱正文',    onSuccess: function(data) {},    onFail: function(error) {}})

接着会调用此方法

function _executeFuction (methodName, options) {  //alert('_executeFuction' + methodName)  if (dd.isInit) {    !!window['_' + methodName] && window['_' + methodName](options)  } else {    !!dd && setupWebViewJavascriptBridge(function () {      if (window.WebViewJavascriptBridge.init) {        if (!dd.isInit) {          //alert('WebViewJavascriptBridge init')          window.WebViewJavascriptBridge.init(function(message, responseCallback) {})        }      }      dd.isInit = true;      setTimeout(function() {         !!window['_' + methodName] && window['_' + methodName](options)       }, 100);    });  }}

以本人的半杯水js水平来看,先调用setupWebViewJavascriptBridge方法:

function setupWebViewJavascriptBridge(callback) {  if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }  if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }  window.WVJBCallbacks = [callback];  var WVJBIframe = document.createElement('iframe');  WVJBIframe.style.display = 'none';  WVJBIframe.src = 'ddjsscheme://__bridge_loaded__';  document.documentElement.appendChild(WVJBIframe);  setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)}

如果WebViewJavascriptBridge加载完成就调用window.WebViewJavascriptBridge.init(function(message, responseCallback) {});如果没有加载完成WVJBIframe.src = 'ddjsscheme://__bridge_loaded__';然后native拦截加载本地的WebViewJavascriptBridge

if (url.equals("ddjsscheme://__bridge_loaded__")) {    //加载bridge    BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);}

接着回来说window.WebViewJavascriptBridge.init(function(message, responseCallback) {})

   function init(messageHandler) {        if (WebViewJavascriptBridge._messageHandler) {            throw new Error('WebViewJavascriptBridge.init called twice');        }        WebViewJavascriptBridge._messageHandler = messageHandler;        var receivedMessages = receiveMessageQueue;        receiveMessageQueue = null;        for (var i = 0; i < receivedMessages.length; i++) {            _dispatchMessageFromNative(receivedMessages[i]);        }    }//提供给native使用,    function _dispatchMessageFromNative(messageJSON) {        setTimeout(function() {            var message = JSON.parse(messageJSON);            var responseCallback;            //java call finished, now need to call js callback function            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.data, message.data, exception);                    }                }            }        });    }  //sendMessage add message, 触发native处理 sendMessage    function _doSend(message, responseCallback) {        if (responseCallback) {            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();            responseCallbacks[callbackId] = responseCallback;            message.callbackId = callbackId;        }        sendMessageQueue.push(message);        messagingIframe.src ='ddjsscheme://__WVJB_QUEUE_MESSAGE__';    }

js不太懂,能看懂流程就可以啦,特别注意_doSend方法里的这行sendMessageQueue.push(message);代码,message就是就是我们从h5调用接口传过来的参数,后面会用到!这时又发出一条协议messagingIframe.src ='ddjsscheme://__WVJB_QUEUE_MESSAGE__';,我们再去本地看看做了什么操作:

 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {   if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //拦截'ddjsscheme://__WVJB_QUEUE_MESSAGE__'       webView.flushMessageQueue();       return true;   } else {       return shouldOverrideUrl(view, url);   } }
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 list = null;                    try {                        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++) {                        Message m = list.get(i);                        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                            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);                                    }                                };                            }                            BridgeHandler handler;                            if (!TextUtils.isEmpty(m.getHandlerName())) {                                handler = messageHandlers.get(m.getHandlerName());                            } else {                                handler = defaultHandler;                            }                            if (handler != null){                                handler.handle(m.getData(), responseFunction);                            }                        }                    }                }            });        }    }public void loadUrl(String jsUrl, CallBackFunction returnCallback) {        this.loadUrl(jsUrl);        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback)}

这里的回调暂时先不关注,后面我们在分析,注意responseCallbacks这个是个 map集合。它的value就是一个回调函数,同时还需知道responseFunction这个是native返回给h5的回调,这里只需知道有这么个概念,后面我们会讲解。this.loadUrl(jsUrl);这里又加载了一段jsjavascript:WebViewJavascriptBridge._fetchQueue();

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);}

这是重点! 这是重点! 这是重点!重要的事说三遍!这里把sendMessageQueue转换成json格式方便native解析,然后把sendMessageQueue回收掉,又到了我们熟悉的

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);

再看一下本地做了什么操作:

@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据            webView.handlerReturnData(url);            return true;        } else {            return shouldOverrideUrl(view, url);        } }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;        }}

这里f.onCallBack(data);调用的就是flushMessageQueue()里的回调,data就是h5传过来的参数

[{"handlerName":"dd.native.call","data":{"handlerName":"biz.navigation.setTitle","params":{"title":"标题"}},"callbackId":"cb_2_1504156072331"}]

第一个handlerName是作为messageHandlersmap的键;
第二个handlerName是native方法路径
params是h5传过来的设置标题的值
callbackId用来回调的在回调这节再去讲解
至此,h5与native通信的触发过程讲解完毕,下节我们一起看看flushMessageQueue()里的回调是如何做处理的。

3. 通讯行为——处理

flushMessageQueue()里有这么一段代码

if (!TextUtils.isEmpty(m.getHandlerName())) {      handler = messageHandlers.get(m.getHandlerName());} else {      handler = defaultHandler;}if (handler != null) {                                handler.handle(m.getData(), responseFunction);}

这个handler意义在于处理h5传过来的数据,使用之前需要注册:

public void registerHandler(String handlerName, BridgeHandler handler) {        if (handler != null) {            messageHandlers.put(handlerName, handler);        }}webView.registerHandler(sFunction, new BridgeHandler() {            @Override            public void handle(String data, CallBackFunction function) {                dispatchTask(data, function);            }});

注册时需要键值对,键就是之前说的第一个handlerName,value是BridgeHandler,调用handle(String data, CallBackFunction function)时传入就data是:

{"handlerName":"biz.navigation.setTitle","params":{"title":"标题"}}

function就是用于回调给h5的,下面再看一下dispatchTask(data, function);

private void dispatchTask(String data, CallBackFunction callBackFunction) {        try {            JSONObject jo = new JSONObject(data);            final String handlerName = jo.getString("handlerName");            String params = jo.getString("params");            Log.d(TAG, "# ============ ");            Log.d(TAG, "# request call ---> " + handlerName);            Log.d(TAG, "# request params ---> " + params);            // 接口调用每次都只执行一次,为了能多次执行,这里需要再重复调用原来的接口            if (null == callBackFunction) {                callBackFunction = new CallBackFunction() {                    @Override                    public void onCallBack(String data) {                        mWebView.callHandler(handlerName, data, null);                    }                };            }            String[] split = handlerName.split("\\.");            String packageName = "***";            String className = split[split.length - 2];            String methodName = split[split.length - 1];            for (int i = 0; i < split.length - 2; i++) {                packageName += "." + split[i];            }            packageName +=                    "." + Character.toUpperCase(className.charAt(0)) + className.substring(1);            Class<?> clazz = Class.forName(packageName);            Constructor constructor = clazz.getConstructor(BridgeWebView.class, Context.class);            Object obj = constructor.newInstance(mWebView, this);            Method method = clazz.getMethod(methodName, String.class, CallBackFunction.class);            method.invoke(obj, params, callBackFunction);        } catch (JSONException | IllegalAccessException | InvocationTargetException                | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {            e.printStackTrace();        }}

知道了方法路径,参数利用反射一切都水到渠成了,再看一下setTitle的处理:

public void setTitle(String data, CallBackFunction callBackFunction) {        String result = null;        JSONObject jo = new JSONObject();        try {            Log.d("setTitle==TTT===", data);            JSONObject params = new JSONObject(data);            String title = params.optString("title", "");            if (mContext != null) {                ((HybridActivity) mContext).setHeadTitle(title);            }            jo.put("result", true);            result = new ResultCallBack().onSuccessResult(jo);        } catch (JSONException e) {            e.printStackTrace();            try {                jo.put("result", false);            } catch (JSONException e1) {                e1.printStackTrace();            }            result = new ResultCallBack()                    .onFailResult(jo);        }        callBackFunction.onCallBack(result);}

这样h5调用native的处理步骤讲解完毕,下面通过CallBackFunction讲解如何回调给js的。

4. 通讯行为——回调

还记得在触发那个步骤中的flushMessageQueue的这一段代码吗:

CallBackFunction responseFunction = null;    // if had callbackId    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);        }    };}

当调用这段callBackFunction.onCallBack(result);代码,就会回调到上面那段代码,在queueMessage(responseMsg);又会调用

String javascriptCommand = String.format("javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');", messageJson);webview.loadUrl(javascriptCommand);

js会把参数回调给h5,这样h5调用native的流程就走通了。

2. 原生调用H5方法实现步骤

未完待续

参考文章

Hybrid APP架构设计思路
解耦---Hybrid H5跨平台性思考

更多相关文章

  1. Android MediaPlayer 常用方法介绍
  2. Android 自定义组件 方法篇
  3. android中HttpURLConnection调用getResponseCode()时崩溃 解决方
  4. android studio调试c++代码
  5. Android自学笔记(Android x86模拟器Intel Atom x86 System Image
  6. android Button组件的属性和方法
  7. Android 中数据库查询方法 query() 中的 select
  8. Android空引用问题的解决方法——on a null object reference

随机推荐

  1. PreferenceActivity和PreferenceFragment
  2. Android实现数据存储技术
  3. 关于Android(安卓)横竖屏切换(避免Activit
  4. 【流媒體】Android(安卓)实时视频采集/编
  5. Android(安卓)Fresco图片处理库用法API英
  6. Android(安卓)之 退出的2种方式
  7. Android音频实时传输与播放(三):AMR硬编码与
  8. Android是什么?
  9. Android的 getSystemService
  10. 记录Android开发中遇到的一些问题