JSBridge原理 - 前端H5与客户端Native交互

这篇具有很好参考价值的文章主要介绍了JSBridge原理 - 前端H5与客户端Native交互。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 概述:

在混合应用开发中,一种常见且成熟的技术方案是将原生应用与 WebView 结合,使得复杂的业务逻辑可以通过网页技术实现。实现这种类型的混合应用时,就需要解决H5与Native之间的双向通信。JSBridge 是一种在混合应用中实现 Web 和原生代码之间通信的重要机制。

1.1. 混和开发:

混合开发(Hybrid)是一种开发模式,指使用多种开发模型开发App,通常会涉及到两大类技术:

原生 NativeWeb H5

  • 原生技术主要指iOS、Android,原生开发效率较低,开发完成需要重新打包整个App,发布依赖用户的更新,性能较高功能覆盖率更高
  • Web H5可以更好的实现发布更新,跨平台也更加优秀,但性能较低,特性也受限

混合开发的意义就在于吸取两者的优点,而且随着手机硬件的升级迭代、系统(Android 5.0+、ISO 9.0+)对于Web特性的较好支持,H5的劣势被逐渐缩小。

1.2. JSBridge 的概念和作用:

  1. 通信桥梁: JSBridge 充当了 Web 应用和原生应用之间的通信桥梁。通过 JSBridge,我们可以在 web 和原生代码之间进行双向通信,使这两者能够互相调用和传递数据。
  2. 原生功能调用: 使用 JSBridge,我们可以在 JavaScript 中调用原生应用中的功能。我们可以通过 web 来触发原生应用中的特定操作,如打开相机、发送通知、调用硬件设备等。
  3. 数据传递: JSBridge 使得 JavaScript 和原生代码之间可以方便地传递数据。意味着我们可以在 web 和原生代码之间传递复杂的数据结构,如对象、数组等,以满足应用的功能需求。
  4. 回调机制: JSBridge 支持回调机制,使得在原生代码执行完某些操作后可以通知 JavaScript,并传递相应的结果。

1.3. 为什么在混合应用开发中 JSBridge 如此重要:

  1. 跨平台开发: JSBridge 允许我们在混合应用中使用一套代码同时运行在不同的平台上。这意味着我们可以使用 Web 技术来开发应用的核心逻辑,并在需要时通过 JSBridge 调用原生功能,从而实现跨平台开发,提高开发效率。
  2. 原生功能扩展: 使用 JSBridge,我们可以充分利用原生平台提供的功能和能力,例如访问硬件设备、调用系统 API 等。这使得我们可以为应用添加更多丰富的功能,提升用户体验。
  3. 灵活性和扩展性: JSBridge 提供了一种灵活和可扩展的方式来实现 Web 和原生代码之间的通信。开发人员可以根据应用的需求随时添加新的原生功能,并通过 JSBridge 在 JavaScript 中调用这些功能,从而实现应用的功能扩展和升级。

2. JSBridge 做了什么?

在Hybrid模式下,H5会需要使用Native的功能,比如打开二维码扫描、调用原生页面、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等,而JavaScript是运行在单独的 JS Context 中(Webview容器)与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的 双向通信 ,这就是JSBridge:以JavaScript引擎或Webview容器作为媒介,通过协定协议进行通信,实现Native端和Web端双向通信的一种机制。

通过JSBridge,Web端可以调用Native端的Java接口,同样Native端也可以通过JSBridge调用Web端的JavaScript接口,实现彼此的双向调用。

JSBridge原理 - 前端H5与客户端Native交互,项目架构,前端,react native

3. JSBridge 实现原理:

把 Web 端和 Native 端的通信比作 Client/Server 模式。JSBridge 充当了类似于 HTTP 协议的角色,实现了 Web 端和 Native 端之间的通信。

将 Native 端原生接口封装成 JavaScript 接口:在 Native 端将需要被调用的原生功能封装成 JavaScript 接口,让 JavaScript 代码可以调用。 JavaScript 接口会被注册到全局对象中,以供 JavaScript 代码调用。

将 Web 端 JavaScript 接口封装成原生接口: 这一步是在 Web 端将需要被调用的 JavaScript 功能封装成原生接口。这些原生接口会通过 WebView 的某些机制暴露给原生代码,以供原生代码调用。

