说说JSBridge的原理?

简介: 本文首发于微信公众号“前端徐徐”,作者徐徐。文章介绍了 JSBridge 的背景、核心原理及其在 Android 和 iOS 平台上的实现方式,探讨了其应用场景、安全性和性能考量,并提供了优化建议。JSBridge 作为混合开发的关键技术,允许网页应用调用原生功能,结合了两者的优点。

本文首发微信公众号:前端徐徐。

大家好,我是徐徐。今天和大家分享一下 JSBridge 的相关知识,了解一下 JSBridge 的原理,帮助大家在日常工作和面试中都可以轻松应对相关问题。

产生背景

在移动设备上,原生应用(Native App)与网页应用(Web App)各有优劣。原生应用访问设备功能强大,运行流畅,但开发成本高,更新维护复杂;网页应用跨平台性好,更新快,但访问设备能力有限,性能相对较弱。随着 HTML5 的兴起,Web 能力不断增强,但依然无法完全替代原生能力。因此,JSBridge 作为一种混合开发模式(Hybrid)的关键技术出现,它允许网页应用调用原生能力,结合了两者的优点。

核心原理

JSBridge 的核心原理是在 WebView 中构建一个通信桥梁,使得 JavaScript 和原生代码可以双向通信。

1. 消息通道的建立

  • Android

方法注入: Android 平台可以使用 WebView.addJavascriptInterface() 方法向 WebView 中注入 Java 对象,JavaScript 可以直接通过这个对象调用其公开的方法。这些方法必须使用 @JavascriptInterface 注解来标记,以便可以被 WebView 识别。

public class WebAppInterface {
    Context mContext;
    // Instantiate the interface and set the context
    WebAppInterface(Context c) {mContext = c;}
    // Show a toast from the web page
    @JavascriptInterface
    public void showToast(String toast) {Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();}
}
WebView myWebView = findViewById(R.id.webview);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
myWebView.loadUrl("file:///android_asset/webview.html");

URL 拦截: 另一种方式是通过拦截 URL 请求。JavaScript 可以通过修改 window.location 或创建一个 iframe 的 src 属性为特定 scheme 的 URL(如 myapp://),Android 原生端通过 WebViewClient.shouldOverrideUrlLoading()或 WebChromeClient.onJsPrompt() 等方法拦截这些 URL,然后解析 URL 中的信息来执行对应的原生操作。

myWebView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {if (url.startsWith("myapp://")) {
            // Handle the custom URL scheme
            handleCustomUrl(url);
            return true;
        }
        return false;
    }
});
private void handleCustomUrl(String url) {// Parse the URL and execute the corresponding native action}
  • iOS

消息处理器: 在 iOS 的 WKWebView 中,可以通过 WKScriptMessageHandler 添加消息处理器,这允许 JavaScript 通过 window.webkit.messageHandlers>.postMessage() 发送消息给原生端。

import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    override func viewDidLoad() {super.viewDidLoad()
        let contentController = WKUserContentController()contentController.add(self, name: "callbackHandler")
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        webView = WKWebView(frame: self.view.bounds, configuration: config)
        self.view.addSubview(webView)
        if let url = Bundle.main.url(forResource: "webview", withExtension: "html") {webView.loadFileURL(url, allowingReadAccessTo: url)
        }
    }
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "callbackHandler" {
            if let messageBody = message.body as? String {print("Received message: \(messageBody)")}}
    }
}

URL 拦截 :类似 Android 的 URL 拦截方法,iOS 也可以通过拦截 URL scheme 来实现。

import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!
    override func viewDidLoad() {super.viewDidLoad()
        webView = WKWebView(frame: self.view.bounds)
        webView.navigationDelegate = self
        self.view.addSubview(webView)
        if let url = Bundle.main.url(forResource: "webview", withExtension: "html") {webView.loadFileURL(url, allowingReadAccessTo: url)
        }
    }
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let url = navigationAction.request.url, url.scheme == "myapp" {
            // Handle the custom URL scheme
            handleCustomUrl(url)
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }
    private func handleCustomUrl(_ url: URL) {// Parse the URL and execute the corresponding native action}
}

