iOS端接入
EMAS提供WindVane供您集成應(yīng)用,以實(shí)現(xiàn)通過(guò)WindVane集成H5應(yīng)用。本文介紹如何在iOS端集成WindVane。
接入SDK
在podfile文件中指定倉(cāng)庫(kù),添加依賴(lài),并執(zhí)行
pod install
或者pod update
命令,獲取SDK到項(xiàng)目中。source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/aliyun/aliyun-specs.git' pod 'WindVane', '11.2.2.1' pod 'ZipArchive', '1.4.0' pod 'NetworkSDK', '10.0.4.6'
添加系統(tǒng)庫(kù)依賴(lài)
在工程項(xiàng)目中(Build Phases -> Link Binary With Libraries)添加以下庫(kù)依賴(lài)。
AssetsLibrary.framework AVFoundation.framework MobileCoreServices.framework AudioToolbox.framework Photos.framework WebKit.framework
按照如下配置,初始化SDK。
#import <WindVane/WindVane.h> @interface EMASWindVaneConfig : NSObject + (void)setUpWindVanePlugin; @end @implementation EMASWindVaneConfig + (void)setUpWindVanePlugin { // 設(shè)置APPKey, 如果使用了安全黑匣子, 就會(huì)使用安全黑匣子的key // [WVUserConfig setAppKey:@"Your appKey"secrect:@"Your secretKey"]; [WVUserConfig setAppKey:@"Your appKey"]; // 設(shè)置環(huán)境 // [WVUserConfigsetEnvironment:WVEnvironmentDaily]; [WVUserConfig setEnvironment:WVEnvironmentRelease]; // 設(shè)置TTID // [WVUserConfig setTTid:@"windvane@****"]; // 設(shè)置UA [WVUserConfig setAppUA:[NSString stringWithFormat:@"TBIOS"]]; // 設(shè)置 App 名稱(chēng),會(huì)在 UserAgent 中帶上,請(qǐng)務(wù)必正確設(shè)置。 [WVUserConfig setAppName:@"EMASDemo"]; // 設(shè)置App版本 NSDictionary *infoDictionary =[[NSBundle mainBundle] infoDictionary]; [WVUserConfig setAppVersion:[infoDictionary objectForKey:@"CFBundleShortVersionString"]]; // WKWebView 支持 NSURLProtocol [WVURLProtocolService setSupportWKURLProtocol:YES]; #ifdef DEBUG [WVUserConfig setDebugMode:YES]; // 打開(kāi) WindVane 的 Log [WVUserConfig openWindVaneLog]; [WVUserConfig setLogLevel:WVLogLevelVerbose]; [WVBasic setJSLogLevel:WVLogLevelVerbose]; #endif // 初始化WindVane各模塊 [WVBasic setup]; [WVAPI setup]; [WVMonitor startMonitoring]; } @end
在
AppDelegate.m
文件的application:didFinishLaunchingWithOptions
方法中,調(diào)用以下代碼,初始化SDK。- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [EMASWindVaneConfig setUpWindVanePlugin]; }
使用WVWKWebView
建議在iOS 9或更高版本再使用WKWebView,iOS 8及之前版本存在較多缺陷。
WVWKWebView使用的是iOS 9.0新加入的WKWebView,使用WebKit內(nèi)核進(jìn)行渲染,具有更高的渲染效率和JS執(zhí)行效率,也支持更多的HTML5特性。有需要的Native業(yè)務(wù)方可以根據(jù)自己的需要,使用WVWKWebView來(lái)展示頁(yè)面。
WindVane iOS版本中,全面優(yōu)化了對(duì)WVWKWebView的支持,通過(guò) [WVURLProtocolServicesetSupportWKURLProtocol:YES]
主動(dòng)開(kāi)啟NSURLProtocol的攔截功能后,可以正常使用預(yù)加載功能,持久化Cookie(設(shè)置有expires或max-age)也可以正常同步,但POST請(qǐng)求會(huì)丟失body,非持久化 Cookie仍是不能同步的。目前僅有XMLHTTPRequest
同步POST請(qǐng)求Blob和FormData不支持。
監(jiān)聽(tīng)頁(yè)面信息
WindVane提供了頁(yè)面的基礎(chǔ)信息。通過(guò)KVO監(jiān)聽(tīng)WebView title屬性和estimatedProgress屬性的變化,就可以及時(shí)獲取頁(yè)面標(biāo)題和加載進(jìn)度的改變,然后客戶(hù)端自行決定如何顯示在界面中。
// 添加 KVO 監(jiān)聽(tīng),務(wù)必在 WebView 銷(xiāo)毀前移除監(jiān)聽(tīng)。
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNewcontext:nil];
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNewcontext:nil];
// 處理 KVO 監(jiān)聽(tīng)。
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {
[super observeValueForKeyPath:keyPathofObject:object change:change context:context];
if ([keyPathisEqual:@"title"]) {
// change[NSKeyValueChangeNewKey] 就是新的頁(yè)面標(biāo)題。
} else if ([keyPath isEqual:@"estimatedProgress"]){
// [change[NSKeyValueChangeNewKey]doubleValue] 就是新的加載進(jìn)度,范圍是 0.0~1.0。
// 注意加載進(jìn)度可能在非主線(xiàn)程觸發(fā),如果有 UI 操作注意切換線(xiàn)程。
}
}
- (void)dealloc {
// 移除KVO監(jiān)聽(tīng),這里只是一個(gè)示例,根據(jù)實(shí)際情況不一定要在dealloc中做。
[_webView removeObserver:selfforKeyPath:@"title"];
[_webView removeObserver:selfforKeyPath:@"estimatedProgress"];
}
接入灰度SDK
接入灰度SDK。
pod 'DynamicConfiguration', '~> 11.0.0' pod 'DynamicConfigurationAdaptor', '~> 1.0.0'
初始化灰度SDK。
// 灰度初始化 - (void)initDyConfig { NSString *appKey = @"xxxxx"; NSString *logicIdentifier = [NSString stringWithFormat:@"%@@%@",appKey, [self isDeviceIphone] ? @"iphoneos" : @"iPad"]; [[DynamicConfigurationAdaptorManager sharedInstance] setUpWithMaxUpateTimesPerDay:10 AndIdentifier:logicIdentifier AndAppkey:appKey]; }
接入ZCache SDK
1.接入ZCache SDK。
pod 'ZCache', '~> 11.1.0.0'
2.初始化ZCache SDK。
- (void)initZCache
{
NSString *appKey = @"xxx";
NSString *appSecret = @"xxx";
NSString *version = @"xxx";
[ZCache setupWithAppKey:appKey appSecret:appSecret appVersion:version];
}
(可選)使用JSBridge
JSBridge的Native方法提供方,要像提供普通方法一樣提供JSBridge,要求必須滿(mǎn)足以下基本約束:
方法調(diào)用后,必須返回,任何情況下都要調(diào)用callback以將結(jié)果返回給JS;可以根據(jù)需要返回不同的數(shù)據(jù)。
方法只能有一個(gè)返回值,也就是說(shuō)callback只允許調(diào)用一次,如果有多個(gè)數(shù)據(jù)需要返回給H5,Android請(qǐng)使用標(biāo)準(zhǔn)事件機(jī)制,iOS請(qǐng)使用
JSBridge context的dispatchEvent
方法。注意做好資源的釋放工作,尤其iOS不允許對(duì)View或ViewController有強(qiáng)引用。
注冊(cè)JSBridge。
EMAS提供了兩種注冊(cè)方案:動(dòng)態(tài)注冊(cè)和靜態(tài)注冊(cè)。
動(dòng)態(tài)注冊(cè):無(wú)需主動(dòng)注冊(cè)給WindVane,使用簡(jiǎn)單方便。但是要求類(lèi)名與JSBridge調(diào)用時(shí)的ClassName相同,且目前僅限WindVane iOS。
靜態(tài)注冊(cè):需要調(diào)用WindVane的注冊(cè)方法,使用操作復(fù)雜。不要求特定的類(lèi)名,會(huì)更加靈活。
總之,只要JSBridge調(diào)用的ClassName并沒(méi)有被占用,那么總是建議使用動(dòng)態(tài)注冊(cè)的方式。
iOS動(dòng)態(tài)注冊(cè)JSBridge。
WindVane提供了新版本的用法,在
WVBridgeCallbackContext
中統(tǒng)一提供了JSBridge需要的所有功能,而且為Weex等非WebView場(chǎng)景也提供了支持。詳情請(qǐng)參見(jiàn)下文的新接口介紹。假設(shè)需要實(shí)現(xiàn)兩個(gè)JSBridge:
MyJSBridge.firstAPI
和MyJSBridge.secondAPI
,那么JSBridge的ClassName是MyJSBridge,HandlerName分別是firstAPI和secondAPI。創(chuàng)建一個(gè)MyJSBridge類(lèi)(動(dòng)態(tài)注冊(cè)要求與JSBridge調(diào)用時(shí)的ClassName相同),需要確保不會(huì)有同名類(lèi)。
令MyJSBridge類(lèi)繼承自WVDynamicHandler(引入頭文件WVDynamicHandler.h)。
實(shí)現(xiàn)firstAPI和secondAPI方法,要求方法的簽名必須為
-/+(void)handlerName:(NSDictionary *)paramswithWVBridgeContext:(id<WVBridgeCallbackContext>)context
,其中:方法可以是靜態(tài)方法,也可以是動(dòng)態(tài)方法。實(shí)例方法會(huì)直接調(diào)用,實(shí)例方法會(huì)先創(chuàng)建一個(gè)實(shí)例,然后在實(shí)例上調(diào)用。
方法名與JSBridge調(diào)用的HandlerName相同,這里為firstAPI和secondAPI。
第一個(gè)參數(shù)為NSDictionary *,是JS傳入的參數(shù)對(duì)象。
第二個(gè)參數(shù)為 id<WVBridgeCallbackContext>,是JSBridge的調(diào)用上下文。
在方法中實(shí)現(xiàn)JSBridge的邏輯,使用
[contextcallbackSuccess:RESULT]/[contextcallbackFailure:RET withResult:RESULT]
來(lái)輸出執(zhí)行成功/失敗的結(jié)果返回給JSBridge調(diào)用方。請(qǐng)保證任何情況下都會(huì)調(diào)用、且只會(huì)調(diào)用一次[context callbackXXX]
系列方法。說(shuō)明JSBridge總是在主線(xiàn)程調(diào)用,如果有耗時(shí)操作,請(qǐng)務(wù)必自行切換線(xiàn)程。
可能你提供的服務(wù)帶有多個(gè)階段輸出性,請(qǐng)使用
[contextdispatchEvent:eventName withParam:param]
將結(jié)果通過(guò)事件的方式輸出給JSBridge調(diào)用方。當(dāng)你的JSBridge邏輯執(zhí)行完畢,可以主動(dòng)調(diào)用
[contextreleaseHandler:self]
方法來(lái)主動(dòng)釋放內(nèi)存,否則就只能等到頁(yè)面銷(xiāo)毀的時(shí)候才會(huì)釋放。注意在dealloc中調(diào)用是沒(méi)有意義的,因此此時(shí)對(duì)象已經(jīng)被釋放了。
#import <Foundation/Foundation.h> #import <WindVane/WindVane.h> @interface MyJSBridge : WVDynamicHandler @end @implementation MyJSBridge // 這是一個(gè)靜態(tài)方法,直接調(diào)用。 + (void)firstAPI:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context{ // 回調(diào)中處理業(yè)務(wù)邏輯 [context callbackSuccess:nil]; } // 這是一個(gè)實(shí)例方法,會(huì)創(chuàng)建一個(gè)新實(shí)例,然后再調(diào)用。 // API實(shí)例 - (void)secondAPI:(NSDictionary *)params // 回調(diào)中處理業(yè)務(wù)邏輯 [context callbackSuccess:nil]; } @end
動(dòng)態(tài)注冊(cè)的JSBridge,要求類(lèi)名不能重復(fù)。若希望擴(kuò)展現(xiàn)有ClassName,可以使用JSBridge別名。
常見(jiàn)錯(cuò)誤
如果引入WindVane時(shí)報(bào)如下的錯(cuò)誤,那么可以在項(xiàng)目的BuildPhrase
中的Link Binary With Libraries
中添加 WebKit,并選擇Status為Optional。
[MISSING IMAGE:error message | left | 618x118, error message | left | 618x118 ]
[MISSING IMAGE:WebKit framework | left | 479x80, WebKit framework | left | 479x80 ]
如果引入WindVane時(shí)報(bào)如下錯(cuò):
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Completion handler passed to -[WVCommonWebView webView:decidePolicyForNavigationAction:decisionHandler:] was not called'
需要手動(dòng)實(shí)現(xiàn)WKNavigationDelegate的代理方法并回調(diào)decisionHandler:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
}
使用iOS WebView
WindVane在iOS平臺(tái)提供了WVWebView(基于UIWebView)和WVWKWebView(基于WKWebView)的支持,并為兩種WebView做了功能和接口的兼容。因此請(qǐng)總是使用
UIView*
作為WebView的類(lèi)型,可以兼容WVWebView和WVWKWebView的主要功能。另外,請(qǐng)使用
#import <WindVane/WindVane.h>
來(lái)引入WindVane的頭文件,無(wú)需區(qū)分WindVane整包或者分包。您在升級(jí)WindVane版本時(shí),請(qǐng)注意檢查并修改代碼的Warning,盡早修改已廢棄的方法。使用WindVane提供的ViewController
首先您可以選擇使用WindVane提供的含有
WebView的WVUIWebViewController
,它封裝了常用的WebView接口,提供了WVWebView和WVWKWebView切換、狀態(tài)欄背景兼容、工具欄、錯(cuò)誤頁(yè)、加載框和橫屏支持等功能。關(guān)于UI無(wú)關(guān)的基礎(chǔ)功能信息請(qǐng)參考
WVViewController.h
,UI相關(guān)的功能請(qǐng)參考WVUIWebViewController.h
。要在
WVUIWebViewController
中切換到WVWKWebView
,可以設(shè)置useWKWebView
屬性:WVUseWKWebViewNever
表示總是使用WVWebView
,是useWKWebView
屬性的默認(rèn)值。WVUseWKWebViewAlways
表示總是使用WVWKWebView
。WVUseWKWebViewCustomdecideIsUseWKWebView
方法的返回值決定是使用WVWKWebView
,還是WVWebView
。由于必須在初始化WebView
之前就決定是否切換到WVWKWebView
,因此WVUIWebViewController
默認(rèn)會(huì)檢查loadUrl
中是否包含_wvUseWKWebView=YES
參數(shù),包含的話(huà)就切換到WVWKWebView
。#import <WindVane/WindVane.h> WVUIWebViewController * controller =[[WVUIWebViewController alloc] init]; controller.loadUrl = @"http://m.taobao.com"; // 開(kāi)啟 JSBridge,如果需要使用 JSBridge 必須調(diào)用。 controller.openLocalService = YES; // [iOS 7 適配] 隱藏導(dǎo)航欄時(shí),為 Status Bar 添加白色背景顏色,按需要設(shè)置。 [controller supportiOS7WithoutStatusBar]; // 禁用 WebView 的長(zhǎng)按事件,按需要設(shè)置。 // controller.openWebKitLongPress = NO; [self.navigationController pushViewController:controlleranimated:YES];
WindVane功能說(shuō)明
WindVaneCore
WindVane核心庫(kù),定義了模塊間的接口和約束,以及公共工具類(lèi),所有模塊都必須依賴(lài)它。
WindVaneBridge
WindVane JSBridge庫(kù),提供了JSBridge的功能,可以在自己的WebView中或者直接脫離WebView使用。
WindVaneBasic
WindVane基礎(chǔ)WebView庫(kù),提供了WebView相關(guān)功能優(yōu)化和擴(kuò)展,包括WebView、WKWebView、ViewController、StandardEventModal模塊。
WindVaneAPI
WindVane自身提供的一些基礎(chǔ)JSBridge API。 需要添加系統(tǒng)庫(kù):CoreBluetooth.framework、AddressBookUI.framework、AddressBook.framework、MessageUI.framework、Messages.framework、ContactsUI.framework、Contacts.framework、UserNotifications.framework。
WindVaneMonitor
WindVane埋點(diǎn)支持庫(kù),提供了埋點(diǎn)的UT。
WindVaneEMASExtension
EMAS擴(kuò)展庫(kù),主要有URL攔截使用預(yù)加載配置和上報(bào)Crash,預(yù)加載可以讓前端在EMAS平臺(tái)將html、js、css 等資源打包成一個(gè)zip包,客戶(hù)端可以采用啟動(dòng)后下載或者直接預(yù)置的方式將zip包解壓到本地,使得用戶(hù)在瀏覽網(wǎng)頁(yè)的時(shí)候不需要再去下載資源,提升用戶(hù)體驗(yàn)。
自定義生命周期
如果實(shí)現(xiàn)的是實(shí)例方法,默認(rèn)情況下,每次調(diào)用都會(huì)創(chuàng)建一個(gè)實(shí)例。該實(shí)例會(huì)被強(qiáng)引用以保證不會(huì)被錯(cuò)誤釋放,直到WebView被銷(xiāo)毀時(shí)才會(huì)釋放。您可以通過(guò)以下方法,實(shí)現(xiàn)實(shí)例生命周期的自定義。
@implementation MyJSBridge
+ (WVBridgeScope)instanceScope {
return WVBridgeScopeInvocation;
}
@end
WindVane支持四種生命周期:每次調(diào)用一個(gè)實(shí)例(WVBridgeScopeInvocation),每個(gè)頁(yè)面一個(gè)實(shí)例(WVBridgeScopePage),每個(gè)View一個(gè)實(shí)例(WVBridgeScopeView)和全局唯一實(shí)例(WVBridgeScopeStatic)。
對(duì)于ScopeInvocation和ScopePage,實(shí)例的引用會(huì)持續(xù)到頁(yè)面被切換(例如WebView跳轉(zhuǎn)新頁(yè)面、回退歷史記錄等)。對(duì)于ScopeView,實(shí)例的引用會(huì)持續(xù)到View被銷(xiāo)毀。因此在確保當(dāng)前實(shí)例可以被銷(xiāo)毀時(shí),建議主動(dòng)調(diào)用 [contextreleaseHandler:self]
立刻釋放當(dāng)前實(shí)例,以節(jié)約內(nèi)存。ScopeStatic的實(shí)例,會(huì)一直存在,但也支持通過(guò) [contextreleaseHandler:self] 主動(dòng)釋放。
與View或ViewController交互
JSBridge中允許與View(包括WebView)或ViewController交互,以實(shí)現(xiàn)某些特殊邏輯。或者檢查ViewController是否具有特定的類(lèi)型,以實(shí)現(xiàn)只允許在某類(lèi)ViewController中調(diào)用的私有JSBridge。
不允許對(duì)View或ViewController持有強(qiáng)引用,防止出現(xiàn)循環(huán)引用導(dǎo)致內(nèi)存泄露。context已提供view屬性和 viewController供實(shí)例方法使用,在WebView環(huán)境下提供了webViewEnv屬性,Weex場(chǎng)景下提供了weexEnv 屬性。這些屬性會(huì)自動(dòng)設(shè)置和更新,您可以直接使用。
發(fā)送事件
在JSBridge中向容器發(fā)送事件,請(qǐng)使用[contextdispatchEvent:withParam:],會(huì)對(duì)當(dāng)前環(huán)境(WebView/Weex)做適配。
事件機(jī)制中提供的方法只能夠支持WebView環(huán)境。
復(fù)雜的JSBridge生命周期管理
對(duì)于一些持續(xù)性的JSBridge,如音頻播放、動(dòng)作感應(yīng),需要長(zhǎng)時(shí)間的發(fā)送事件。但是在用戶(hù)跳轉(zhuǎn)到其它Native 頁(yè)面、跳轉(zhuǎn)到其它H5 WindVane提供了以下生命周期回調(diào)方法,可以在JSBridge中實(shí)現(xiàn)。
@implementation MyJSBridge /** * 重置處理器 - 該方法將會(huì)在加載新頁(yè)面時(shí)調(diào)用,可以做一些清理工作。 * * @param context WVBridge 被重置時(shí)的上下文。 * @param request 要加載的新頁(yè)面。 */ -(void)resetWithContext:(id<WVBridgeContext>)contextwithNextRequest:(NSURLRequest *)request { } /** * 暫停處理器 - 該方法將會(huì)在 UIViewControllerviewWillDisappear 時(shí)調(diào)用,用于降低WVBridge API的性能消耗,例如暫停播放音樂(lè)和持續(xù)性監(jiān)聽(tīng)器等。 * 不會(huì)對(duì) WVBridgeScopeStatic 作用域的 WVBridge 調(diào)用。 * * @param context WVBridge 被暫停時(shí)的上下文。 */ -(void)pauseWithContext:(id<WVBridgeContext>)context { } /** * 恢復(fù)處理器 - 該方法將會(huì)在 UIViewControllerviewWillAppear 時(shí)調(diào)用,用于恢復(fù) WVBridge API 的性能,例如恢復(fù)播放音樂(lè)和持續(xù)性監(jiān)聽(tīng)器等。 * 不會(huì)對(duì) WVBridgeScopeStatic 作用域的 WVBridge 調(diào)用。 * * @param context WVBridge 被恢復(fù)時(shí)的上下文。 */ -(void)resumeWithContext:(id<WVBridgeContext>)context { } @end
如果JSBridge需要在切換到新頁(yè)面后自動(dòng)清理,需要實(shí)現(xiàn)
resetWithContext:withNextRequest:
;如果需要在當(dāng)前UIViewController
被其它Native界面覆蓋時(shí)暫停和恢復(fù),需要實(shí)現(xiàn)pauseWithContext:
和resumeWithContex:
。一般建議與WVBridgeScopeView同時(shí)使用。如果需要在應(yīng)用切換前后臺(tái)時(shí)做特殊工作,可以自行注冊(cè)
UIApplicationDidBecomeActiveNotification
和UIApplicationWillResignActiveNotification
這兩個(gè)通知。新接口介紹
在新版本JSBridge接口,將所有功能收攏到了統(tǒng)一的id<WVBridgeCallbackContext>中,一切功能都可以通過(guò)context來(lái)實(shí)現(xiàn)。具體包括:
獲取環(huán)境信息,基本信息有來(lái)源URL referrer,view和viewController,以及當(dāng)前的環(huán)境
envUIView<WVWebViewBasicProtocol> *WeexSDKInstance *
。發(fā)送事件,提供了
dispatchEvent:withParam:
方法。釋放實(shí)例,提供了
releaseHandler:
方法。回調(diào),提供了
callbackSuccess:
和callbackFailure:withResult:
方法,以及一系列失敗回調(diào)的快捷方法。所有JSBridge的方法參數(shù)全部統(tǒng)一為
NSDictioanry* params
和id<WVBridgeCallbackContext> context
,無(wú)論是動(dòng)態(tài)注冊(cè),還是各類(lèi)靜態(tài)注冊(cè)的方法。
支持懶加載的動(dòng)態(tài)庫(kù)
一些客戶(hù)端為了提高應(yīng)用打開(kāi)速度,對(duì)動(dòng)態(tài)庫(kù)做了懶加載處理,這樣可以將動(dòng)態(tài)庫(kù)的加載延遲到使用時(shí),避免影響應(yīng)用啟動(dòng)耗時(shí)。
如果動(dòng)態(tài)庫(kù)中提供了動(dòng)態(tài)注冊(cè)JSBridge,那么在動(dòng)態(tài)庫(kù)被加載之前,是找不到對(duì)應(yīng)的類(lèi)的,也無(wú)法正常調(diào)用。因此我們懶加載的動(dòng)態(tài)庫(kù)做了特別的支持,允許動(dòng)態(tài)庫(kù)通過(guò)plist 配置動(dòng)態(tài)JSBridge的類(lèi)名,以便WindVane在需要使用JSBridge時(shí)主動(dòng)將動(dòng)態(tài)庫(kù)加載進(jìn)來(lái)。
解決方案是在動(dòng)態(tài)庫(kù)的framework根目錄,提供一個(gè)命名為XXX_bundle.plist的文件,其中的XXX部分可以任意命名,甚至同一個(gè)framework包含多個(gè)符合此規(guī)則的plist也可以。在plist中添加名為windvane_bridge_list的數(shù)組,并在數(shù)組中添加需要配置的動(dòng)態(tài)JSBridge類(lèi)名,多個(gè)plist的配置會(huì)自動(dòng)合并。示例如下:
[MISSING IMAGE:Dynamic Framework Config | left | 600x374, Dynamic Framework Config | left |600x374 ]
請(qǐng)確保動(dòng)態(tài)庫(kù)全部位于客戶(hù)端的Frameworks目錄,如果不同動(dòng)態(tài)庫(kù)之間的映射關(guān)系存在覆蓋,動(dòng)態(tài)庫(kù)會(huì)在JSBridge被首次使用時(shí),打出日志 'A' 注冊(cè)的WVBridge 'XXX' 被 'B' 覆蓋了,此外,在WindVane的DEBUG模式下還會(huì)彈出Alert提示。
iOS靜態(tài)注冊(cè)JSBridge
WindVane iOS允許將JSBridge注冊(cè)到全局,也可以注冊(cè)到特定WebView。前者可以在任意WebView中調(diào)用,而后者只能在注冊(cè)的WebView中使用,適合需要較嚴(yán)格的權(quán)限控制的JSBridge。 但是注冊(cè)到全局的JSBridge,也可以通過(guò)對(duì)ViewController 的類(lèi)型或?qū)傩赃M(jìn)行校驗(yàn),以達(dá)到部分權(quán)限的控制,因此如無(wú)特別的必要,請(qǐng)總是將JSBridge注冊(cè)到全局,會(huì)有更少的內(nèi)存消耗。
將JSBridge注冊(cè)到全局
#import <WindVane/WindVane.h> // JSBridge 調(diào)用的 Block。 WVBridgeHandler handler = ^(NSDictionary *params, id<WVBridgeCallbackContext> context) { [context callbackSuccess:nil]; }; // 注冊(cè)JSBridge到全局,特別注意這里的JSBridge 名稱(chēng)合并寫(xiě)作@"className.handlerName" 格式。 WVBridgeRegisterHandler(@"className.handlerName",handler);
將JSBridge注冊(cè)到特定WebView
這樣注冊(cè)的JSBridge,只能在注冊(cè)到的WebView中使用className和handlerName調(diào)用。
#import <WindVane/WindVane.h> // JSBridge 調(diào)用的 Block WVBridgeHandler handler = ^(NSDictionary *params, id<WVBridgeCallbackContext> context) { [context callbackSuccess:nil]; }; // 注冊(cè) JSBridge 到指定 WebView,特別注意這里的 JSBridge 名稱(chēng)合并寫(xiě)作 @"className.handlerName" 格式。 [webview registerHandler:@"className.handlerName" withBlock:handler]; // 注冊(cè) JSBridge到指定ViewController中的WebView,特別注意這里的JSBridge名稱(chēng)合并寫(xiě)作 @"className.handlerName" 格式。 [wvViewController registerHandler:@"className.handlerName" withBlock:handler];
靜態(tài)注冊(cè)的JSBridge,不支持復(fù)雜的生命周期管理。如果需要實(shí)現(xiàn)復(fù)雜的JSBridge,請(qǐng)動(dòng)態(tài)注冊(cè)JSBridge。
靜態(tài)注冊(cè)的JSBridge,若className和handlerName都相同,那么后注冊(cè)的會(huì)覆蓋先注冊(cè)的。同時(shí)也會(huì)覆蓋同名的動(dòng)態(tài)注冊(cè)方法。
JSBridge別名
WindVane提供了JSBridge的別名服務(wù),可以用來(lái)對(duì)現(xiàn)有ClassName的JSBridge進(jìn)行擴(kuò)展或覆蓋,也可以對(duì)JSBridge的升級(jí)、更新、改名和沖突提供解決方案。
允許將原始的JSBridge className.handlerName
,映射到新的別名aliasClassName.aliasHandlerName
,您既可以使用舊的className.handlerName
來(lái)訪(fǎng)問(wèn),也可以使用新的aliasClassName.aliasHandlerName
來(lái)訪(fǎng)問(wèn)。
WindVane的別名服務(wù),是首先將別名解析為原始名稱(chēng),然后使用原始名稱(chēng)來(lái)查找 JSBridge;不要將別名指向其它別名,可能導(dǎo)致無(wú)法正確找到JSBridge。
iOS注冊(cè)全局別名:全局別名必須與全局JSBridge配合使用。
#import <WindVane/WindVane.h>
// 注冊(cè) JSBridge 別名到全局。
NSDictionary * alias = @{
@"類(lèi)別名.方法別名": @"類(lèi)名.方法名",
@"aliasClassName.aliasHandlerName":@"className.handlerName"
};
WVBridgeRegisterAlias(alias);
JSBridge全局鑒權(quán)
WindVane提供了JSBridge的全局鑒權(quán)接口,供實(shí)現(xiàn)App級(jí)別的JSBridge API鑒權(quán)。該鑒權(quán)層切入到JSBridge的調(diào)用邏輯中,可以由客戶(hù)端統(tǒng)一自定義鑒權(quán)邏輯。
對(duì)于單個(gè)JSBridge的鑒權(quán),請(qǐng)?jiān)谠揓SBridge的實(shí)現(xiàn)中自行實(shí)現(xiàn)。
#import <WindVane/WindVane.h>
// 實(shí)現(xiàn) JSBridge 鑒權(quán)方法。
@interface MyJSBridgeCheckerHandler : NSObject<WVBridgeCheckerProtocol>
@end
@implementation MyJSBridgeCheckerHandler
/**
檢查指定 WVBridge 是否具有執(zhí)行權(quán)限。
* 總是在后臺(tái)線(xiàn)程調(diào)用。
*
* @param apiName WVBridge 的名稱(chēng),格式為 "類(lèi)名.方法名"。
* @param paramsWVBridge 的調(diào)用參數(shù)。
* @param context WVBridge 的執(zhí)行上下文。
*
* @return 權(quán)限檢查結(jié)果。
*/
- (WVBridgePermission*)checkPermission:(NSString *)apiName withParams:(NSDictionary *)paramswithContext:(id<WVBridgeContext>)context {
return [WVBridgePermissionpermissionNotSure];
}
@end
// 注冊(cè)鑒權(quán)處理器。
[WVBridgeChecker registerChecker:[[MyJSBridgeCheckerHandler alloc]init]];
在Native中使用JSBridge
WindVane iOS中,支持在Native中調(diào)用JSBridge。現(xiàn)在提供有兩種用法:
簡(jiǎn)單用法
這里適合一些簡(jiǎn)單場(chǎng)景下,Native調(diào)用與UI無(wú)關(guān)的JSBridge。直接調(diào)用
[WVBridge callHandler:withParams:withCallback:]
方法,傳入以下參數(shù):name: 要執(zhí)行的JSBridge名稱(chēng),格式為@"類(lèi)名.方法名"。
params: 要執(zhí)行的JSBridge參數(shù),與JS使用方式保持一致 ,對(duì)象用NSDictionary代替,數(shù)組用NSArray代替)。
callback: 此次JSBridge調(diào)用的回調(diào),可能在任意線(xiàn)程調(diào)用。
復(fù)雜用法
這里適合Native調(diào)用全功能的JSBridge,可以用于在自己的容器中完整接入JSBridge。
實(shí)現(xiàn)WVBridgeDelegate協(xié)議,供JSBridge回調(diào)數(shù)據(jù)給Native。
實(shí)例化自己的WVBridge實(shí)例,傳入env和上面實(shí)現(xiàn)的delegate。
設(shè)置 WVBridge 實(shí)例的view和viewController屬性,為JSBridgeAPI提供環(huán)境信息。其它屬性可以根據(jù)需要設(shè)置:
opageId:當(dāng)前頁(yè)面的ID,變化后前一頁(yè)面未調(diào)用的JSBridge會(huì)全部失效,避免影響到當(dāng)前頁(yè)面。
oopenPermissionCheck:?jiǎn)⒂冒酌麊涡r?yàn),只允許符合WindVane domain 配置中的Ali域名白名單的 URL 調(diào)用JSBridge,其它URL的調(diào)用都會(huì)被拒絕。
調(diào)用
callHandlerWithRequest:
、callHandlerWithURL:
或callHandler:withParams:withReqId:
方法之一,就會(huì)調(diào)用實(shí)際的JSBridge,JSBridge的回調(diào)、事件都會(huì)通過(guò)delegate返回回來(lái)。
標(biāo)準(zhǔn)事件機(jī)制
WindVane的標(biāo)準(zhǔn)事件機(jī)制,提供了Native和H5之間互發(fā)事件的標(biāo)準(zhǔn)通訊接口,便于各業(yè)務(wù)場(chǎng)景下的基于統(tǒng)一的事件模型來(lái)通訊。
Native向H5發(fā)送事件
WindVane提供了WVStandardEventCenter 類(lèi),供Native向H5發(fā)送事件:
在事件參數(shù)中,WebView是要發(fā)送事件的目標(biāo)WebView,eventName是事件的名稱(chēng)(字符串),eventData是事件的數(shù)據(jù)(iOS是
NSDictionary*
,Android是一個(gè)JSON Object
格式的String)。在iOS平臺(tái)的JSBridge中,不建議使用這里的方法來(lái)發(fā)送事件,而是使用JSBridge context的dispatchEvent方法來(lái)發(fā)送事件,對(duì)多種容器(WebView/Weex)提供了兼容。#import <WindVane-Basic/WVStandardEventCenter.h> // 向指定 WebView 發(fā)送事件。 [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData withWebView:webView]; // 向所有 WebView 發(fā)送事件,請(qǐng)謹(jǐn)慎使用。 [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData];
H5用法:
document.addEventListener('eventName', function(data) { // 這里要注意,Native 傳遞過(guò)來(lái)的事件參數(shù)是在data的param屬性中。 alert(data.param); }, false);
H5向Native發(fā)送事件
H5用法:使用JSBridge來(lái)發(fā)送事件,注意引入windvane.js,詳情可以參考如下示例。
WVStandardEventCenter.postNotificationToNative: var params = { event: 'eventName', param: { // 事件要傳遞的數(shù)據(jù)。 } }; window.WindVane.call('WVStandardEventCenter','postNotificationToNative',params, function(e) { alert('success'); }, function(e) { alert('failure: ' + JSON.stringify(e)); });
iOS用法:使用標(biāo)準(zhǔn)的消息中心監(jiān)聽(tīng)事件。
[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(myEventListener) name:eventName object:nil] - (void)myEventListener:(NSNotification*)notification { // 事件參數(shù)可以從 notification.userInfo 中獲取。 }