JsBridge框架原理全解析

  • 前言
  • JsBridge的整体框架
  • JsBridge那些是值得我们学习的方面
  • JsBridge是如何调用JavaScrpit方法的
    • 开源库是如何调用JavaScript的方法的呢?
    • 将参数封装为Message
    • 分发处理
  • JsBridge是如何定义方法开放API给JavaScript调用的
  • JsBridge接收到JavaScript的消息后如何处理
  • 总结

前言

GitHub源代码

JsBridge是一个很流行的Native和Js双向通信库,目前Start-6713,对于进行Hybrid APP 开发的程序员来说,是一个不得不进行深度研究的基础库,值得借鉴。
整个JsBridge库非常简单简单,使用的核心原理就是利用了两点特性:

  • WebView.loadUrl调用Js方法
  • BridgeWebViewClient.shouldOverrideUrlLoading接收Js的调用

JsBridge的整体框架

JsBridge共有八个类,其中BridgeWebView和BridgeWebViewClient是最重要的两个类,BridgeWebView是WebView的自定义,封装了向JavaScript发送信息的方法和处理JavaScript返回的数据,BridgeWebViewClient是对JavaScript使用URL发送过来的分数据进行拦截和分发。

描述
BridgeWebView 负责封装发送的数据;处理返回的数据;管理响应方法、发送消息方法;设置统一处理的BridgeHandler方法
BridgeWebViewClient 使用shouldOverrideUrlLoading拦截和方法到BridgeWebView处理
CallBackFunction 所有回调响应对象的接口,用于规范响应对象
BridgeHandler Bridge操作接口,是响应JS调用的处理对象接口,每个在本地注册的方法都应当使用BridgeHandler作为接口
Message 封装的消体对象
BridgeUtil 工具类,里面编码了一些协议、常量、从URI解析出数据的常用方法、给WebView注入脚本的代码
WebViewJavascriptBridge 库里的WebView实现了这个接口,更类似适配器模式的作用,但是实践应用中并没有看出多大的价值

JsBridge那些是值得我们学习的方面

通读了整JsBridge后,觉得JsBridge是个很好的框架,首先JsBridge很简单,对于一般的初学者,想要阅读整篇代码也不是很困难,使用的都是一些简单的运用,而且目前还没有封装的非常深,但是如果有一些JavaScript基础对于了解整个项目来说会是一种事半功倍的感觉,所以建议同学们如果想要更了解JsBridge框架,能去先了解一下JavaScript基础。尽管JsBridge简单,但是却是一个很不错的框架,我将以我学习后的感悟,从我学习到的内容开始,一步步深入的了解JsBridge:

  • C/S 模型的切换,是框架的核心思想。
  • Map +策略模式 维护请求和响应对象

C/S 模型的切换:
什么叫C就是Client,S指代Service,在Android调用JavaScript方法的时候,可以把Android端看作C端,把前端看成是B端,当JavaScript 调用Android的方法时,可以把前端看成是C端,把Android端看成是B端,在JsBridge中Android调用JavaScript方法时,第一步是Android扮演C端发送信息给前端,前端扮演B端接收到信息后处理对应的方法,第二步是前端处理完结后扮演C端端角色发送信息给Android 端,Android端扮演B端的角色处理响应的数据,就完成了整个双向通信流程。

Map +策略模式 :
策略模式用来封装算法的设计模式,首先我们开发的时候可以预想到需要开发给JavaScript的方法是会随着业务的扩展而不断增加的,如果仅仅使用传统的使用一个个方法来定义,会让业务代码非常复杂,不利于扩展,那么使用策略模式,就很好的解决了一个问题,定一个策略接口,对不同的方法标示取出不同的方法实现进行调用,而Map是为了消除大量的if-else判断,结合策略模式来用,让代码更具扩展性。

JsBridge是如何调用JavaScrpit方法的

库调用JavaScript的API是BridgeWebView.callHandler(String handlerName, String data, CallBackFunction callBack),从这个方法开始,我们开始逐层深入经过的所有调用流程:

  • BridgeWebView.callHandler(String handlerName, String data, CallBackFunction callBack)
  • BridgeWebView.doSend(String handlerName, String data, CallBackFunction responseCallback)
  • BridgeWebView.queueMessage(Message m)
  • BridgeWebView.dispatchMessage(Message m)

大致梳理完我们先提出一个疑问,开源库是如何调用JavaScript的方法的呢?

开源库是如何调用JavaScript的方法的呢?

我们先看BridgeWebView.dispatchMessage(Message m) 的重点代码:

 void dispatchMessage(Message m) {.....String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);// 必须要找主线程才会将数据传递出去if (Thread.currentThread() == Looper.getMainLooper().getThread()) {this.loadUrl(javascriptCommand);}}

从代码中,我们看到有WebView.loadUrl(url)的调用,然后我们看一下javascriptCommand字段表示的是什么,可以看JS_HANDLE_MESSAGE_FROM_JAVA常量

 final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";

JS_HANDLE_MESSAGE_FROM_JAVA常量里面有一个**javascript:**协议头,我们能联想到,这是一个最基础的调用JavaScript知识点,也是进行原生单向调用JavaScript的方法。在发送消息前还需要对一些特殊字符的转义,大家可以看看,这里我就不介绍来。
看完最核心的内容,我们可以看看在发送消息前,库都做了那些处理。

将参数封装为Message

callHandler(String handlerName, String data, CallBackFunction callBack) 只有一个操作,就是调用doSend方法,它仅仅起到一个转发内部私有方法的作用。

 private void doSend(String handlerName, String data, CallBackFunction responseCallback) {Message m = new Message();if (!TextUtils.isEmpty(data)) {m.setData(data);}if (responseCallback != null) {String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));responseCallbacks.put(callbackStr, responseCallback);m.setCallbackId(callbackStr);}if (!TextUtils.isEmpty(handlerName)) {m.setHandlerName(handlerName);}queueMessage(m);}

doSend方法做了两件事:
一件事封装Message,封装的作用是为定规范setData是封装发送的数据,setHandlerName是设置调用的JavaScript函数setCallbackId是设置回调Id;
第二件事是生成唯一的CallbackId,使用Map<String, CallBackFunction>结构记录当有回调的时候触发对应的方法。

分发处理

封装好后,我们还会遇到一个问题,是直接发送调用消息,还是需要等待某种事件触发,很显而易见的事情就是,我们可能会遇到前端页面还没有加载完成的时候,这时候肯定无法调用,那么我们就需要一个队列在前端没有加载完成前进行收集需要发送的信息,然后等待前端页面加载完毕后统一进行处理,这就需要当我们发送消息前,进行分发,决定我们是需要马上发送消息,还是加入队列等待:

 private void queueMessage(Message m) {if (startupMessage != null) {startupMessage.add(m);} else {dispatchMessage(m);}}

在以上代码中就是根据startupMessage是否为NULL来判断前端页面是否加载完毕,如果为加载完毕startupMessage不为NULL,把消息体加入队列中缓冲起来,等待加载完毕后进行调用,如果页面已经是加载挖成的,就调用dispatchMessage发送调用消息。

JsBridge是如何定义方法开放API给JavaScript调用的

 public void registerHandler(String handlerName, BridgeHandler handler) {if (handler != null) {// 添加至 Map<String, BridgeHandler>messageHandlers.put(handlerName, handler);}}

BridgeWebView.registerHandler(String handlerName, BridgeHandler handler)用来定义开发API方法和实现,registerHandler方法只做了一件事,就是把handlerName和handler以映射的关系添加到messageHandlers键值对对象中进行管理,相对应的还有一个unregisterHandler操作。库里对开发的AP约束了必须实现BridgeHandler接口,这样抽象的好处是更方便管理,可以动态添加和删除API,而不对其他功能产生任何影响。需要更深入的了解Native和JavaScript是如何交互的,我们需要到下一章才进行说明。

JsBridge接收到JavaScript的消息后如何处理

目前有三种JavaScript调用Android Native的方法,分别是:

  • 拦截shouldOverrideUrlLoading方法
  • 拦截alert、prompt、confirm处理方法
  • addJavascriptInterface注册Android对象供H5调用

addJavascriptInterface在早期出现了一些漏洞兼容性不好,alert、prompt、confirm处理方法编码起来复杂度比较高,而且拦截了alert。。这些方法也不好处理,目前鉴于兼容性和复杂度,一般是采用拦截shouldOverrideUrlLoading方法+在Url自定义协议头进行 分发处理,我们把目光聚焦到BridgeWebViewClient.shouldOverrideUrlLoading中来:

 // 增加shouldOverrideUrlLoading在api》=24时@Overridepublic boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {String url = request.getUrl().toString();......// 如果是返回数据if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {webView.handlerReturnData(url);return true;// 如果不是返回信息数据} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {webView.flushMessageQueue();return true;} else {return this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, request);}}

当JavaScript请求更新WebView的url时,就会触发这个方法,JavaScript可以使用iframe技术进行隐匿调用来支持发送信息到Android 端,一般的自定义的协议为: 公司协议名://类型//调用对象名//想要处理的数据 列子如下:

yy://return/{function}/returncontent

这就是一些处理返回信息的协议URI。
回头看一下代码,其实就是对JavaScript发送过来的数据进行分流,当是处理返回响应信息的时候,调用BridgeWebView.handlerReturnData(),当不是响应信息,就分流到BridgeWebView.flushMessageQueue()方法。

分析了BridgeWebViewClient.shouldOverrideUrlLoading我们来先看看BridgeWebView.handlerReturnData():

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

这段代码从URL中解析出了functionName,就是我们属于调用的回调方法名,JavaScript只会发送一下格式的Url:

url = yy://return/_fetchQueue/[{"responseId":
"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}]

解析出来后,我们会发现,解析出来functionName是_fetchQueue,这使得我们不得不得把目光转移到BridgeWebView.flushMessageQueue()中,当我们把疑问的先藏在心里,把目光投到BridgeWebView.flushMessageQueue()我们就会发现,这些是对接收URL的统一处理总线:
BridgeWebView.flushMessageQueue()做了三件事情:

  • 给JavaScript发送刷新JavaScript消息队列的信息,把整个维护的messageHandlers发送给我们
  • 把发送过来的String转成Message队列
  • 遍历Message,判断是响应还是调用Android Native方法

第一步使用javascript:WebViewJavascriptBridge._fetchQueue();,通知JavaScript发送信息,拦截到的信息就类似上文讲到的

url = yy://return/_fetchQueue/[{"responseId":
"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}]

会转发到BridgeWebView.flushMessageQueue()中的回调方法CallBackFunction中。

第二步回调到CallBackFunction中的数据是String的,我们需要转义成我们能处理的Message列表,
Message封装有Message.toArrayList(data)可以转义。

第三步就是判断发送过来的信息体,是什么类型,主要分为两种类型,一种是调用JavaScript响应,一种是注册的开发Android Native 方法。

CallBackFunction function = responseCallbacks.get(responseId);
String responseData = m.getResponseData();
function.onCallBack(responseData);
responseCallbacks.remove(responseId);

如果是调用JavaScript响应responseId不为空,直接获取responseCallbacks的响应方法调用即可。

for (int i = 0; i < list.size(); i++) {Message m = list.get(i);String responseId = m.getResponseId();。。。。。CallBackFunction responseFunction = null;// if had callbackId 如果有回调Idfinal String callbackId = m.getCallbackId();if (!TextUtils.isEmpty(callbackId)) {responseFunction = new CallBackFunction() {@Overridepublic void onCallBack(String data) {Message responseMsg = new Message();responseMsg.setResponseId(callbackId);responseMsg.setResponseData(data);queueMessage(responseMsg);}};} else {responseFunction = new CallBackFunction() {@Overridepublic void onCallBack(String data) {// do nothing}};}// BridgeHandler执行BridgeHandler handler;if (!TextUtils.isEmpty(m.getHandlerName())) {handler = messageHandlers.get(m.getHandlerName());} else {handler = defaultHandler;}if (handler != null){handler.handler(m.getData(), responseFunction);}}}

如果responseId不为空,说明是注册的开发Android Native 方法,取出前面讲解的messageHandlers注册好的方法进行调用即可。

总结

Android Native 调用JavaScript的双向通信的流程是这样的:

  • 我给你发送信息,你根据信息提供的方法名调用你的方法,
  • 调用我完成后你把响应消息放在sendMessageQueue队列中,然后通知我有新的信息需要接收
  • 我再发送一次信息让你_fetchQueue把sendMessageQueue的消息发给我处理,处理回调到响应方法中
  • Native调用JavaScript双向通信结束

Android Native 注册本地方法给JavaScript调用的双向通信的流程是这样的:

  • 我注册一个以方法名标示的本地方法,映射到一个对象处理,然后等待JavaScript给我发送消息
  • 接收到JavaScript发送的请求,处理对应的对象方法,处理完后给JavaScript发送处理完毕的响应消息
  • 完成本次双向通信

JsBridge框架原理全解析相关推荐

  1. Go modules基础精进,六大核心概念全解析(下)

    Go 语言做开发时,路径是如何定义的?Go Mudules又为此带来了哪些改变?本文将会全面介绍Go Modules六大核心概念,包括了设计理念与兼容性原则等,掌握这些技术点对于管理和维护Go 模块有 ...

  2. 第四章:Spring项目文件上传两种方式(全解析)

    欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系.本系列教程希望您能站在上帝 ...

  3. 阿里秋招面试全解析(含内推岗)

    每个技术人都有个大厂梦,我觉得这很正常,并不是饭后的谈资而是每个技术人的追求.像阿里.腾讯.美团.字节跳动.京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻 ...

  4. Apache Web服务器访问控制机制全解析

    Apache Web服务器访问控制机制全解析 原文请见: http://netsecurity.51cto.com/art/201102/245666.htm Linux下的Aapche服务器提供了强 ...

  5. 6.15 Unity引擎渲染效率全解析

    UWA新晋主播赵福恺从Unity渲染模块中的各种渲染效果性能.PBR渲染性能以及阴影的渲染性能三个角度分别进行了详细的分析总结.为响应各大听众的需求,小编奉上完整视频回顾,同时也向看完直播才下班的五好 ...

  6. python读取txt文件写入-Python读写txt文本文件的操作方法全解析

    一.文件的打开和创建 >>> f = open('/tmp/test.txt') >>> f.read() 'hello python! hello world! ...

  7. jQuery Ajax 实例 全解析(转)

    jQuery Ajax 实例 全解析 jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯. 废话少说,直接进入正题,我 ...

  8. Fragment全解析系列

    文/YoKey(简书作者) 原文链接:http://www.jianshu.com/p/d9143a92ad94 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". F ...

  9. 万物之始正则表达式全解析三部曲(中篇)-正则表达式运算符优先级及匹配规则

    前言 各位小伙伴大家好,接下来几天时间,我会从多个角度对正则表达式进行系统阐述,让你了解正则表达式的前世今生. 该系列文章上篇 万物之始正则表达式全解析三部曲(上篇)-正则表达式基础知识及语法 以下是 ...

最新文章

  1. Python---根据字符串导入包(importlib)
  2. 简谈Redis的线程模型
  3. mysql limit耗时过长
  4. Unity3D研究院之Android同步方法读取streamingAssets
  5. mysql xa_Mysql对XA的支持
  6. 钢琴块2电脑版_云上钢琴学生端电脑版|云上钢琴学生端 V2.3.1 最新PC版 下载_当下软件园...
  7. Monkeyrunner脚本的录制与回放
  8. POJ-1163(DP,Water)
  9. CSS表格及表单美化
  10. 【编解码】从零开始写H264解码器(2) NALU
  11. matlab adaptfilt.rls,基于RLS算法的多麦克风降噪
  12. bboss es对比直接使用es客户端的优势
  13. Certificate Transparency
  14. 交互设计谁是最好用的原型绘制工具
  15. [HARDWARE] ddr、ddr2、ddr3的区别
  16. 特斯拉自动驾驶造假实锤:总监出马亲自作证,撞车片段被删,所有功能均为预编程...
  17. THREE.js模型贴图不显示
  18. 基于Matlab的二阶电路的动态电路分析!
  19. Android 安卓APK包解析名和安装到手机桌面的名称是两个名字,比如:解析出来的APP名:老娘舅(开店创业),安装到手机桌面的APP名:老娘舅
  20. redis简介-各种基础

热门文章

  1. SLR语法分析器-编译原理
  2. 那些成功学和鸡汤文没有告诉你的事
  3. Linux 日志查看方法(小记)
  4. IDataParameter[]怎么创建
  5. ABC类网络的范围,IP子网划分、构造超网
  6. ESP8266和DHT11通讯
  7. UNET详解和UNET++介绍(零基础)
  8. echarts html ajax,ECharts+Ajax动态加载数据实例(.NET)
  9. phoenix使用指南
  10. jquery 仙女散花动画特效