Android插件开发#
实现一个plugin#
1、Plugin必须实现HyPlugin接口。
2、在receiveJsMsg方法上添加PluginAnnotation注解,注解里面的name为与前端约定的handleName。
推荐handleName定义成: 业务名称.功能 这种形式
3、Plugin的onCreate在Plugin实例化的时候被调用,onDestory是Plugin被销毁的时候调用,receiveJsMsg是在接
收到QunarAPI消息的时候被调用。
推荐在receiveJsMsg中每收到一条消息就new一个新的业务类实例来处理这条消息。禁止用静态变量来持有JSResponse对象。
public class NetworkType implements HyPlugin {
@Override
public void onCreate() {
//TODO 初始化
}
@Override
public void onDestory() {
//TODO 处理资源回收
}
@PluginAnnotation(name="network.getType")
@Override
public void receiveJsMsg(JSResponse jsResponse, String handlerName) {
//TODO 编写业务分发逻辑
}
}
4、Plugin给js回数据
jsResponse.error(1, msg, null); 或者 jsResponse.success(jsonObject);
5、如果需要接收onActivityResult的数据,需要向HyPRWebView中注册HyStatusListener实例。
jsResponse.getContextParam().hyView.getIBaseContext().registerActivityStatusListener(new HyStatusListener());
6、Plugin在第一次被QunarJS调用的时候被实例化,在跟自己业务Project关联的所有WebView被销毁后才销毁。
iOS插件开发#
HelloWord#
H在 iOS 上开发 Hytive 插件并不是一件复杂的事情,下面从 “Hello world” 开始,逐步进行说明。
#import "HYPlugin.h"
// 一个插件类,应该是继承自 HYPlugin
@interface HYHelloWorldPlugin : HYPlugin
@end
#import "HYHelloWorldPlugin.h"
@implementation HYHelloWorldPlugin
// 这个标志会把插件注册到 Hytive 中, 从而 Hytive 知道了这个类属于一个插件
HY_LAZY_LOAD_PLUGIN()
// 方法的定义比较特殊,需要按照插件规定的格式写,只有按规定格式写方法签名,才能得到正确的参数,从而 JS 和 Native 可以很好的传参或回调
- (void)hello:(NSDictionary *)data callback:(HYJavaScriptCallback)responseCallback {
// HANDLE_NAME 相当关键,确定了和前端约定的 api
HANDLE_NAME(hello);
//data为前端调用navtive时传递的参数,responseCallback为回调前端函数
if (data && [data objectForKey:@"value"]) {
id value = [data objectForKey:@"value"];
NSDictionary *responseDict = @{@"result": [NSString stringWithFormat:@"Hello, %@", value]};
// 这是一个成功的回调
// 给 js 返回数据的 callback,数据格式是 NSDictionary
responseCallback(JSResponseSuccess(), responseDict);
}else{
// 这是一个失败的回调,需要传递errcode,和errmsg
responseCallback(JSResponseFail(-1, @"参数为空"), @{});
}
}
@end
麻雀虽小五脏俱全,上面插件就是一个 Hytive 完整的插件, 前端调用方式。
WebViewJavascriptBridge.invoke('hello', function(data){console.log(data)}, {value='world'});
// 可以返回 Hello, world
插件开发详细流程#
看完了上面的例子,下面开始详细介绍开发一个插件的流程和涉及到的知识点。
2.1 定义插件类,继承自 HYPlugin #
一个插件必须继承自 HYPlugin 基类,HYPlugin 实现了 HYBridgeDelegate 协议,如果不继承 HYPLug,则注册插件将会抛出异常。
2.2 插件级别 #
2.2.1 定义 #
根据插件实例化之后所有者,给插件定义级别。如果实例化之后属于整个项目的,则级别为 HYPluginLevelProject,如果实例化之后属于 WebView,则级别为 HYPluginLevelView。
在 Hytive 中,定义了一个枚举 HYPluginLevel 来定义插件的级别。
/**
* 插件 类型
*/
typedef NS_ENUM(NSInteger, HYPluginLevel){
/**
* 项目级别的插件,每个项目只有一个实例
*/
HYPluginLevelProject = 0,
/**
* WebView级别的插件, 每个webview将会拥有自己的插件实例
*/
HYPluginLevelView,
};
2.2.2 使用 #
在插件中实现以下方法,指定插件的级别。
+ (HYPluginLevel)getPluginLevel;
实例
+ (HYPluginLevel)getPluginLevel
{
return HYPluginLevelProject;
}
2.2.3 项目级别的插件 #
HYPluginLevelProject 为默认值,如果不实现+ (HYPluginLevel)getPluginLevel
方法,则默认插件级别为 HYPluginLevelProject。
2.2.4 Webview级别的插件 #
HYPluginLevelView 级别的插件实例化完之后,会与 Webview 一一对应起来。在 Webview 需要保存数据的时候,可以直接保存到插件中,而不会被别的 Webview 污染数据。 同样的场景在项目级别的插件中,会因为变量是全局的导致会被新的 Webview 改掉。
一个常见的场景是分享插件,分享插件每个页面都有不同的数据,如果实现为 HYPluginLevelProject 级别,则新开 Webview 之后,分享插件的数据会被修改,返回到前一页面之后需要重新赋值。
2.3 注册插件 #
插件创建完之后,必须注册,Hytive 才能识别。目前支持两种类型的插件,分别是:
使用时才初始化的插件(lazyload plugin), 通过`HY_LAZY_LOAD_PLUGIN()`` 大部分插件都属于这种类型,因为很多项目根本不会使用所有的插件,所以提供懒加载插件会有很多优点。
Lazyload 插件只有 HTML5 调用了这个插件类中的方法时,才会初始化,并且会按照项目级别或者 Webview 级别去保存插件实例。
一开始就初始化的插件(autoload plugin),通过
HY_AUTO_LOAD_PLUGIN()
这种类型的插件,主要应用场景是需要在初始化时监听通知,或者 Native 主动给 JS 发送消息的场景, 所以极少部分插件才会注册为 autoload plugin。
例子:
@implementation VWGNotificationPlugin
HY_AUTO_LOAD_PLUGIN();
- (id) init {
self = [super init];
if (self) {
// 需要注册通知,就使用 autoload plugin
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selector:) name:@"name" object:nil];
}
return self;
}
@end
还有一种场景是 Native 主动给 JS 发送消息时,应该把插件注册为 autoload,这样就能保证改插件能尽快实例化。
值得一提的是,注册为 autoload 的插件,都属于项目级别的插件(与之对应的还有 WebView 级别的插件,后面会介绍)。
注: 在早期版本,会通过 Runtime,遍历所有类找到插件,自动注册,后来考虑到性能问题,演变成了插件主动注册。
2.4 通信机制 #
Hytive-iOS 的通信机制用一句话来说就是:“JS 到 Native 是通过 AJAX 请求,Native 这边拦截之后解析数据,进行处理。Native 到 JS 是通过 Native 把数据构建成一个 JS 代码,然后用 Native 执行一下 JS”。
2.4.1 定义 JS 到 Native 的通信方法 #
JS 到 Native的通信是最常用的使用方式,几乎大部分插件都是这种类型的交互,比如获取地理位置,调用扫一扫等等。
要是先 JS 到 Native 的通信,只要遵循 Hytive 的规则,实现起来并不复杂。
/*
* 三个参数的插件方法
* param context native的上下文,里面包含了 HYView ID 和 Project ID
* param data 前端传给 native 的参数
* param responseCallback 给 JS 的 callback
*/
- (void)nativeContext:(HYContextParam *)context jsdata:(NSDictionary *)data callback:(HYJavaScriptCallback)responseCallback {
HANDLE_NAME(jshandler_name);
...
}
从上面的代码可以看出,我们只要按规则定义一个方法,即可做到 JS 和 Native 交互。
其实针对规则来说,方法名并不重要,重要的是参数,不管方法名是什么,参数肯定会是和上面一样的。
HANDLE_NAME(jshandler_name);
jshandler_name 定义了和前端交互的 handler name,Hytive 也是通过 handler name 才能找到要初始化当前的插件类。
建议 handler name 加上业务前缀,以避免和其它插件重复。(在 iOS 上如果定义了重复的 handler name,程序编译会触发断言崩溃。)
插件方法不仅仅三个参数这一种,Hytive 还支持两个参数和一个参数的插件。分别是
/*
* 两个参数的插件方法
* param data 前端传给 Native 的参数
* param responseCallback 给 JS 的 callback
*/
- (void)jsdata:(NSDictionary *)data callback:(HYJavaScriptCallback)responseCallback {
HANDLE_NAME(jshandler_name);
...
}
/*
* 一个参数的插件方法
* param responseCallback 给 JS 的 callback
*/
- (void)jscallback:(HYJavaScriptCallback)responseCallback {
HANDLE_NAME(jshandler_name);
...
}
还有一个值得要提的是,Hytive 对 responseCallback 进行了封装,写插件的人不需要关注太多数据的构造。
/**
* 返回给JS的成功数据封装,ret = true
*
* @return 构造出的 HYJSResponse 对象 包含ret = true
*/
HYJSResponse * JSResponseSuccess();
/**
* 返回给 JS 的失败数据的封装, ret = false, errocode,和 errmsg 可定义
*
* @param errcode 错误码
* @param errmsg 错误信息
*
* @return 构造出的 HYJSResponse 对象 包含ret = false, errcode, errmsg
*/
HYJSResponse * JSResponseFail(NSInteger errcode, NSString *errmsg);
- 注: 涉及到 UI 的插件一定要在主线程执行
现在所有的插件均运行在主线程,因此当存在耗时长的操作时为了避免页面卡住需使用多线程来处理
2.4.2 从 Native 到 JS 的通信方法 #
Native 到 JS 的调用,是通过 Bridge 来完成的,而 Bridge 的初始化是在 Project中进行的。所以使用 Bridge 对象时,可以用 projec.bridge
的形式完成。
Native 到 JS 的通信总共提供了 6 个方法,分别是:
/**
* 给当前项目的所有 Webview 发送消息
*
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
*/
- (void)sendAll:(NSString*)handlerName;
/**
* 给当前项目的所有 Webview 发送消息
*
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
* @param param 传给 JS 的数据,是一个 Dictionary 对象
*/
- (void)sendAll:(NSString*)handlerName withParam:(id)param;
/**
* 给当前项目的所有 Webview 发送消息
*
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
* @param param 传给 JS 的数据,是一个 Dictionary 对象
* @param responseCallback JS 的回调
*/
- (void)sendAll:(NSString *)handlerName withParam:(id)param responseCallback:(HYResponseCallback)responseCallback;
/**
* 给指定的 Webview 发送消息
*
* @param otherView 要发送消息的 Webview
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
*/
- (void)sendTo:(HYView *)otherView handler:(NSString *)handlerName;
/**
* 给指定的webview发送消息
*
* @param otherView 要发送消息的 Webview
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
* @param param 传给js的数据,是一个 Dictionary 对象
*/
- (void)sendTo:(HYView *)otherView handler:(NSString *)handlerName param:(id)param;
/**
* 给指定的 Webview 发送消息
*
* @param otherView 要发送消息的 Webview
* @param handlerName handlerName, 不允许重复,命名规则 XXX.XXX, 例如 webview.open
* @param param 传给 JS 的数据,是一个 Dictionary 对象
* @param responseCallback JS 的回调
*/
- (void)sendTo:(HYView *)otherView handler:(NSString *)handlerName param:(id)param responseCallback:(HYResponseCallback)responseCallback;
其实上面方法总结下来就两类,一类是给所有项目级别的 WebView 发送消息,另一类是给单独的 WebView 发送消息。
验证#
开发完Hytive的插件,接下来就是验证。
在Safari的调试工具中,输入以下命令
// js 调用native的函数,handler_name为HANDLE_NAME中的名字
WebViewJavascriptBridge.invoke('handler_name', function(data){console.log(data)},{参数})
// 监听navtive调用的函数
WebViewJavascriptBridge.on('handler_name',function(){alert('data')})