JSBridge:混合开发中的双向通信[Android、iOS&JavaScript]

简介: WebView 是移动端中的一个控件,它为 JS 运行提供了一个沙箱环境。WebView 能够加载指定的 url,拦截页面发出的各种请求等各种页面控制功能,JSB 的实现就依赖于 WebView 暴露的各种接口。由于历史原因,IOS以8为分界,Android以4.4为分界,分为高低两个版本。而它们的区别在于 —— 回调。高版本可以通过执行回调拿到 JS 执行完毕的返回值,然后准确进行下一步操作。而低版本无法执行回调!Hybrid App 的核心。

什么是WebView

WebView 是移动端中的一个控件,它为 JS 运行提供了一个沙箱环境。WebView 能够加载指定的 url,拦截页面发出的各种请求等各种页面控制功能,JSB 的实现就依赖于 WebView 暴露的各种接口。

由于历史原因,IOS以8为分界,Android以4.4为分界,分为高低两个版本。而它们的区别在于 —— 回调。高版本可以通过执行回调拿到 JS 执行完毕的返回值,然后准确进行下一步操作。而低版本无法执行回调!

什么是 JSB

Hybrid App 的核心。我们开发的 h5 页面运行在端上的 WebView 容器之中,很多业务场景下 h5 需要依赖端上提供的信息/能力,这时我们需要一个可以连接原生运行环境和 JS 运行环境的桥梁 。 这个桥梁就是 JSB,JSB 让 Web 端和 Native 端得以实现双向通信。

JSB的目的

JSB 的目的就是“让 Native 可以调用 web 端的 JavaScript 代码,让 web 端可以调用 Native 的原生代码”。

native 调用 web 端代码

无论 Android 还是 iOS,在调用 web 端代码的时候,必须是调用的“挂载在window上的函数”。拿一个例子来说:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="调用JS方法"
    android:onClick="onJSFunction1"/>
public void onJSFunction1 (View v) {
    mWebView.evaluateJavascript("javascript:onFunction('android调用JS方法')", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String s) {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setMessage(s);
            builder.setNegativeButton("确定", null);
            builder.create().show();
        }
    });
}

evaluateJavascript 就是调用 JS 中的方法:onFunction1,并传入参数,在回调中进行处理 —— 这个回调至关重要。

在 js 中:

window.onFunction = function(str) {
    alert(str);
    return "这是 onFunction 方法的返回值";
}

android端调用web端代码

上面的 java 代码中,mWebView 是实例化的 腾讯X5内核组件。在它的配置中首先注意到这样一行代码:

/**
 * 允许加载的网页执行 JavaScript 方法
 */
webSettings.setJavaScriptEnabled(true);

允许网页执行 JS 方法。这个非常重要:它是 Native 是否能够向 web 通信的关键!这一点下面我们会提到。

在进行完一些基础配置后,我们会构建一个 JSBridge 对象:

addJavascriptInterface(
        new MyJaveScriptInterface(mContext, this),
        "AndroidJSBridge");

这个对象就叫做“AndroidJSBridge”,在这个处理中,这个 JSB 对象会被挂载到网页的 window 对象下,从而作为原生端和 web 端通信的桥梁。

web 调用 native 端代码

上面说了在初始化后 window 对象下会有一个 AndroidJSBridge 对象,在网页中也可以直接通过 window.AndroidJSBridge 拿到这个对象,从而调用 Android 端提供给网页端的方法(这里以Android为例):
现在 Android 提供了这样一个方法:

@JavascriptInterface
public void androidTestFunction1 (String str) {
    AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
    builder.setMessage(str);
    builder.setNegativeButton("确定", null);
    builder.create().show();
}

目的是在 APP 弹出一个 Alert 对话框,对话框中的内容为 JavaScript 传入的字符串。

注意:对于 Android 来说,当 js 调用方法并传参时,Android方法接收的参数只能是“基本数据类型”。对于“复杂数据类型”,只能通过 JSON.stringify(Object) 转化成 string 类型。
而 iOS 则不受此限制。

在 web 项目中,拿原生项目来说,我们直接这么写即可:

<input type="button" value="调用androidTestFunction1" @click="toAndroidFunction1()" />

<script>
function toAndroidFunction1() {
    window.AndroidJSBridge.androidTestFunction1('调用 android 下的 function1 方法')
}
</script>

web 端调用 android 方法
当然,在 android 方法中,我们也可以直接 return 数据,到了 web 端就是“回调”了。

function toAndroidFunction2() {
    let result = window.AndroidJSBridge.androidTestFunction1('androidTestFunction2方法的返回值');
    alert(result);
}

web 端调用有返回值的 android方法
诶,为什么调用的 alert 也是弹出的 android 原生弹出框?
因为在初始化的时候,原生对 alert 进行了一次“劫持”:

/**
 * 监听网页中的url加载事件
 */
private void initChromeClient () {
    setWebChromeClient(new WebChromeClient(){

        /**
         * alert()
         * 监听alert弹出框,使用原生弹框代替alert。
         */
        @Override
        public boolean onJsAlert(WebView webView, String s, String s1, JsResult jsResult) {

                AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
                builder.setMessage(s1);
                builder.setNegativeButton("确定", null);
                builder.create().show();
            jsResult.confirm();

            return true;
        }
    });
}

双向通信

Native向web发送消息

其实原理上面已经说了:

Native 向 Web 发送消息基本原理上是在 WebView 容器中动态地执行一段 JS 脚本,通常情况下是调用一个挂载在全局上下文的方法。
具体来说 —— Native 端可以直接调用挂载在 window 上的全局方法并传入相应的函数执行参数,并且在函数执行结束后 Native 端可以直接拿到执行成功的返回值。

场景:页面上是一个web page,在其上有一个原生控件 Button 和 Input。在输入框中输入一段代码更改 web 页面上某个标签的 innerHTML

<EditText
    android:id="@+id/editText_name"
    android:layout_width="edit_parent"
    android:layout_height="edit_content"
    android:hint="请输入你想要执行的js代码" />

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Native向Web发送消息" />
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import cn.sunday.hybridappdemo.views.X5WebView;
 
public class MainActivity extends AppCompatActivity {
    private X5WebView mWebView;
    
    private Button button;
    private EditText editText_name;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.Button_quedin);
        this.editText_name = (EditText) findViewById(R.id.editText_name);
        
        button.setOnClickListener(new View.OnClickListener() {//注册监听
            @Override //监听点击事件
            public void onClick(View v) {
                String name = editText_name.getText().toString();
                mWebView.evaluateJavascript("javascript:onShowPageNative(" + name + ")");
            }
        })
    })
}
window.onShowPageNative = function(str) {
    new Function(str);
}

将文本框输入的字符视为 JS 字符串并调用相关 API 直接执行

你是否想到了著名的用于解决跨域问题的jsonp?其实很多地方都使用了“将代码作为字符串传递再执行”的原理。最出名的就是微信小程序的(双线程)混合架构模型中view层向逻辑层通信。

Web 向 Native 发送消息

Web 向 Native 发送消息本质上就是某段 JS 代码的执行端上是可感知的,目前业界主流的实现方案有两种,分别是拦截式和注入式。

拦截式

和浏览器类似 WebView 中发出的所有请求都是可以被 Native 容器感知到的,因此拦截式具体指的是 Native 拦截 Web 发出的 URL 请求,双方在此之前约定一个 JSB 请求格式,如果该请求是 JSB 则进行相应的处理,若不是则直接转发。

Native 拦截请求的钩子方法:

  • Android:shouldOverrideUrlLoading
  • IOS:

    • 8+:decidePolicyForNavigationAction
    • 8-:shouldStartLoadWithRequest

拦截式的流程存在几个问题:

通过何种方式发出请求?
Web 端发出请求的方式非常多样,例如 <a>iframe.srclocation.href、ajax 等,但 <a> 需要用户手动触发,location.href 可能会导致页面跳转,安卓端拦截 ajax 的能力有所欠缺,因此绝大多数拦截式实现方案均采用 iframe 来发送请求。

如何规定 JSB 的请求格式?
一个标准的 URL 由 <scheme>://<host>:<port><path> 组成,相信大家都有过从微信或手机浏览器点击某个链接意外跳转到其他 App 的经历,如果有仔细留意过这些链接的 URL 你会发现目前主流 App 都有其专属的一个 scheme来作为该应用的标识,例如微信的 URL scheme 就是 weixin://。JSB 的实现借鉴这一思路,定制业务自身专属的一个 URL scheme 来作为 JSB 请求的标识,例如字节内部的 bytedance://

// Web 通过动态创建 iframe,将 src 设置为符合双端规范的 url scheme
const CUSTOM_PROTOCOL_SCHEME = 'vdian'

