HBuilder 第三方外掛開發

pikacode發表於2016-12-16

本人最近開發了 HBulider 整合極光推送(JPush)的外掛,鑑於 HBuilder 官網上缺少 iOS 的示例 ,而且官網也只給出了 JavaScript 呼叫 native 程式碼的介面,對於 native 呼叫 JavaScript 並且向 JavaScript 傳送 event 事件的方法卻在 native層 進行了封裝。筆者在踩過了一些小坑之後,終於成功的開發了外掛,並且 實現了 JavaScript 和 native 的雙向溝通 。特此跟大家分享一下在 HBuilder 外掛開發過程中的經驗和關鍵程式碼。

JPush 例項展示


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

HBuilder 第三方外掛開發
例項及功能展示

以上即為根據本文內容開發出的例項

如您需使用極光推送產品請至此 [極光推送官方網站]

新外掛配置


配置 manifest.json

首先用原始碼的方式開啟工程 /Pandora/ 目錄下的 manifest.json ,在 "permissions" 中新增新的外掛名稱:

 "permissions": {
    "Push":{
        "description": "極光推送外掛"
    }
},複製程式碼
配置 feature.plist

在 Xcode 中開啟 /PandoraApi.bundle/ 目錄下的 feature.plist ,為外掛新增新的 item:

HBuilder 第三方外掛開發
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 中接收後進行處理。

向 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 的溝通

* 如您對本文有任何疑問或建議,歡迎交流

相關文章