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被销毁后才销毁。

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')})