function web2Native(event) {    
    const messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + event;
    document.documentElement.appendChild(messagingIframe);

    setTimeout(() => {
        document.documentElement.removeChild(messagingIframe);
    }, 200)
}

拦截式在双端都具有非常好的向下兼容性,曾经是最主流的 JSB 实现方案,但目前在高版本的系统中已经逐渐被淘汰,理由是它有如下几个劣势:

  • 连续发送时可能会造成消息丢失(可以使用消息队列解决该问题)
  • URL 字符串长度有限制
  • 性能一般,URL request 创建请求有一定的耗时(Android 端 200-400ms)

注入式

注入式的原理是通过 WebView 提供的接口向 JS 全局上下文对象(window)中注入对象或者方法,当 JS 调用时,可直接执行相应的 Native 代码逻辑,从而达到 Web 调用 Native 的目的。
—— 也就是上面说的“web 端调用 Native 端代码”。

这种方法简单而直观,并且不存在参数长度限制和性能瓶颈等问题,目前主流的 JSB SDK 都将注入式方案作为优先使用的对象。

相关文章
|
6天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
6天前
|
安全 数据安全/隐私保护 Android开发
探索Android与iOS的隐私保护策略
在数字时代,智能手机已成为我们生活中不可或缺的一部分,而随之而来的则是对个人隐私和数据安全的日益关注。本文将深入探讨Android与iOS两大操作系统在隐私保护方面的策略和实践,分析它们如何应对日益严峻的隐私挑战,以及用户应如何保护自己的数据安全。通过对比分析,我们将揭示两大系统在隐私保护方面的优势和不足,为用户提供有价值的见解和建议。
|
6天前
|
Android开发 Swift iOS开发
深入探索iOS与Android操作系统的架构差异及其对应用开发的影响
在当今数字化时代,移动设备已经成为我们日常生活和工作不可或缺的一部分。其中,iOS和Android作为全球最流行的两大移动操作系统,各自拥有独特的系统架构和设计理念。本文将深入探讨iOS与Android的系统架构差异,并分析这些差异如何影响应用开发者的开发策略和用户体验设计。通过对两者的比较,我们可以更好地理解它们各自的优势和局限性,从而为开发者提供有价值的见解,帮助他们在这两个平台上开发出更高效、更符合用户需求的应用。
|
9天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
28 9
|
7天前
|
前端开发 Android开发 开发者
探索Android与iOS的跨平台开发策略
在当今多元化的移动设备市场中,开发者面临着为不同操作系统设计应用的挑战。本文深入探讨了Android和iOS两大主流平台的跨平台开发策略。我们将分析使用Flutter、React Native等框架进行跨平台开发的优劣,并讨论如何克服各平台间的差异性,以实现高效、一致的用户体验。此外,文章还将提供一些实用的技巧和最佳实践,帮助开发者优化跨平台应用的性能和兼容性。
25 4
|
8天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
8天前
|
存储 安全 数据安全/隐私保护
深入探索Android与iOS的隐私保护机制:一场没有硝烟的较量####
本文深度剖析了Android与iOS两大移动操作系统在用户隐私保护方面的策略与实践,揭示两者在设计理念、技术实现及用户体验上的异同。通过对比分析,旨在为读者提供一个全面而深入的视角,理解两大平台如何在保障用户隐私的同时,实现功能的丰富与便捷。本文不涉及具体产品推荐或品牌偏好,仅从技术角度出发,探讨隐私保护的现状与挑战。 ####
|
8天前
|
前端开发 Android开发 iOS开发
探索Android与iOS的跨平台开发策略
在移动应用开发的多元化时代,跨平台开发已成为开发者追求效率和广泛覆盖的重要手段。本文深入探讨了Android与iOS两大主流平台下的跨平台开发策略,分析了各自的优势与挑战,并通过实际案例展示了如何有效实施跨平台解决方案,以期为开发者提供有价值的参考和启示。
|
6天前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第39天】在苹果的生态系统中,SwiftUI框架以其声明式语法和易用性成为开发者的新宠。本文将深入SwiftUI的核心概念,通过实际案例展示如何利用这一框架快速构建用户界面,并探讨其对iOS应用开发流程的影响。
|
7天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS的系统架构差异
本文旨在通过对比分析Android和iOS两大移动操作系统的系统架构,揭示它们在设计理念、安全性、应用生态及开发环境等方面的显著差异。我们将从底层架构出发,逐步剖析至用户界面层面,为开发者和科技爱好者提供一份详尽的技术参考。
17 1