前言
APP在運(yùn)營(yíng)過(guò)程當(dāng)中,很多時(shí)候需要做一些推廣活動(dòng)類的h5,這類h5會(huì)涉及跳轉(zhuǎn)到原生的某個(gè)頁(yè)面或者需要使用某個(gè)原生已經(jīng)存在的功能。針對(duì)這種情況,很多人會(huì)說(shuō)需要先前埋點(diǎn)一些特定的交互到webview上,但有時(shí)候計(jì)劃趕不上計(jì)劃,推廣活動(dòng)有很多中類型,誰(shuí)也很難詳盡列舉需要埋點(diǎn)的交互,本著偷懶省事的原則,自己曾經(jīng)設(shè)計(jì)了一套使用(WebViewJavascriptBridge + Runtime)解決方案,整理出來(lái)僅供后來(lái)者參考。
預(yù)備知識(shí)
- WebViewJavascriptBridge交互認(rèn)識(shí)
- Runtime基礎(chǔ)理解
方案設(shè)計(jì)

實(shí)現(xiàn)過(guò)程
- webview預(yù)留監(jiān)聽(tīng)事件 clientDefineAction ,該事件可寫入自定義的webview基類當(dāng)中
- h5在需要交互的地方,使用WebViewJavascriptBridge傳入特定data,格式如下:
bridge.callHandler('clientDefineAction', {'type':'1','controll':'XXViewController','params':'{'xx':'xx'}'}, function(response) {});
data格式說(shuō)明:
type:int 0或1 ,調(diào)用類型, 0:方法,1:頁(yè)面
controll:string類型,需要調(diào)用的方法或者跳轉(zhuǎn)的頁(yè)面,需要ios跟android分別給到配置人員
params:string類型,客戶端需要用的參數(shù),需要ios跟android分別給到配置人員
對(duì)于controll需要傳入的參數(shù)是實(shí)體類的情況,params以以下格式返回:
'params':{'xx':'xx','entity':{'UserEntity_userInfo':{'birthday':'1988-09-09','name':'hahaha'}}
params 中key為 entity 時(shí)表明解析的json為實(shí)體類,實(shí)體類為UserEntity,在controll中的參數(shù)名稱為userInfo。
可看ViewController對(duì)比理解:

- 解析data之后,調(diào)用Runtime解析器進(jìn)行處理。
核心處理邏輯代碼
ps: 涉及公司業(yè)務(wù),已去掉部分代碼
#pragma mark ------ 監(jiān)聽(tīng)預(yù)留事件
- (void)clientDefineAction:(NSDictionary *)dict
{
NSInteger type = [StringHelper formatterIntValue:[dict objectForKey:@"type"]];
NSString *controllName = [dict objectForKey:@"controll"];
NSDictionary *params = [dict objectForKey:@"params"];
if (type == 0) {
//調(diào)方法
SEL methodsel = NSSelectorFromString([NSString stringWithFormat:@"%@:",controllName]);
if ([self.methodHandle respondsToSelector:methodsel]) {
//methodHandle為常用方法(微信分享、支付等)集合,需要自己整理
}
}else{
//調(diào)頁(yè)面
NSString *class = [controllName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([class isEqualToString:@"tab1"]) {
//跳轉(zhuǎn)tab1
return;
}
if ([class isEqualToString:@"tab2"]) {
//跳轉(zhuǎn)tab2
return;
}
if ([class isEqualToString:@"tab3"]) {
//跳轉(zhuǎn)tab3
return;
}
if ([class isEqualToString:@"tab4"]) {
//跳轉(zhuǎn)tab4
return;
}
[self findRightRoutedWithClass:class Params:params];
}
}
runtime解析器
- (void)findRightRoutedWithClass:(NSString *)class Params:(NSDictionary *)params
{
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
// 根據(jù)字符串返回類
Class rClass = objc_getClass(className);
if (!rClass){
return;
}
// 創(chuàng)建對(duì)象
id instance = [[rClass alloc] init];
WS(weakSelf);
if (params) {
// 對(duì)該對(duì)象賦值屬性
NSDictionary * propertys = params;
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([key isEqualToString:@"entity"]) {
//對(duì)象類型屬性
NSDictionary *entitys = [propertys objectForKey:key];
[entitys enumerateKeysAndObjectsUsingBlock:^(NSString *ckey, id entityObj, BOOL *s) {
NSArray *entityParamArray = [ckey componentsSeparatedByString:@"_"];
if (entityParamArray.count > 0) {
const char *className = [entityParamArray[0] cStringUsingEncoding:NSASCIIStringEncoding];
Class entity = objc_getClass(className);
entity = [entity objectWithKeyValues:entityObj];//這里使用MJExtension解析對(duì)象
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
{
unsigned int outCount, i;
// 獲取對(duì)象里的屬性列表
objc_property_t * properties = class_copyPropertyList([instance class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 屬性名轉(zhuǎn)成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判斷該屬性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
//這里處理UIViewController特定屬性
if ([verifyPropertyName isEqualToString:@"title"]) {
return YES;
}
return NO;
}
h5 關(guān)鍵js:
<script>
$(function(){
new FastClick(document.body);
setupWebViewJavascriptBridge(function(bridge) {
//跳轉(zhuǎn)
$(".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':'搶購(gòu)價(jià):¥0.10'}}, function(response) {});
});
});
});
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0);
}
</script>
最后
demo這里就不給出來(lái)了,等整理下一篇(短鏈跳轉(zhuǎn))再給,>_<//