前言
APP在运营过程当中,很多时候需要做一些推广活动类的h5,这类h5会涉及跳转到原生的某个页面或者需要使用某个原生已经存在的功能。针对这种情况,很多人会说需要先前埋点一些特定的交互到webview上,但有时候计划赶不上计划,推广活动有很多中类型,谁也很难详尽列举需要埋点的交互,本着偷懒省事的原则,自己曾经设计了一套使用(WebViewJavascriptBridge + Runtime)解决方案,整理出来仅供后来者参考。
预备知识
Runtime基础理解
方案设计
sheji.png
实现过程
webview预留监听事件 clientDefineAction ,该事件可写入自定义的webview基类当中
h5在需要交互的地方,使用WebViewJavascriptBridge传入特定data,格式如下:
bridge.callHandler('clientDefineAction', {'type':'1','controll':'XXViewController','params':'{'xx':'xx'}'},function(response){});
data格式说明:
type:int 0或1 ,调用类型, 0:方法,1:页面
controll:string类型,需要调用的方法或者跳转的页面,需要ios跟android分别给到配置人员
params:string类型,客户端需要用的参数,需要ios跟android分别给到配置人员
对于controll需要传入的参数是实体类的情况,params以以下格式返回:
'params':{'xx':'xx','entity':{'UserEntity_userInfo':{'birthday':'1988-09-09','name':'hahaha'}}
params 中key为 entity 时表明解析的json为实体类,实体类为UserEntity,在controll中的参数名称为userInfo。
可看ViewController对比理解:
params_entity.png
解析data之后,调用Runtime解析器进行处理。
核心处理逻辑代码
ps: 涉及公司业务,已去掉部分代码
#pragma mark ------ 监听预留事件- (void)clientDefineAction:(NSDictionary*)dict{NSIntegertype = [StringHelper formatterIntValue:[dict objectForKey:@"type"]];NSString*controllName = [dict objectForKey:@"controll"];NSDictionary*params = [dict objectForKey:@"params"];if(type ==0) {//调方法SEL methodsel =NSSelectorFromString([NSStringstringWithFormat:@"%@:",controllName]);if([self.methodHandle respondsToSelector:methodsel]) {//methodHandle为常用方法(微信分享、支付等)集合,需要自己整理} }else{//调页面NSString*class= [controllName stringByTrimmingCharactersInSet:[NSCharacterSetwhitespaceCharacterSet]];if([classisEqualToString:@"tab1"]) {//跳转tab1return; }if([classisEqualToString:@"tab2"]) {//跳转tab2return; }if([classisEqualToString:@"tab3"]) {//跳转tab3return; }if([classisEqualToString:@"tab4"]) {//跳转tab4return; } [selffindRightRoutedWithClass:classParams:params]; }}
runtime解析器
- (void)findRightRoutedWithClass:(NSString*)classParams:(NSDictionary*)params{constchar*className = [classcStringUsingEncoding:NSASCIIStringEncoding];// 从一个字串返回一个类Class newClass = objc_getClass(className);if(!newClass){// 创建一个类Class superClass = [NSObjectclass]; newClass = objc_allocateClassPair(superClass, className,0);// 注册你创建的这个类objc_registerClassPair(newClass); }// 创建对象idinstance = [[newClass alloc] init]; WS(weakSelf);if(params) {// 对该对象赋值属性NSDictionary* propertys = params; [propertys enumerateKeysAndObjectsUsingBlock:^(idkey,idobj,BOOL*stop) {if([key isEqualToString:@"entity"]) {//对象类型属性NSDictionary*entitys = [propertys objectForKey:key]; [entitys enumerateKeysAndObjectsUsingBlock:^(NSString*ckey,identityObj,BOOL*s) {NSArray*entityParamArray = [ckey componentsSeparatedByString:@"_"];if(entityParamArray.count >0) {constchar*className = [entityParamArray[0] cStringUsingEncoding:NSASCIIStringEncoding]; Class entity = objc_getClass(className); entity = [entity objectWithKeyValues:entityObj];//这里使用MJExtension解析对象if([weakSelf checkIsExistPropertyWithInstance:instance verifyPropertyName:entityParamArray[1]]) {// 利用kvc赋值[instance setValue:entity forKey:entityParamArray[1]]; } } }]; }else{//普通类型属性if([weakSelf checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {// 利用kvc赋值[instance setValue:obj forKey:key]; } } }]; } [self.currentParentVc pushNormalViewController:instance];}- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString*)verifyPropertyName{unsignedintoutCount, i;// 获取对象里的属性列表objc_property_t * properties = class_copyPropertyList([instanceclass], &outCount);for(i =0; i < outCount; i++) { objc_property_t property =properties[i];// 属性名转成字符串NSString*propertyName = [[NSStringalloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];// 判断该属性是否存在if([propertyName isEqualToString:verifyPropertyName]) { free(properties);returnYES; } } free(properties);//这里处理UIViewController特定属性if([verifyPropertyName isEqualToString:@"title"]) {returnYES; }returnNO;}
h5 关键js:
$(function(){newFastClick(document.body); setupWebViewJavascriptBridge(function(bridge){//跳转$(".shareBtn").click(function(){// bridge.callHandler('clientDefineAction', {'type':'1','controll':'XxxViewController','params':{'xxx':'xxx'}}, function(response) {});// bridge.callHandler('clientDefineAction', {'type':'1','controll':'tab1'}, function(response) {});bridge.callHandler('clientDefineAction', {'type':'0','controll':'shareToQQ','params':{'shareUrl':'http://www.baidu.com','shareTitle':'奇异果子','shareDesc':'抢购价:¥0.10'}},function(response){}); }); });});functionsetupWebViewJavascriptBridge(callback){if(window.WebViewJavascriptBridge) {returncallback(WebViewJavascriptBridge); }if(window.WVJBCallbacks) {returnwindow.WVJBCallbacks.push(callback); }window.WVJBCallbacks = [callback];varWVJBIframe =document.createElement('iframe'); WVJBIframe.style.display ='none'; WVJBIframe.src ='wvjbscheme://__BRIDGE_LOADED__';document.documentElement.appendChild(WVJBIframe); setTimeout(function(){document.documentElement.removeChild(WVJBIframe) },0);}