本人最近開發了 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:
其中需要注意的是:
- 最頂部的 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 的溝通