一篇写给 Android 刚入门小伙伴的 JSBridge 文章
复杂的程序都需要分层表达
正文
这篇文章写给那些经常在 JAVA 层 “扑腾”,几乎没有接触过 H5 相关技术的小伙伴。我会在文章中解释 JSBridge 是什么?JSBridge 的技术定位是什么?理清 JSBridge 出现是要解决什么问题,最后讲一下对于实现 JSBridge 的一些技术方案思考。
整篇文章,不贴过多的代码,只讲清原理和设计思路,因为只有理解了思路,才能更加灵活的做方案取舍。
注:这里的 H5 泛指前端,不仅仅是 HTML5 的含义。
背景
在一切开始之前,我们需要先搞明白 JSBridge 在客户端技术栈中所处的位置,有助于对 JSBrige 所能发挥的作用做一个整体的认知,也能从一个客户端技术演进的角度来理解 JSBridge 的重要性。
JSBridge 的思想其实对一直做 Native 的客户端开发者来说,是很陌生的,这是由其客户端的业务属性所决定的,但是只要涉及到一些运营属性,那么接触 JSBridge 就是不可以避免的。所以在这样的业务属性下,客户端的技术演进,肯定都是要经历以下的过程
Native -> Native + H5 -> Native + JSBridge -> Native + RN(Flutter)
各个阶段如下:
- Native:在客户端初期,人力不足,只使用 Native 的能力实现一些重要的需求
- Native + H5:在后续的发展过程中,会使用 H5 简单的处理一些静态的页面,无论是从排版来说,还是从开发成本来说,一些静态页面,例如隐私声明,协议约定等大段落文本,H5 在实时更新和发布成本来说都优于 Native
- Native + JSBridge:再到后边,整个团队对业务方向的摸索过程中,H5 因为高的更新的频次和可快速迭代发版的特性,会成为整个阶段的技术首选,客户端的角色就是提供对应的基本能力,这个时候就需要有一个成本不高,但是足够用的方案,Native + JSBridge ,易上手,方案简单,且维护成本较低。
- Native + RN:但是 JSBridge 的方案太过简单,且性能上不去,而继续往后的客户端发展过程中,必然会对性能,视觉,交互有着更大的要求,原生的 WebView 的方案就会被放弃,那么 Native + RN 的方案就会脱颖而出,这套方案可以支撑更大的业务要求,对性能也提升很大,但是对技术要求和学习成本要求也更多,因为有新的语言要学习,新的框架要接入。
- Native + Flutter:如果往更大的开放平台走,类似于支付宝和微信的平台式APP,那么使用 Flutter 这样自带渲染引擎的方案,或者自己开发定制一套方案是更好的选择
注:在方案的选取过程中,其实对于跨平台性也是一个很重要的因素,因为不涉及到方案比较,这里就不过多提及
理解了 JSBridge 在客户端演进过程中的作用后,接下来我们就来解开 JSBridge 的神秘面纱。
JSBridge
在了解了 JSBridge 的前后背景,我们从在这节内容开始直面 JSBridge。
这里我们先给 JSBridge 下一个简单的定义,从抽象的层面或者更大的角度来理解 JSBridge 的整个定位,至于对 JSBridge 直观的,可以触摸的理解,你在后边的实现方案中会看到。
JSBridge,简单来说,是一种协议,是一种思想,同时也是一类问题的一个通用解决方案。
JSBridge 是和 DHCP 协议,P2P 协议同样的解决问题的方案。
DHCP 协议用来解决 IP 静态分配导致的效率问题
P2P 协议用来解决单一服务器难以承受巨大的带宽压力问题
而 JSBridge 协议解决的是 H5 端和 Native 端的通信问题
但是给出 JSBridge 是一种协议这个结论来说未免太过简单抽象了一点,说它可以解决 H5 端和 Native 端通信的问题,许多人估计也是一头雾水,别着急,我们一步步来,先来看一下如果不使用 JSBridge,使用原生提供的解决方案是怎么样的。
原生解决方案
Google 在 API level 1 就已经提供了 addJavascriptInterface(Object object, String name) 来作为 JS 访问 Native 代码的入口,
原理是:将 Java 对象注入成为 JS 中 window 对象的一个属性,我们都知道 window 对象是浏览器中的顶级对象,名下的属性和方法可以在全局直接引用,所以 Java 对象的方法就被映射到了 window 的一个属性下了。
相应的,在 API level 1 也提供了 loadUrl(String url) 来加载 Url 或者 JS 代码
但是,这两个接口都有各自的问题
addJavascriptInterface(Object object, String name) 在 Android 4.2 之前有安全问题,Google 在 4.2 之后将其限制为加了 @JavascriptInterface 的注解才可以被访问到。
而 loadUrl(String url) 加载 JS,是没有返回值的,对于想要接收 JS 回调的情况来说,只能通过其他方式解决,
在 Android4.4 之后,Google 提供了 evaluateJavascript(String script, ValueCallback<String> resultCallback) 方法来加载 JS 方法并且可以接到回调。
但是不管是后续版本新增的接口也好,原有的接口也好,原生提供的方式都有一些很明显的弊端:
- 高低版本接口不一致,有兼容性要处理
- 低版本有安全漏洞,方案无法统一
- JS 调用 Native 没有回调,接口也不友好
- 无法实现自由的双端通信
因为以上的弊端,那么急需找出一种可以兼容高低版本,又没有安全漏洞,且实现统一的方案,这就是 JSBridge。
如何实现 JSBridge?
终于来到最重要的章节了
双端通信(更像是 C/S 端),其实就是
告诉对方我想要你做什么
想要对方做什么,可以换句话说,想要对方的什么资源,既然请求需要对方的每个资源,那么就需要标明要哪个资源,这种标定对方资源的请求听起来是不是有点耳熟,没错,就是 URI (Uniform Resource Identifier),唯一资源定位符
A Uniform Resource Identifier (URI) is a string of characters that unambiguously identifies a particular resource. To guarantee uniformity, all URIs follow a predefined set of syntax rules, but also maintain extensibility through a separately defined hierarchical naming scheme (e.g. http://).
– from Wikipedia
结合到我们的实际场景中来看,JS 端通过 URI 的消息格式从 Native 端获取某些资源,然后返回给 JS 端,这就是 JSBridge 要做的全部的事情。
以上内容,我们再细分为
- 定义 URI 格式(解决 JS 向 Native 请求数据的问题)
- 定义回调格式 (解决 Native 向 JS 回传数据的问题)
定义 URI 格式
翻译成人话,就是
约定 scheme 头部,标明协议类型,比如可以是 bridge
约定调用的类名,例如 JSMethod
约定调用的本次请求的唯一标识,例如 123(当前生命周期唯一标识即可)
约定调用的方法名,例如 getLocationInfo
约定调用的参数,例如 realtime=ture
将以上的内容使用 URI 协议拼接起来就是bridge://JSMethod:123/getLocationInfo?realtime=true
定义回调
Native 在收到 JS 的请求之后,就需要对这次请求作出响应,提供 JS 端想要的数据,那么对 JS 端来说,我发起的 N 多请求,如何标明 Native 的返回的数据就是对应我刚刚发出的请求呢,我们在之前的 URI 中,针对本次的请求已经设计了一个唯一标识符,那么让 Native 回调时带上这个唯一标识符,JS 就可以和之前的请求一一对应起来了,至于如何回调的问题,我们可以约定一个回调函数 onResponse(int identiy, String jsonData) 将数据放入其中返回既可。
备注:
有些人可能直接想到的是 Java 中的 Callback 回调,但是有一点需要明确,Java 中可以使用回调是因为通信的双方都是 jvm 虚拟机可以识别的 object 对象,但是对于 JS 和 Java 这两个通信者来说,就需要找一个双方都可以识别的类型来完成回调类型的定义,对于 Android 的 WebView 内核来说,这个类型就是基本类型 String 字符串。
找到了方法标识想要的资源,那么就剩下解决如何告诉的问题了,在 HTTP 协议中,告知对方使用的是 IP 协议负责定位,TCP/UDP 协议负责传输,但是针对 Android 端与 H5 端的通信,我们互相通信的双方其实就是当前窗口生命周期内的二者,没有第三方,那么我们要做的就是只是找到可以让双方通信的”入口“即可。
通信入口
我们先来找 Native 端向 JS 端发送消息的办法,目前通用的解决方案有两种:
loadUrl(String url)evaluateJavascript(String script, ValueCallback<String> resultCallback)
这两个方案的利弊和使用限制,我们在上边已经解释过了,这里就不再多说。
接下来就是 JS 端向 Native 端发送消息的办法,目前通用的解决方案有四种:
1.可以在 shouldOverrideUrl() 中拦截Url,在 url 中带参数过来,参数预先和 Native 协商好
这是 WebViewClient 的一个可以复写的方法,目的是在当前 WebView 加载 Url 时给应用程序一个控制的机会,控制什么,控制要不要加载这个 url,一般来说页面内部的重定向都会走这个函数,我们可以决定要不要拦截一些跳转,当然,我们也可以和 H5 约定在链接中加入参数来调用 Native 的某些资源
2.也可以在 onJsPrompt(),onJsConfirm(),onJsAlert(),中传入参数过来,同样和 Native 协商好就可以
这三个方法分别对应 JS 中的 window.prompt(),window.confirm(),window.alert(),同样,在 WebViewClient 中可以复写这三个方法接收消息
3.还有第三种方案,使用 onConsoleMessage(ConsoleMessage consoleMessage) 方案
这个方法对应 js 中的 console.log(),同样,可以在 WebViewClient 中复写接收消息
4.原生的注入方法,使用 addJavascriptInterface(Object object, String name),这个方案的利弊也已经在上边解释过了
总结
在定义好协议,找到通信入口之后,我们就可以在 JS 中使用定义好的协议请求 Native 的代码了,然后在自定义的回调中获取 Native 回传的内容,这部分内容可以参考 github 中的实现,原理都是相同的。
参考:稍后补上,尽请期待,( https://github.com/0xforee )
结尾
这一期简单介绍了 JSBridge 的一些实现方案和思路,但是如果要写一个合格稳定可用的 JSBridge 除了要定好协议,基本实现出来,还需要一些额外的调优工作,我们在接下来的一期中继续探索这方面的内容。
一篇写给 Android 刚入门小伙伴的 JSBridge 文章
欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考


