当前位置: 代码网 > it编程>编程语言>Javascript > JSBridge原理 - 前端H5与客户端Native交互

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

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

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接口,实现彼此的双向调用。

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 中注册的回调函数,并传递执行结果或错误信息等。

具体实现代码:

  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,
        );
}

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com