HBuilder 第三方插件开发

本人最近开发了 HBulider 集成极光推送(JPush)的插件,鉴于 HBuilder 官网上缺少 iOS 的示例 ,而且官网也只给出了 JavaScript 调用 native 代码的接口,对于 native 调用 JavaScript 并且向 JavaScript 发送 event 事件的方法却在 native层 进行了封装。笔者在踩过了一些小坑之后,终于成功的开发了插件,并且 实现了 JavaScript 和 native 的双向沟通 。特此跟大家分享一下在 HBuilder 插件开发过程中的经验和关键代码。

JPush 实例展示


首先附上完整 demo [JPush HBuilder Demo] 并为大家展示一下:

实例及功能展示

以上即为根据本文内容开发出的实例

如您需使用极光推送产品请至此 [极光推送官方网站]

新插件配置


配置 manifest.json

首先用源码的方式打开工程 /Pandora/ 目录下的 manifest.json ,在 "permissions" 中添加新的插件名称:

 "permissions": {
    "Push":{
        "description": "极光推送插件"
    }
},
配置 feature.plist

在 Xcode 中打开 /PandoraApi.bundle/ 目录下的 feature.plist ,为插件添加新的 item:

feature.plist

其中需要注意的是:

  • 最顶部的 key 值 Push ,必须跟 manifest.json 中配置的插件名一致
  • class 的值需要跟 native 代码中的类名一致,此处为 JPushPlugin
  • 因为本插件拓展自 HBuilder 已经封装好的 PGPush ,故 baseclass 为父类

通过以上配置,就可以在 JavaScript 中通过 Push --> JPushPlugin 的对应关系,调用 native 代码了。

JavaScript 调用本地代码的实现


这部分在 [HBuilder 官网插件开发指导] 中已经给出了较详细的说明,这里就不再赘述,附上关键代码:

document.addEventListener("plusready", function() {
    var _BARCODE = 'Push';  // 插件名称
    var B = window.plus.bridge;
    
    var JPushPlugin = {

        callNative : function(fname, args, successCallback) {
            var callbackId = this.getCallbackId(successCallback, this.errorCallback);
            if (args != null) {
                args.unshift(callbackId);
            } else {
                var args = [callbackId];
            }
            return B.exec(_BARCODE, fname, args);
        },
        
        getCallbackId : function(successCallback) {
            var success = typeof successCallback !== 'function' ? null : function(args) 
            {
                successCallback(args);
            };
            callbackId = B.callbackId(success, this.errorCallback);
            return callbackId;
        },
        
        errorCallback : function(errorMsg) {
            console.log("Javascript callback error: " + errorMsg);
        },
            
        jsHello : function(args){
            this.callNative("nativeHello", args, null);
        },
        
    window.plus.Push = JPushPlugin;
    
}, true);

其中 callNative 为封装好用于调用 native 代码的方法,参数如下:

  • fname:要调用的 native 的方法名
  • args:传给 native 的参数,必须是数组
  • successCallback:成功回调,null 为没有

以上代码最后面的 "jsHello" 方法,即为封装好的 js 方法,在工程的其他文件里通过

window.plus.Push.jsHello(args);

的方式即可调用本地的 "nativeHello" 方法。

Objective-C 调用 js 的实现


与 Phonegap 的差异

在 HBuilder 官方文档中并没有提及 OC 调用 js 的方法,从 OC 中的类名(PGPlugin 等)可以看出,其应该是对 Phonegap 的封装,但是却并没有提供 Phonegap 中直接调用 js 的接口,例如:

NSString *evalString = [NSString stringWithFormat:@"jsFunction(%@)",args];
[self.commandDelegate evalJs:evalString];

也无法向 js 发送 event ,例如:

NSString *evalString = [NSString stringWithFormat:@"cordova.fireDocumentEvent('event_name',%@)",args];
[self.commandDelegate evalJs:evalString];

其中 self 为继承自 CDVPlugin 的插件类实例。

经过笔者的查找,发现在 HBuilder 提供的 PDRCoreAppFrame(:PDRNView :UIView) 类中,有如下方法可以调用 js 代码:

/**
 @brief 在当前页面同步执行Javascript
 @param js javasrcipt 脚本
 @return NSString* 执行结果
 */
- (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)js;

获取 PDRCoreAppFrame 对象

其中 PDRCoreAppFrame 为控制 webView 的实例,数量可能为多个,且在视图层级中的位置不确定,故需要通过遍历 app 中所有 view ,来找出 PDRCoreAppFrame ,以下是通过 递归 找出所有 PDRCoreAppFrame 的方法:

-(NSMutableArray*)searchViews:(NSArray*)views{
    NSMutableArray *frames = [NSMutableArray array];
    for (UIView *temp in views) {
        if ([temp isMemberOfClass:[PDRCoreAppFrame class]]) {
            [frames addObject:temp];
        }
        if ([temp subviews]) {
            NSMutableArray *tempArray = [self searchViews:[temp subviews]];
            for (UIView *tempView in tempArray) {
                if ([tempView isMemberOfClass:[PDRCoreAppFrame class]]) {
                    [frames addObject:tempView];
                }
            }
        }
    }
    return frames;
}

其中:

  • 参数 views 为同一层级中的 views
  • 返回值 frames 为从该层级中找到的 PDRCoreAppFrame

调用 js

这样我们就可以用上述方法获取到所有的 PDRCoreAppFrame 进而调用 js 代码了:

-(void)evaluatingJavaScriptFromString:(NSString*)string{
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    NSArray *views = [[[window rootViewController] view] subviews];
    //调用上述方法
    NSArray *frames = [self searchViews:views];
    for (PDRCoreAppFrame *appFrame in frames) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [appFrame stringByEvaluatingJavaScriptFromString:string];
        });
    }
}