2. JavaScript 调用原生方法

  • 方法调用: JavaScript 通过调用事先注入的对象的方法或者通过发送消息的方式来请求原生功能的执行。
  • 参数传递:调用时可以传递参数,这些参数通常需要被序列化(比如转换为 JSON 字符串),以便原生代码可以解析并获取所需信息。

Android 方法注入的形式前端调用

<!DOCTYPE html>
<html>
  <body>
    <button onclick="showAndroidToast('Hello Android!')">Show Toast</button>
    <script type="text/javascript">
      function showAndroidToast(toast) {Android.showToast(toast);
      }
    </script>
  </body>
</html>

Android URL 拦截形式调用

<!DOCTYPE html>
<html>
<body>
    <button onclick="navigateTo('myapp://showtoast/Hello%20Android')">Show Toast</button>
    <script type="text/javascript">
        function navigateTo(url) {window.location.href = url;}
    </script>
</body>
</html>

IOS 方法注入的形式前端调用

<!DOCTYPE html>
<html>
<body>
    <button onclick="sendMessageToNative('Hello iOS!')">Send Message</button>
    <script type="text/javascript">
        function sendMessageToNative(message) {window.webkit.messageHandlers.callbackHandler.postMessage(message);
        }
    </script>
</body>
</html>

IOS URL 拦截形式调用

<!DOCTYPE html>
<html>
<body>
    <button onclick="navigateTo('myapp://showtoast/Hello%20iOS')">Show Toast</button>
    <script type="text/javascript">
        function navigateTo(url) {window.location.href = url;}
    </script>
</body>
</html>

3. 原生代码执行操作

  • 原生代码解析从 JavaScript 传递过来的命令和参数,然后执行相应的原生操作,如访问硬件、修改设置、存储数据等。

4. 结果回传给 JavaScript

  • 回调函数:原生操作完成后,通常需要将结果回传给 JavaScript。这可以通过回调函数实现,原生代码可以调用 WebView.evaluateJavascript()(Android)或 WKWebView.evaluateJavaScript()(iOS)来执行 JavaScript 代码,从而触发 JavaScript 中的回调函数。
  • URL 重定向: 另一种方法是原生代码构造一个结果数据的 URL,然后加载这个 URL,JavaScript 端通过拦截 URL 来获取数据。

应用场景举例

JSBridge 的使用场景非常广泛,例如:

  • 社交分享:用户在 WebView 中点击分享,JSBridge 调用原生的分享对话框,分享到不同的社交网络。
  • 设备功能:通过 JSBridge 访问用户的相册或相机来上传或处理图片。
  • 支付集成:在 Web 页面中集成原生的支付 SDK,提供更加流畅的支付体验。
  • 数据同步:JSBridge 可以用来同步网页数据和原生应用的数据,保持用户数据的一致性。
  • 性能优化:对于复杂的数据处理或动画,可以通过 JSBridge 调用更高效的原生代码实现。

安全性考量

在使用 JSBridge 时,安全是一个重要的考虑因素。因为不当的使用可能会导致安全漏洞,比如:

  • 注入漏洞:如果 WebView 允许执行任意的 JavaScript 代码,攻击者可能会注入恶意代码,这可能导致跨站脚本攻击(XSS)等安全问题。
  • 接口暴露:通过 JSBridge 暴露给 JavaScript 的原生方法如果没有适当的权限控制,可能被恶意网页滥用,导致数据泄露或者不安全的操作。
  • 输入验证:原生方法在处理从 JavaScript 接收到的数据时,如果没有进行严格的输入验证和清理,可能会被注入恶意数据。

