iOS(安卓/客户端)与JS交互方案设计

对于有热更新需求的App 应用,使用前端技术写业务代码是一个应用比较广泛的技术方向。随之而来的就是如何使JS与原生的交互简单且规范。 我们想要达到的目的:

① 交互方法的统一,实现标准化。 ② JS 调用原生简单无感,结果通过 block 回调给JS。 ③ 客户端接收JS交互方法简单无感,只需要专注业务代码实现。

1. 实现方案探索

常用的 JS 交互方案有三种: ① 拦截自定义的超链接协议请求,通过解析超链接,实现指定交互方法。 ② 通过JSCore 实现,客户端向JS注入OC类。 ③ 通过window.webkit.messageHandlers.xxx.postMessage(params); 实现。

我们最终选用了第③种方式实现。 第一种方式不够灵活,不仅要自定义schme,参数也需要进行GET请求拼接,复杂的交互也难以实现。 第二种方式虽然可以,但是会暴露原生的类给JS,暴露类名并不是我们想要的。 所以我们选用了第三种通过方式实现。

2. 交互的统一化和标准化

2.1 JS统一化交互

统一化和标准化,在JS端的体现,我们想要将所有与原生交互的实现,由一个统一的方法输出,且前端(JS)无需关注iOS或安卓系统。 这样避免交互方法的杂乱无章。 前端查阅代码,见到这样的方法调用,一眼就可以看出来是与前端交互,且交互的具体行为。 前端只要想到交互,就想到调用这样的方法实现实现即可。 这样就大大减轻了前端的交互成本。 例如定义一个统一的交互方法:

KYJSBridge_call(xxx1, xxx2, callback)

JS需要由原生实现的功能,都由该方法实现。 第一个参数(xxx1): 是需要原生实现的功能名称。 第二个参数(xxx2): 是处理该功能需要的参数。 第三个参数(callback): 处理结果原生通过block 返回给JS。 比如前端想要原生实现一个拍照并压缩到指定大小的功能,前端与原生约定功能名称为takePhoto(这里成为JSApi,后面会用),压缩参数为{'photoSize':'500'}。 那么前端直接调用:

KYJSBridge_call('takePhoto', {'photoSize':'500'}, function (result) {

var iamgeBase64String = result.iamgeBase64String;

});

这就是我们想要的前端的统一化和标准化的东西。

2.2 原生统一化交互

既然前端实现了这样的统一化,那么按照这个思路,原生提供的交互也要有一个统一的规范,于是,我们定义原生的交互接口规范,所有给前端提供的交互接口,都是遵循这样的方法实现。如下:

typedef void (^KYJsApiResponseCallbackBlock)(id responseData);

@protocol KYJsApiHandlerProtocol

@optional

/// JsApi 统一处理函数

/// @param data js 传递过来的参数

/// @param context 当前上下文

/// @param callback 处理结果给 js 回调,回调用一次即失效

- (void)jsApiHandler:(NSDictionary *)data context:(KYJsContext *)context callback:(KYJsApiResponseCallbackBlock __nullable)callback;

@end

查阅代码时,只要是遵循该接口规范的实现,我们就能知道这是提供给JS端的。 但是,如果所有接口都是一样的名字,我们又该如何区分JS要原生实现的是什么功能呢? 这里就要进入一个查表机制,也就是前面提高的JSApi 与对应实现的类在原生注册到一个表中,根据前端传过来的JSApi,查表找到具体实现的类。因为原生的接口方法是统一的,因此找到实现也就不难了。这一些列操作都是在框架内部完成的。 客户端只需要遵守协议,并实现协议。同时把约定的JSApi与实现类注册到表中即可。 这也就达到了客户端提供接口的统一性和规范性。

3. CallBack 实现

原生的处理结果如何通过callback 给到JS。 这其实就是 原生调用JS闭包的一个过程,但是我们又如何找到对应交互的JS闭包? 那就保存JS闭包代码。 在JS调用桥接方法KYJSBridge_call 的时候,就需要将该闭包保存起来,待原生完成相关处理后,再找到该闭包去执行。 保存的时候,可以利用时间戳(至少要精确到毫秒级),生成一个唯一的Key值,将闭包保存,同时该Key值要传递给原生,待原生完成任务后,再将该Key和结果传递给JS,JS通过Key找到闭包,并调用闭包执行。 这样的整个过程,都是在框架内部实现,前端和原生都是无感知的。

4. 上下文的设计

有很多上下文的环境参数,比如 WebView 容器控制器,有时候也是需要在交互中用到的,我们引入了 Context 上下文的设计理念,将一些参数保存的上下文中,供原生交互接口使用,同时也便于扩展,又不影响现有代码的正常使用。

5. 扩展

在该交互方案的基础上,封装私有的交互方法,同时单独维护私有的JSApi注册表。 比如前端网络请求,原则上都要通过原生去发出,并把结果给到JS。 以及导航栏的相关设置操作。 同时,该框架中也应用到了自定义导航栏和自定义的Web容器。

5. 代码

如果您有兴趣,欢迎下载阅读。

说明: 源代码已做脱敏处理。 源代码仅做技术分享,但基本功能可用。 细节业务处理,需要您自己补充。

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: