App实现JSBridge的最佳方案
前沿
写这篇文章的主要目的是对 App 的 JSBridge 做一个全面的介绍,同时根据不同的使用场景总结出一份 App 实现 JSBridge 的最佳方案。对于没有接触过 App 的同学能够对 JSBridge 有个大致的概念,对于做过 App 的 JSBridge 开发的同学也能有更系统的认识,也是自己对于相关知识点的归纳总结。
一、概念
什么是 JSBridge ?
JSBridge 的全称:JavaScript Bridge,中文名 JS桥
或 JS桥接器
。
JSBridge 是一种用于在 Android 和 iOS 应用与 H5 之间进行通信的技术。它允许应用开发者在原生代码中调用 JavaScript 函数,以及 在JavaScript 中调用原生代码函数。其通常用于移动应用开发中,可以使用 JSBridge 技术在原生应用中嵌入网页,并在网页与原生应用之间进行交互。
二、原理
JSBridge 通过在 WebView 中注册 JavaScript 函数来实现通信。WebView 是一种在应用中嵌入网页的组件,可以在应用中显示网页内容。JSBridge 通过在 WebView 中注册 JavaScript 函数,并在原生代码中调用这些函数来实现通信。
例如,下面是一个使用 JSBridge 实现通信的示例代码:
/* Android 端实现 */
// 在WebView中注册JavaScript函数
webView.loadUrl("javascript:function myFunction() { /* JavaScript code here */ }");
// 在原生代码中调用JavaScript函数
webView.loadUrl("javascript:myFunction()");
/* iOS 端实现 */
// 在WebView中注册JavaScript函数
[self.webView stringByEvaluatingJavaScriptFromString:@"function myFunction() { /* JavaScript code here */ }"];
// 在原生代码中调用JavaScript函数
[self.webView stringByEvaluatingJavaScriptFromString:@"myFunction()"];
上面的代码通过在 WebView 中注册 JavaScript 函数 myFunction
,并在原生代码中调用这个函数来实现通信。
在实际开发中,我们一般是创建一个 JSBridge 对象,然后通过 WebView 的 addJavascriptInterface
方法进行注册。
// WebView 的 addJavascriptInterface 方法源码
public void addJavascriptInterface(Object object, String name) {
checkThread();
if (object == null) {
throw new NullPointerException("Cannot add a null object");
}
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Invalid name");
}
mJavascriptInterfaces.put(name, object);
}
该方法首先检查当前线程是否是 UI 线程,以确保添加桥接对象的操作是在 UI 线程中进行的。接着,该方法会检查桥接对象和名称的有效性,确保它们都不为空。最后,该方法会把桥接对象与名称关联起来,并存储到 WebView 的 mJavascriptInterfaces
对象中。
当网页加载完成后,WebView 会把桥接对象的方法注入到网页中,使得网页能够调用这些方法。当网页中的 JavaScript 代码调用桥接对象的方法时,WebView 会把该方法调用映射到原生代码中,从而实现网页与原生应用之间的交互。
addJavascriptInterface
方法的主要作用是把桥接对象的方法注入到网页中,使得网页能够调用这些方法。它的具体实现方式可能会因平台而异,但是它的基本原理是一致的。
三、原生实现
以 H5 获取 App 的版本号为例。Android相关源码
要实现一个获取 App 版本号的 JSBridge,需要在 H5 中编写 JavaScript 代码,并在 Android 原生代码中实现对应的原生方法。
首先,需要在 H5 中编写 JavaScript 代码,用于调用 Android 的原生方法。例如,可以在 H5 中定义一个函数,用于调用 Android 的原生方法:
// assets/index.html
function getAppVersion() {
// 通过JSBridge调用Android的原生方法
JSBridge.getAppVersion(function(version) {
// 在这里处理获取到的Android版本号
});
}
然后,需要在 Android 的原生代码中实现对应的原生方法。例如,可以实现一个名为 getAppVersion
的方法,用于在 H5 中调用:
// com.fitem.webviewdemo.AppJSBridge
@JavascriptInterface
public String getAppVersion() {
// 获取App版本号
String version = BuildConfig.VERSION_NAME;
// 将App版本号返回给H5
return version;
}
最后通过 Webview 注入定义的 JavascriptInterface 方法的对象,在 H5 生成 window.jsBridge 对象进行调用。
// com.fitem.webviewdemo.MainActivity.kt
webView.addJavascriptInterface(jsBridge, "jsBridge")
iOS 的实现和 Android 类似:
- (void)getIOSVersion:(WVJBResponseCallback)callback {
// 获取App版本号
let version = Bundle.main.object(forInfoDictionaryKey:
"CFBundleShortVersionString") as! String
// 将App版本号返回给H5
callback(version);
}
// 在网页加载完成后设置JSBridge
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 设置JSBridge
[WebViewJavascriptBridge enableLogging];
self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[self.bridge setWebViewDelegate:self];
}
四、跨平台(Flutter)
1. JSBridge 实现
Flutter 实现 JSBridge 功能的插件有很多,但基本上大多数都是基于原生的 JSBridge 能力实现。这里主要介绍官方的 webview_flutter
插件。
webview_flutter
插件实现 App 与 H5 之前的通信分为:App 发送消息到 H5 和 H5 发送消息到 APP 两部分。
H5 发送消息到 APP。首先在 Flutter 应用中添加 WebView
组件,并设置 JavascriptChannel
:
WebView(
initialUrl: 'https://www.example.com',
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: {
// 设置JavascriptChannel
JavascriptChannel(
name: 'JSBridge',
onMessageReceived: (JavascriptMessage message) {
// 在这里处理来自H5的消息
},
),
},
),
在H5中,可以通过 JSBridge
对象来调用原生方法:
// 通过JSBridge调用原生方法
window.jsBridge.postMessage('Hello, world!');
App 发送消息到 H5。 在 Flutter 中,通过 WebViewController 的 runJavascrip
调用 H5 中 window 对象的方法
controller.runJavascript("receiveMessage(${json.encode(res)})")
在 H5 中,可以通过 onmessage
事件来接收来自原生的消息:
// 接收来自原生的消息
window.receiveMessage = function receiveMessage(message) {
console.log(message);
};
2. 局限性
webview_flutter
最大的局限在于 App 端与 H5 端之间的通信只支持单向通信,无法通过一次调用直接获取另一端的返回值。
五、App 实现 JSBridge 的最佳方案
1. 实现目标
H5 兼容原生老版本 JSBridge。
支持两端双向通信。针对
webview_flutter
的单向通信的局限性进行改造优化,使其能支持返回值的回调。
2. NativeBridge 插件开发
NativeBridge 本质上是对 webview_flutter 的单向通信能力进行扩展
和封装
。
NativeBridge 插件的使用和实现原理,请阅读之前的文章《Flutter插件之NativeBridge》和《NativeBridge实现原理解析》。
3. 实现效果
- H5 支持原生老版本 JSBridge 兼容。
// 获取app版本号 返回String
async getVersionCode() {
// 是否是新的JSBridge
if (this.isNewJSBridge()) {
return await window.jsBridgeHelper.sendMessage('getVersionCode', null)
} else {
return window.iLotJsBridge.getVersionCode()
}
}
- 支持两端双向通信。
// H5 获取 App 的值
const versionNo = await jsBridge.getVersionCode()
// App 获取 H5 的值
var isHome = await NativeBridgeHelper.sendMessage("isHome", null, webViewController).future ?? false;
- 新增超时连接机制
就像网络请求一样,我们不能让代码执行一直阻塞在获取返回值的位置上。因为单向发送消息是不可靠的,可能存在消息丢失,或者另一端不响应消息的情况。因此我们需要类似网络请求一样,增加超时回调机制。
// 增加回调异常容错机制,避免消息丢失导致一直阻塞
Future.delayed(const Duration(milliseconds: 100), (){
var completer = _popCallback(callbackId);
completer?.complete(Future.value(null));
});
总结
我们首先介绍了 JSBridge 的概念和原理,然后通过在 Android 、iOS 和 Flutter 中实现 JSBridge 来理解原生和 Flutter 之前的差异,最后总结了在 App 中实现 JSBridge 的最佳方案,方案包括支持原生和 Flutter 的兼容,并优化 webview_flutter
只支持单向通信的局限性和增加超时回调机制。
链接:https://juejin.cn/post/7177407635317063735
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。