为了提高安全性,可以采取以下措施:

  • 接口白名单:确保只有预定义的、受信任的 Web 内容可以调用 JSBridge 接口。
  • 方法限制:只暴露必要的方法给 JavaScript,避免提供太宽泛的 API 接口。
  • 参数校验:在原生端对所有从 JavaScript 传入的数据进行严格的验证和清洗,以避免注入攻击。
  • 使用 HTTPS:确保 WebView 中加载的内容是通过 HTTPS 传输的,减少中间人攻击的风险。
  • 版本控制:对于 JSBridge 的接口,实现版本控制,确保前后端兼容性和安全性。

性能考量

JSBridge 的使用也会对应用的性能产生影响,尤其是在以下方面:

  • 多次交互:频繁的 JavaScript 与原生代码之间的通信可能会导致性能瓶颈。
  • 线程管理:JavaScript 通常在 UI 线程执行,而原生操作可能在不同的线程中执行,不当的线程管理可能会造成 UI 卡顿。
  • 序列化开销:在 JSBridge 通信过程中,需要对传递的数据进行序列化和反序列化,这个过程可能会影响性能。

为了优化性能,可以采取以下措施:

  • 减少通信:尽量减少 JavaScript 和原生代码之间的通信次数,可以通过合并请求或者批量处理来实现。
  • 异步执行:对于耗时的原生操作,采用异步执行的方式,避免阻塞 UI 线程。
  • 优化数据传输:简化传递给原生代码的数据结构,减少不必要的数据序列化开销。

结语

JSBridge 技术为混合应用开发提供了一种高效的解决方案,弥补了网页技术无法直接调用原生功能的不足。通过建立 JavaScrip t 与原生代码之间的桥梁,开发者可以在 WebView 中实现丰富的功能交互,提升应用的用户体验。掌握 JSBridge 的工作原理和实现方法,对于开发高性能、可维护的混合应用至关重要。随着技术的发展,JSBridge 将继续演进,为移动应用开发提供更加便捷和强大的支持。

相关文章
|
6月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
72 0
|
4月前
|
前端开发 JavaScript
前端 JS 经典:封装全屏功能
前端 JS 经典:封装全屏功能
34 0
|
6月前
|
移动开发 JavaScript iOS开发
WKWebView和js互调方法的实现
WKWebView和js互调方法的实现
41 0
|
前端开发
【面试题】:前端怎么实现组件的封装和上传
前端如何实现组件的封装以及上传
208 0
|
前端开发 数据安全/隐私保护
前端开发常用的方法封装(二)
将阿拉伯数字翻译成中文的大写数字、将数字转换为大写金额、 判断一个元素是否在数组中和数组排序等......
83 0
|
前端开发 算法
前端开发常用的方法封装
截取地址栏里携带的参数、时间转换工具、字符串的截取等......
68 0
|
SQL 程序员 数据库
Flutter(二十九)——封装SQLHelpers
居天下之广居,立天下之正位,行天下之大道;得志,与民由之;不得志,独行其道。富贵不能淫,贫贱不能移,威武不能屈,此之谓大丈夫。
369 2
Flutter(二十九)——封装SQLHelpers
|
消息中间件 Web App开发 存储
浏览器原理 15 # WebAPI:setTimeout 是如何实现的?
浏览器原理 15 # WebAPI:setTimeout 是如何实现的?
150 0
浏览器原理 15 # WebAPI:setTimeout 是如何实现的?
|
Java fastjson Maven
Android组件化开发(二)--网络请求组件封装
前面一篇文章我们讲解了`maven私服`的搭建,maven私服在`组件化框架`中有一个很重要的地位就是可以将我们的`lib`库放到局域网中,供公司其他开发者使用,实现类库的分享。 下面是这个系列准备实现的一个`组件化实战项目框架`:
Android组件化开发(三)--图片加载组件封装
今天我们来封装一个`图片加载库`:`lib_image_loader`