本人最近开发了 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 的沟通
* 如您对本文有任何疑问或建议,欢迎交流
版权印为您的作品印上版权
网友评论
PDRCore *pd = [PDRCore Instance];
PDRCoreAppFrame *app = [pd.appManager.activeApp mainFrame];
[app evaluateJavaScript:"js方法" completionHandler:nil];
{
[PDRCore handleSysEvent:PDRCoreSysEventRevDeviceToken withObject:deviceToken];
[JPUSHService registerDeviceToken:deviceToken];
}
我这样写了一下,可以注册推送,但是前端掉push.push.init会报错.
{
[PDRCore handleSysEvent:PDRCoreSysEventRevDeviceToken withObject:deviceToken];
}
是这个方法吗?
[appFrame stringByEvaluatingJavaScriptFromString:@"alert('hhhhhhhh')"];
});这样也不行,我是实在 没辙了,还求你给条明路,爱你,么么哒❤️
[appFrame stringByEvaluatingJavaScriptFromString:@"alert('hhhhhhhh')"];
});
本地是可以 调用Js代码,但是直接在主线程就死锁了,界面上就弹出了一个alert框,点都点不动,本地调用Js我就简单alert("xxxx")都不行,这是什么问题,能给个解决问题方向吗,感激不尽,谢谢,项目中需要调用Js,我这第一步都不行,很急,🙏🙏🙏