调用示例:

NSString *evalString = @"alert("make a js call");";
[self evaluatingJavaScriptFromString:evalString];

但是并不建议用这种方式,因为该方法会强制向每个 webView 的页面都发送一条执行语句,有时会出现并不希望的结果。因此,建议使用下面发送 event 的方式,并在 js 中接收后进行处理。</p>

向 js 发送 event

笔者对上述方法再次进行了封装:

-(void)fireEvent:(NSString*)event args:(id)args{
    NSString *evalString = nil;
    NSError  *error      = nil;
    NSString *argsString = nil;

    if (args) {
        if ([args isKindOfClass:[NSString class]]) {
            argsString = args;
        }else{
            NSData   *jsonData   = [NSJSONSerialization dataWithJSONObject:args options:0 error:&error];
            argsString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
            if (error) {
                NSLog(@"%@",error);
            }
        }
        evalString = [NSString stringWithFormat:@"\
                      var jpushEvent = document.createEvent('HTMLEvents');\
                      jpushEvent.initEvent('%@', true, true);\
                      jpushEvent.eventType = 'message';\
                      jpushEvent.arguments = '%@';\
                      document.dispatchEvent(jpushEvent);",event,argsString];
    }else{
        evalString = [NSString stringWithFormat:@"\
                      var jpushEvent = document.createEvent('HTMLEvents');\
                      jpushEvent.initEvent('%@', true, true);\
                      jpushEvent.eventType = 'message';\
                      document.dispatchEvent(jpushEvent);",event];
    }
    //调用上述方法
    [self evaluatingJavaScriptFromString:evalString];
}

其中对传入的 args 进行了简单的处理。

最后我们通过调用一行代码即可做到向 js 发送 event :

[self fireEvent:@"event_name" args:args];

js 接收 event 并处理

在上一步中发送了 "event_name" 的事件之后,可以在 html 的 script 中通过以下方式捕获:

document.addEventListener("event_name", onEventFunc, false);
function onEventFunc(args){
    var obj = JSON.parese(args);
    window.setTimeout(function(){
        alert(obj);
    },0);
}

至此,就彻底实现了 Objective-C 向 js 的沟通

* 如您对本文有任何疑问或建议,欢迎交流

版权印为您的作品印上版权

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容