3.1. Native -> Web

Native端调用Web端,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。

3.1.1. Android:

Android 提供了 evaluateJavascript 来执行JS代码,并且可以获取返回值执行回调:

String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value) {

  }
});
3.1.2. IOS:

IOS的 WKWebView 使用 evaluateJavaScript:

[webView evaluateJavaScript:@"执行的JS代码" 
  completionHandler:^(id _Nullable response, NSError * _Nullable error) {
  // 
}];

3.2. Web -> Native

Web调用Native端主要有两种方式

3.2.1. URL Schema

URL Schema是类URL的一种请求格式,格式如下:

<protocol>://<host>/<path>?<qeury>#fragment
  
// 我们可以自定义JSBridge通信的URL Schema,比如:
hellobike://showToast?text=hello

Native加载WebView之后,Web发送的所有请求都会经过WebView组件,所以Native可以重写WebView里的方法,从来拦截Web发起的请求,我们对请求的格式进行判断:

  • 符合我们自定义的URL Schema,对URL进行解析,拿到相关操作、操作,进而调用原生Native的方法
  • 不符合我们自定义的URL Schema,我们直接转发,请求真正的服务

例如:

  get existOrderRedirect() {
    let url: string;
    if (this.env.isHelloBikeApp) {
      url = 'hellobike://hellobike.com/xxxxx_xxx?from_type=xxxx&selected_tab=xxxxx';
    } else if (this.env.isSFCApp) {
      url = 'hellohitch://hellohitch.com/xxx/xxxx?bottomTab=xxxx';
    }
    return url;
  }

这种方式从早期就存在,兼容性很好,但是由于是基于URL的方式,长度受到限制而且不太直观,数据格式有限制,而且建立请求有时间耗时。

3.2.2. 在Webview中注入JS API

通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中

Web端就可以直接在全局 window 下使用这个暴露的全局JS对象,进而调用原生端的方法。

Android注入方法:

  • 4.2 前,Android 注入 JavaScript 对象的接口是 addJavascriptInterface 但是这个接口有漏洞
  • 4.2 之后,Android引入新的接口 @JavascriptInterface 以解决安全问题,所以 Android 注入对对象的方式是有兼容性问题的。

IOS注入方法:

  • iOS的UIWebView:JavaSciptCore 支持 iOS 7.0 及以上系统
  • iOS的WKWebView:WKScriptMessageHandler 支持 iOS 8.0 及以上系统

例如:

  1. 注入全局对象
// 注入全局JS对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
    private Context ctx;
    NativeBridge(Context ctx) {
        this.ctx = ctx;
    }

    // 绑定方法
    @JavascriptInterface
    public void showNativeDialog(String text) {
        new AlertDialog.Builder(ctx).setMessage(text).create().show();
    }
}
  1. Web调用方法:
// 调用nativeBridge的方法
window.NativeBridge.showNativeDialog('hello');

4. H5具体实现:

将功能抽象为一个 AppBridge 类,封装两个方法,处理交互和回调

具体步骤:

  1. 首先需要定义一个 JavaScript 类或者对象来封装 JSBridge 方法。
  2. 在 JavaScript 类或对象的构造函数中,初始化桥接回调的方法。这个方法负责接收来自原生应用的回调数据,并根据回调数据中的信息执行相应的操作。
  3. 调用原生方法: 定义一个方法,用于在 JavaScript 中调用原生方法。这个方法需要接收原生类的映射、要调用的原生方法名以及传递给原生方法的参数,并将这些信息传递给原生应用。
  4. 处理原生回调: 在初始化桥接回调的方法中,需要定义处理原生回调的逻辑。当收到原生应用的回调数据时,根据回调数据中的信息执行相应的操作,比如调用 JavaScript 中注册的回调函数,并传递执行结果或错误信息等。

具体实现代码:文章来源地址https://www.toymoban.com/news/detail-849271.html

  1. 调用原生方法:
