HBuilder 第三方插件开发

作者: pikacode | 来源:发表于2016-04-21 16:10 被阅读8759次

    本人最近开发了 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 的沟通

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

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

    相关文章

      网友评论

      • LeesinS:OC调用JS方法 可以这样获取:
        PDRCore *pd = [PDRCore Instance];
        PDRCoreAppFrame *app = [pd.appManager.activeApp mainFrame];
        [app evaluateJavaScript:"js方法" completionHandler:nil];
      • 850d3cbb6e47:hbuilder集成阿里云推送做过吗?
      • 卧龙小:我想js 发送Event ,但是无法调用 document.addEventListener 中监听的JS 方法~ 求教
      • 950b816c0f00:请问我用的是PDRCoreRunModeNormal方式集成,可以获取 PDRCoreAppFrame 对象,使用stringByEvaluatingJavaScriptFromString原生调JS吗,试了很多方法还是不行,不知道什么原因,大神能指导我一下吗
      • 酷哥不回头看爆炸:@共由石石石 我没有找到原因。但是解决了问题,我是在原生的获取到token的回调方法里面又重新调用了mui的方法。
        共由石石石:@无言是非丶 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
        {
        [PDRCore handleSysEvent:PDRCoreSysEventRevDeviceToken withObject:deviceToken];
        [JPUSHService registerDeviceToken:deviceToken];
        }
        我这样写了一下,可以注册推送,但是前端掉push.push.init会报错.
        共由石石石:- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
        {
        [PDRCore handleSysEvent:PDRCoreSysEventRevDeviceToken withObject:deviceToken];
        }
        是这个方法吗?
      • cf6c45fca3dd:在新插件配置的配置 feature.plist 只能用 Xcode或Android 中打开吗?
      • f677f02780e8:不好意思,按照这种方式集成到自己的app时报not get devicetoken yet,但是在demo中用相同的buddleid及appkey却是成功的,不知道为什么?
        共由石石石:你这个问题找到原因了吗?我现在也是这样的.都核对过了,实在找不到原因了.
        pikacode:不清楚你的问题
      • 穿山甲到底说了什么:看在我 还在加班的份上。。。。。 :kissing_heart:
        共由石石石:@无言是非丶 你好,你找到这个原因了吗?我这边也是走不到这个方法.
        酷哥不回头看爆炸:你好, 添加极光推送插件之后 JPushPlugin.m 中onRevDeviceToken方法未被调用,是什么原因?
        pikacode:@穿山甲到底说了什么 加我微信 pikacode
      • 穿山甲到底说了什么:大哥,大大哥,大大大大哥,告诉我吧,我发现了可能是死锁,后来想到直接开启一个新线程来调用JS,没想到直接崩了,一看堆栈错误,果然是加了WebLock 同步锁,dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [appFrame stringByEvaluatingJavaScriptFromString:@"alert('hhhhhhhh')"];
        });这样也不行,我是实在 没辙了,还求你给条明路,爱你,么么哒❤️
        AppleLSY:哦,你的意思就是当我调用原生插件的时候,在JS的回调里面再写一个调用另一个插件的方法,在另一个插件方法里面只写callBack方法,然后JS接收回调是吧?
        穿山甲到底说了什么:@AppleLSY 用这个方法可以 进PDRCoreAppFrame头文件看看注释 -[PDRCoreAppFrame dispatchDocumentEvent:]; 这个接口可以调用JS方法不死锁,不会卡住UI,但是这个接口没有提供任 参数,只有一个eventName,也就意味着 你只能调用JS方法,确无法传递参数给Js,所以 ,我最后 的解决方案是 重新写了一个插件如TestPGPlugin继承PGPlugin,就跟官方插件Demo一样,先在JS层注册(即调用)你的异步插件方法,这点很重要,这样就有了回调callbackId,注意插件方法keepCallback写成YES,这样就可以多次通知JS层。当你想 调用哪个JS方法时候,就在异步插件方法 给JS层 发送一条消息,Js收到回调后,你可以再发送一条通知给iOS原生层或者再JS收到的回调callback里面再次调用在你的另一个插件同步方法不就可以了吗,可以就可以传很多参数,看你iOS原生层需要哪些参数,就在Js层传哪些,这种解决方案肯定可以,
        AppleLSY:@穿山甲到底说了什么 我也遇到相同的情况..试了很多方法都不行..请问你解决了吗?
      • 穿山甲到底说了什么:很抱歉的打扰您,我有一个 问题就是 ,为什么 在官方Demo中直接调用 dispatch_async(dispatch_get_main_queue(), ^{
        [appFrame stringByEvaluatingJavaScriptFromString:@"alert('hhhhhhhh')"];
        });
        本地是可以 调用Js代码,但是直接在主线程就死锁了,界面上就弹出了一个alert框,点都点不动,本地调用Js我就简单alert("xxxx")都不行,这是什么问题,能给个解决问题方向吗,感激不尽,谢谢,项目中需要调用Js,我这第一步都不行,很急,🙏🙏🙏
        950b816c0f00:@pikacode 怎么解决的啊,大神能告诉我一下吗,可以发我邮箱里不这个问题的解决方案,2289220150@qq.com;求方法
        pikacode:@穿山甲到底说了什么 我也遇到过同样的问题,然后解决了:stuck_out_tongue_winking_eye:

      本文标题:HBuilder 第三方插件开发

      本文链接:https://www.haomeiwen.com/subject/bzmtrttx.html