// 定义一个名为 callNative 的方法,用于在 JavaScript 中调用原生方法
callNative<P, R>(classMap: string, method: string, params: P): Promise<R> {
    return new Promise<R>((resolve, reject) => {
        // 生成一个唯一的回调 ID
        const id = v4();
        // 将当前的回调函数保存到 __callbacks 对象中,以 callbackId 作为键
        this.__callbacks[id] = { resolve, reject, method: `${classMap} - ${method}` };
        // 构造通信数据,包括原生类映射、要调用的方法、参数和 callbackId 
        const data = {
            classMap,
            method,
            params: params === null ? '' : JSON.stringify(params),
            callbackId: id,
        };
        const dataStr = JSON.stringify(data);
        // 根据当前环境判断是 iOS 还是 Android,并调用相应平台的原生方法
        if (this.env.isIOS && isFunction(window?.webkit?.messageHandlers?.callNative?.postMessage)) {
            // 如果是 iOS 平台,则调用 iOS 的原生方法
            window.webkit.messageHandlers.callNative.postMessage(dataStr);
        } else if (this.env.isAndroid && isFunction(window?.AppFunctions?.callNative)) {
            // 如果是 Android 平台,则调用 Android 的原生方法
            window.AppFunctions.callNative(dataStr);
        }
    });
}
  1. 回调处理:
// 初始化桥接回调函数,该参数在 constructor 中调用
private initBridgeCallback() {
    // 保存旧的回调函数到 oldCallback 变量中
    const oldCallback = window.callBack;
    // 重新定义 window.callBack 方法,用于处理原生应用的回调数据
    window.callBack = (data) => {
        // 如果存在旧的回调函数,则调用旧的回调函数
        if (isFunction(oldCallback)) {
            oldCallback(data);
        }
        // 获取原生应用的回调信息,包括数据和回调 ID
        console.info('native callback', data, data.callbackId);
        // 从回调数据中获取回调 ID
        const { callbackId } = data;
        // 根据回调 ID 查找对应的回调函数
        const callback = this.__callbacks[callbackId];
        // 如果找到了对应的回调函数
        if (callback) {
            // 如果回调数据中的 code 为 0,则表示执行成功,调用 resolve 方法处理成功的结果
            if (data.code === 0) {
                callback.resolve(data.data);
            } else {
                // 否则,表示执行失败,构造一个错误对象并调用 reject 方法处理错误信息
                const error = new Error(data.msg) as Error & {response:unknown};
                error.response = data;
                callback.reject(error);
            }
            // 删除已经处理过的回调函数
            delete this.__callbacks[callbackId];
        }
    };
}
  1. 使用:
// 调用原生方法的封装函数
callNative<P, R>(classMap: string, method: string, params: P) {
    // 从容器中解析出 AppBridge 实例
    const bridge = container.resolve<AppBridge>(AppBridge);
    // 使用 bind 方法将 AppBridge 实例中的 callNative 方法绑定到 bridge 对象上,并保存到 func 变量中
    const func = bridge.callNative.bind(bridge);
    // 调用 func 方法,并传入 classMap、method 和 params 参数,实现调用原生方法的功能
    return func<P, R>(classMap, method, params);
}


// 打开 webview
// 调用 callNative 方法,传入参数 url,classMap 为 'xxxxx/hitch',method 为 'openWebview'
openWebView(url: string): Promise<void> {
    return this.callNative<{url:string}, void>('xxxxx/hitch', 'openWebview', { url });
}


// 获取驾驶证 OCR 信息
getDriverLicenseOcrInfo(
    params: HBNative.getDriverLicenseOcrInfo.Params,
): Promise<HBNative.getDriverLicenseOcrInfo.Result> {
    // 调用 callNative 方法,传入参数 params,classMap 为 'xxxxx/hitch',method 为 'getOcrInfo'
    // 返回一个 Promise 对象,该 Promise 对象用于处理异步结果
    return this.callNative<
        HBNative.getDriverLicenseOcrInfo.Params,
        HBNative.getDriverLicenseOcrInfo.Result>(
            'xxxxx/hitch', 'getOcrInfo', params,
        );
}

到了这里,关于JSBridge原理 - 前端H5与客户端Native交互的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C/C++服务器和客户端交互笔记

    Socket通信三要素:通信的目的地址、使用的端口号(http 80 / smtp 25)、使用的传输协议(TCP、UDP)。 nslookup xx 可以查询xx网址的IP地址。 Socket通信模型 telnet ipxx 进行主机间通信。 一个简单的服务器和客户端通信程序,服务器端代码: 客户端代码: socket(套接字)的中文意思

    2024年02月13日
    浏览(44)
  • c++: websocket 客户端与服务端之间的连接交互

    目录 socket 头文件 延迟时间 通信协议地址 TCP/IP 服务端 客户端 编程步骤 服务端 客户端 编程步骤 1. 初始化 WSAStartup 2. 创建 socket 2.1 协议族 2.2 socket 类型 2.3 协议 3. 绑定 bind (服务端) 4. 监听 listen(服务端) 5. 请求连接 connect(客户端) 6. 接收请求 accept(服务端) 7. 发送

    2024年02月14日
    浏览(41)
  • 如何使用Laravel的HTTP客户端与外部API交互

    Laravel使API交互对新的和有经验的Web开发人员来说都是轻而易举的。Larvel的HTTP客户端是建立在PHP的Guzzle HTTP客户端之上,让开发者在进行HTTP请求时有更顺畅的体验。它的主要功能包括认证, 路由, 和有效的对象关系映射(ORM). 本文将探讨如何使用Laravel的HTTP客户端来进行请求, 调

    2024年01月21日
    浏览(85)
  • TCP流套接字编程(模拟多个客户端与服务器交互)

    目录 一、ServerSocket API 1.1、ServerSocket构造方法 1.2、ServerSocket方法 二、Socket API  2.1、socket构造方法  2.2、socket方法 三、TCP 中的长短连接 四、示例  实现聊天室功能 五、存在的问题  ServerSocket 是创建TCP服务端Socket的API。 1.1、ServerSocket构造方法 方法签名 方法说明 ServerSocket

    2024年02月13日
    浏览(44)
  • 韩版传奇 2 源码分析与 Unity 重制(二)客户端启动与交互流程

    该专题将会分析 LOMCN 基于韩版传奇 2,使用 .NET 重写的传奇源码(服务端 + 客户端),分析数据交互、状态管理和客户端渲染等技术,此外笔者还会分享将客户端部分移植到 Unity 和服务端用现代编程语言重写的全过程。 在这一篇文章中,我们将从客户端入手,分析从 TCP 连接

    2024年02月03日
    浏览(72)
  • 《QT从基础到进阶·十六》QT实现客户端和服务端的简单交互

    QT版本:5.15.2 VS版本:2019 客户端程序主要包含三块:连接服务器,发送消息,关闭客户端 服务端程序主要包含三块:打开消息监听,接收消息并反馈,关闭服务端 1、先打开服务端监听功能 2、点击客户端connect连接服务端 3、在客户端输入消息点击send发送到服务端 4、在服务

    2024年02月03日
    浏览(36)
  • Golang笔记:使用ssh包作为客户端与SSH服务器交互

    Golang中可以使用 golang.org/x/crypto/ssh 包作为SSH客户端或者SSH服务使用。这篇文章将简单记录下作为客户端使用的一些内容。 Package ssh implements an SSH client and server. 作为客户端与SSH服务器操作上来说主要分为三步: 使用一定的参数与SSH服务器建立连接得到 Client 对象; 在 Client 之

    2024年02月09日
    浏览(63)
  • 前端解决客户端不安全随机数

    前端项目在安全漏洞扫描的时候,爆出了客户端不安全随机数的问题,看了下代码是因为使用了 Math.random() 生成随机数造成的。 百度了一下,math.random()并不是真的随机数,而是伪随机数! 原因 Math.random() 函数是 JavaScript 内置的一个函数,它用于生成一个 0 到 1(包括0,不包括

    2024年02月16日
    浏览(44)
  • 客户端读写HBase数据库的运行原理

    1.HBase的特点 HBase是一个数据库,与RDMS相比,有以下特点: ① 它不支持SQL ② 不支持事务 ③ 没有表关系,不支持JOIN ④ 有列族,列族下可以有上百个列 ⑤ 单元格,即列值,可以存储多个版本的值,每个版本都有对应时间戳 ⑥ 行键按照字典序升序排列 ⑦ 元数据 和 数据 分

    2024年02月10日
    浏览(57)
  • 使用JSBridge框架来实现Android与H5(JS)交互

    1.首先我们来了解一下什么是JSBridge? 在开发中,为了追求开发的效率以及移植的便利性,一些展示性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给

    2024年02月08日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包