全部产品
云市场
云游戏

iOS H5容器指南

更新时间:2019-01-14 16:16:04

接入

  • 整包获取(推荐)

整包的WindVane包含了所有功能

  1. source 'git@gitlab-ce.emas-poc.com:EMAS-iOS/emas-specs.git'
  2. pod 'WindVane' ,'1.0.0'
  • 分包获取
  1. source 'git@gitlab-ce.emas-poc.com:EMAS-iOS/emas-specs.git'
  2. pod 'WindVaneCore','1.0.0'
  3. pod 'WindVaneBridge','1.0.0'
  4. pod 'WindVaneBasic','1.0.0'
  5. pod 'WindVaneAPI','1.0.0'
  6. pod 'WindVaneMonitor','1.0.0'
  7. pod 'WindVaneEMASExtension','1.0.0'
  • 功能说明

WindVaneCore

WindVane 核心库,定义了模块间的接口和约束,以及公共工具类,所有模块都必须依赖它。

WindVaneBridge

WindVane JSBridge 库,提供了 JSBridge 的功能,可以在自己的 WebView 中或者直接脱离 WebView 使用。

WindVaneBasic

WindVane 基础 WebView 库,提供了 WebView 相关功能优化和扩展,包括 WebView、WKWebView、ViewController、StandardEventModal 模块。

WindVaneAPI

WindVane 自身提供的一些基础 JSBridge API需要添加系统库:CoreBluetooth.framework、AddressBookUI.framework、AddressBook.framework、MessageUI.framework、Messages.framework、ContactsUI.framework、Contacts.framework、UserNotifications.framework

WindVaneMonitor

WindVane 埋点支持库,提供了埋点的 UT 通道,允许将 WindVane 的埋点情况上传到EMAS平台,包括 H5 性能埋点,错误埋点,JSBridge 埋点和预加载埋点等。

WindVaneEMASExtension

EMAS扩展库,主要有url拦截使用预加载配置和上报crash,预加载可以让前端在 EMAS 平台将 html、js、css 等资源打包成一个 zip 包,客户端可以采用启动后下载或者直接预置的方式将 zip 包解压到本地,使得用户在浏览网页的时候不需要再去下载资源,提升用户体验。

  1. pod 'ZCache', '10.0.3'

初始化和配置

如下配置好对应EMAS平台的配置

  1. @interface EMASWindVaneConfig : NSObject
  2. + (void)setUpWindVanePlugin;
  3. @end
  4. @implementation EMASWindVaneConfig
  5. + (void)setUpWindVanePlugin {
  6. // 设置APPKey, 如果使用了安全黑匣子, 就会使用安全黑匣子的key
  7. // [WVUserConfig setAppKey:@"4272" secrect:@"0ebbcccfee18d7ad1aebc5b135ffa906"];
  8. [WVUserConfig setAppKey:[[EMASService shareInstance] appkey]];
  9. // 设置是否使用安全黑匣子
  10. [WVUserConfig useSafeSecert:NO];
  11. // 设置环境
  12. // [WVUserConfig setEnvironment:WVEnvironmentDaily];
  13. [WVUserConfig setEnvironment:WVEnvironmentRelease];
  14. // 设置TTID
  15. //[WVUserConfig setTTid:@"windvane@demo2"];
  16. // 设置UA
  17. [WVUserConfig setAppUA:[NSString stringWithFormat:@"TBIOS"]];
  18. // 设置 App 名称,会在 UserAgent 中带上,请务必正确设置。
  19. [WVUserConfig setAppName:@"EMASDemo"];
  20. // 设置app版本
  21. NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
  22. [WVUserConfig setAppVersion: [infoDictionary objectForKey:@"CFBundleShortVersionString"]];
  23. // WKWebView 支持 NSURLProtocol
  24. [WVURLProtocolService setSupportWKURLProtocol:YES];
  25. #ifdef DEBUG
  26. [WVBasicUserConfig setDebugMode:YES];
  27. // 打开 WindVane 的 Log
  28. [WVBasicUserConfig openWindVaneLog];
  29. [WVUserConfig setLogLevel:WVLogLevelVerbose];
  30. [WVBasic setJSLogLevel:WVLogLevelVerbose];
  31. #endif
  32. // 初始化WindVane各模块
  33. //[WVTBExtension setup];
  34. [WVBasic setup];
  35. [WVAPI setup];
  36. [WVMonitor startMonitoring];
  37. [EMASWVExtensionConfig setup];
  38. }
  39. @end

在应用程序初始化时

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  2. [self initZCacheConfig];
  3. [EMASWindVaneConfig setUpWindVanePlugin];
  4. }

另外:ZCache初始化参考基础SDK初始化部分

常见错误

如果引入 WindVane 时报如下的错误,那么可以在项目的 Build Phrase 中的 Link Binary With Libraries 中添加 WebKit,并选择 Status 为 Optional。

error message | left | 618x118

WebKit framework | left | 479x80

使用iOS WebView

WindVane 在 iOS 平台提供了 WVWebView(基于 UIWebView) 和 WVWKWebView(基于 WKWebView) 的支持,并为两种 WebView 做了功能和接口的兼容。因此请总是使用 UIView * 作为 WebView 的类型,可以兼容 WVWebView 和 WVWKWebView 的主要功能。

另外,请总是使用 #import 来引入 WindVane 的头文件,无需区分 WindVane 整包或者分包。业务方在升级 WindVane 版本时,请注意检查并修改代码的 Warning,尽早修改已废弃的方法。

使用 WindVane 提供的 ViewController

首先您可以选择使用 WindVane 提供的含有 WebView 的 WVUIWebViewController,它封装了常用的 WebView 接口,提供了 WVWebView 和 WVWKWebView 切换、状态栏背景兼容、工具栏、错误页、加载框和横屏支持等功能。

UI 无关的基础功能请参考 WVViewController.h,UI 相关的功能请参考 WVUIWebViewController.h。

要在 WVUIWebViewController 中切换到 WVWKWebView,可以设置 useWKWebView 属性:

  • WVUseWKWebViewNever 表示总是使用 WVWebView,是 useWKWebView 属性的默认值。
  • WVUseWKWebViewAlways 表示总是使用 WVWKWebView
  • WVUseWKWebViewCustom 表示在创建 WebView 时,根据 decideIsUseWKWebView 方法的返回值决定是使用 WVWKWebView,还是 WVWebView。由于必须在初始化 WebView 之前就决定是否切换到 WVWKWebView,因此 WVUIWebViewController 默认会检查 loadUrl 中是否包含 _wvUseWKWebView=YES 参数,包含的话就切换到 WVWKWebView
  1. #import <WindVane/WindVane.h>
  2. WVUIWebViewController * controller = [[WVUIWebViewController alloc] init];
  3. controller.loadUrl = @"http://m.taobao.com";
  4. // 开启 JSBridge,如果需要使用 JSBridge 必须调用。
  5. controller.openLocalService = YES;
  6. // [iOS 7 适配] 隐藏导航栏时,为 Status Bar 添加白色背景颜色,按需要设置。
  7. [controller supportiOS7WithoutStatusBar];
  8. // 禁用 WebView 的长按事件,按需要设置。
  9. // controller.openWebKitLongPress = NO;
  10. [self.navigationController pushViewController:controller animated:YES];

默认的 WebAppInterface 实现

WindVane iOS 中内置了常用 WebAppInterface 接口的默认实现,具体包含:

  • setNaviBarHidden:隐藏或显示导航栏,隐藏导航栏时总是同时隐藏状态栏
  • setCustomPageTitle:设置导航栏标题
  • setNaviBarStyle:设置导航栏前景色和背景色
  • setStatusBarStyle:设置状态栏前景色
  • setNaviBarRightItem:设置导航栏右侧按钮
  • clearNaviBarRightItem:清除导航栏右侧按钮
  • forbidPanGesture:禁用手势返回
  • pop:回退当前 ViewController,优先回退历史记录

    对应的 Native 接口,全部列出在了 WVWebViewControllerUIProtocol.h 中,根据需要,可以自行重写 WVUIWebViewController 提供的默认实现(注意在 ViewController appear/disappear 时要正确重置样式)。也可以只重写 WVUIWebViewController+CustomizeUI.h 中列出的更新接口,由 WindVane 在合适的时机完成样式的重置。WVUIWebViewController.allowsControlNavigationBar 的默认值为 NO,表示禁用以上 WebAppInterface 接口。业务方需要自行将该属性设置为 YES,以启用该功能。

使用 WebView

如果您不希望直接使用 WVUIWebViewController,可以直接创建 WVWebViewWVWKWebView,但建议使用 UIView<WVWebViewProtocol> * 作为 WebView 的类型,便于动态切换 WebView 类型。特别是请总是使用 evaluateJavaScript:completionHandler: 异步接口来执行 JS

WebView 本身提供了 JSBridge、下拉刷新、上拉更多、长按保存图片、加载进度和页面性能监测等功能,具体请参考 WVWebViewProtocol.h

WVWebViewProtocol 抹平了绝大部分 UIWebView 和 WKWebView 的差异,目前仍有以下细节区别:

  1. allowsInlineMediaPlaybackmediaPlaybackRequiresUserAction 在 WKWebView 初始化后再设置无法生效,必须在 initWithFrame:configuration: 的 configuration 中设置。
  2. evaluateJavaScript:completionHandler: 的返回值序列化方式略有不同。如果方法的返回值不是字符串,UIWebView 会转换为 NSString *,而 WKWebView 会序列化为与 JavaScript 类型对应的 Native 类型。建议总是使用字符串作为 JavaScrip 返回值。使用时必须正确设置 WebView 的 sourceViewController 属性,并在 ViewController 的 viewWillAppear 时调用 bindingWebViewService 方法,在 viewWillDisappear 时调用 releaseWebViewService 方法。
  1. // 创建 WebView 时调用:
  2. _webView.sourceViewController = self;
  3. // WebView 的 JSBridge 功能默认是关闭的,需要主动开启。
  4. _webView.openLocalService = YES;
  5. (void)viewWillDisappear:(BOOL)animated {
  6. [super viewWillDisappear:animated];
  7. [_webView releaseWebViewService];
  8. }
  9. (void)viewWillAppear:(BOOL)animated {
  10. [super viewWillAppear:animated];
  11. [_webView bindingWebViewService];
  12. }
  13. (void)dealloc {
  14. // dealloc 中要正确释放 WebView。
  15. _webView = nil;
  16. }

使用 WKWKWebView

建议在 iOS9 或更高版本再使用 WKWebView,iOS8 中存在较多 bug。

WVWKWebView 使用的是 iOS 8.0 新加入的 WKWebView,使用 WebKit 内核进行渲染,具有更高的渲染效率和 JS 执行效率,也支持更多的 HTML5 特性。有需要的 Native 业务方可以根据自己的需要,使用 WVWKWebView 来展示页面。

WindVane iOS 版本中,全面优化了对 WVWKWebView 的支持,通过 [WVURLProtocolService setSupportWKURLProtocol:YES] 主动开启 NSURLProtocol 的拦截功能后,可以正常使用预加载功能,持久化 Cookie(设置有 expires 或 max-age)也可以正常同步,但 POST 请求会丢失 body,非持久化 Cookie 仍是不能同步的。目前仅有 XMLHTTPRequest 同步 POST 请求 Blob 和 FormData 不支持。

监听页面信息

WindVane 提供了页面的基础信息。通过 KVO 监听 WebView title 属性和 estimatedProgress 属性的变化,就可以及时获取页面标题和加载进度的改变,然后客户端自行决定如何显示在界面中。

  1. // 添加 KVO 监听,务必在 WebView 销毁前移除监听。
  2. [_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
  3. [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
  4. // 处理 KVO 监听。
  5. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  6. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  7. if ([keyPath isEqual:@"title"]) {
  8. // change[NSKeyValueChangeNewKey] 就是新的页面标题。
  9. } else if ([keyPath isEqual:@"estimatedProgress"]) {
  10. // [change[NSKeyValueChangeNewKey] doubleValue] 就是新的加载进度,范围是 0.0~1.0。
  11. // 注意加载进度可能在非主线程触发,如果有 UI 操作注意切换线程。
  12. }
  13. }
  14. - (void)dealloc {
  15. // 移除 KVO 监听,这里只是一个示例,根据实际情况不一定要在 dealloc 中做。
  16. [_webView removeObserver:self forKeyPath:@"title"];
  17. [_webView removeObserver:self forKeyPath:@"estimatedProgress"];
  18. }

使用JSBridge

简介

WindVane JSBridge 提供了 JS 与 Native 之间的通信链路,基于这个通信基础,Native 可以暴露出一些服务 API 提供给 JS 调用,并通过方法返回值或事件将数据返回给 JS。我们提供了相当多的实用内置的JS API,相关的API参考文档如下:https://help.aliyun.com/document_detail/102685.html

JS 端会使用 ClassName.HandlerName 来调用 JSBridge,WindVane 会根据 ClassName 与 HandlerName,来找到对应的 Native 方法。

JSBridge 的 Native 方法提供方,要像提供普通方法一样提供 JSBridge,要求必须满足以下基本约束

  1. 方法调用后,必须返回,任何情况下都要调用 callback 以将结果返回给 JS;可以根据需要返回不同的数据。
  2. 方法只能有一个返回值,也就是说 callback 只允许调用一次,如果有多个数据需要返回给 H5,Android 请使用标准事件机制,iOS 请使用 JSBridge context 的 dispatchEvent 方法。
  3. 注意做好资源的释放工作,尤其 iOS 不允许对 View 或 ViewController 有强引用。

注册JSBridge

JSBridge 需要注册给 WindVane,才能够让 JS 正确的调用到对应的 Native 方法。我们提供了两种注册方案——动态注册和静态注册:

  • 动态注册无需主动注册给 WindVane,使用简单方便。但是要求类名与 JSBridge 调用时的 ClassName 相同,且目前仅限 WindVane iOS。
  • 静态注册需要调用 WindVane 的注册方法,使用略微麻烦。不要求特定的类名,会更加灵活。

总得来说,只要 JSBridge 调用的 ClassName 并没有被占用,那么总是建议使用动态注册的方式。

iOS 动态注册JSBridge

WindVane 提供了新版本的用法,在 WVBridgeCallbackContext 中统一提供了 JSBridge 需要的所有功能,而且为 Weex 等非 WebView 场景也提供了支持。详情请参见下面的新接口介绍。

假设需要实现两个 JSBridge:MyJSBridge.firstAPI 和 MyJSBridge.secondAPI,那么 JSBridge 的 ClassName 是 MyJSBridge,HandlerName 分别是 firstAPI 和 secondAPI。

  1. 创建一个 MyJSBridge 类(动态注册要求与 JSBridge 调用时的 ClassName 相同),需要确保不会有同名类。
  2. 令 MyJSBridge 类继承自 WVDynamicHandler(引入头文件 WVDynamicHandler.h)。
  3. 实现 firstAPI 和 secondAPI 方法,要求方法的签名必须为 -/+ (void)handlerName:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context 其中:
    • 方法可以是静态方法,也可以是动态方法。实例方法会直接调用,实例方法会先创建一个实例,然后在实例上调用。
    • 方法名与 JSBridge 调用的 HandlerName 相同,这里为 firstAPI 和 secondAPI。
    • 第一个参数为 NSDictionary *,是 JS 传入的参数对象。
    • 第二个参数为 id<WVBridgeCallbackContext>,是 JSBridge 的调用上下文。
  4. 在方法中实现 JSBridge 的逻辑,使用 [context callbackSuccess:RESULT]/[context callbackFailure:RET withResult:RESULT] 来输出执行成功/失败的结果返回给 JSBridge 调用方。请保证任何情况下都会调用、且只会调用一次 [context callbackXXX] 系列方法

注意:

  1. JSBridge 总是在主线程调用,如果有耗时操作,请务必自行切换线程。
  2. 可能你提供的服务带有多个阶段输出性,请使用 [context dispatchEvent:eventName withParam:param] 将结果通过事件的方式输出给 JSBridge 调用方。
  3. 当你的 JSBridge 逻辑执行完毕,可以主动调用 [context releaseHandler:self] 方法来主动释放内存,否则就只能等到页面销毁的时候才会释放。注意在 dealloc 中调用是没有意义的,因此此时对象已经被释放了。
  1. #import <Foundation/Foundation.h>
  2. #import <WindVane/WindVane.h>
  3. @interface MyJSBridge : WVDynamicHandler
  4. @end
  5. @implementation MyJSBridge
  6. // 这是一个静态方法,直接调用。
  7. + (void)firstAPI:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context {
  8. // Do something
  9. [context callbackSuccess:nil];
  10. }
  11. // 这时一个实例方法,会创建一个新实例,然后再调用。
  12. - (void)secondAPI:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context {
  13. // Do something
  14. [context callbackSuccess:nil];
  15. }
  16. @end

动态注册的 JSBridge,要求类名不能重复。若希望扩展现有 ClassName,可以使用JSBridge 别名。

自定义生命周期

如果实现的是实例方法,默认情况下,每次 JSBridge 调用都会创建一个新实例;该实例会被强引用已保证不会被错误释放,直到 WebView 被销毁时才会释放。可以实现以下方法,实现实例生命周期的自定义:

  1. @implementation MyJSBridge
  2. + (WVBridgeScope)instanceScope {
  3. return WVBridgeScopeInvocation;
  4. }
  5. @end

WindVane 支持四种生命周期:每次调用一个实例(WVBridgeScopeInvocation),每个页面一个实例(WVBridgeScopePage),每个 View 一个实例(WVBridgeScopeView)和全局唯一实例(WVBridgeScopeStatic)。

对于 ScopeInvocationScopePage,实例的引用会持续到页面被切换(例如 WebView 跳转新页面、回退历史记录等);对于 ScopeView,实例的引用会持续到 View 被销毁。因此在确保当前实例可以被销毁时,建议主动调用 [context releaseHandler:self] 立刻释放当前实例,以节约内存。ScopeStatic 的实例,会一直存在,但也支持通过 [context releaseHandler:self] 主动释放。

与View或ViewController交互

JSBridge 中允许与 View(包括 WebView,以及 Weex 的 View 等) 或 ViewController 进行交互,已实现某些特殊逻辑。或者检查 ViewController 是否具有特定的类型,已实现只允许在某类 ViewController 中调用的私有 JSBridge。

不允许对 View 或 ViewController 持有强引用,防止出现循环引用导致内存泄露。context 已提供 view 属性和 viewController 供实例方法使用,在 WebView 环境下提供了 webViewEnv 属性,Weex 场景下提供了 weexEnv 属性。这些属性会自动设置和更新,可以直接使用。

发送事件

在 JSBridge 中向容器发送事件,请使用 [context dispatchEvent:withParam:],会对当前环境(WebView/Weex)做适配。

事件机制中提供的方法只能够支持 WebView 环境。

复杂的JSBridge生命周期管理

对于一些持续性的 JSBridge,如音频播放、动作感应,需要长时间的发送事件。但是在用户跳转到其它 Native 页面、跳转到其它 H5 页面时,可能需要暂停音频播放和动作感应。WindVane 提供了以下生命周期回调方法,可以在 JSBridge 中实现:

  1. @implementation MyJSBridge
  2. /**
  3. * 重置处理器 - 该方法将会在加载新页面时调用,可以做一些清理工作。
  4. *
  5. * @param context WVBridge 被重置时的上下文。
  6. * @param request 要加载的新页面。
  7. */
  8. - (void)resetWithContext:(id<WVBridgeContext>)context withNextRequest:(NSURLRequest *)request {
  9. }
  10. /**
  11. * 暂停处理器 - 该方法将会在 UIViewController viewWillDisappear 时调用,用于降低 WVBridge API 的性能消耗,例如暂停播放音乐和持续性监听器等。
  12. * 不会对 WVBridgeScopeStatic 作用域的 WVBridge 调用。
  13. *
  14. * @param context WVBridge 被暂停时的上下文。
  15. */
  16. - (void)pauseWithContext:(id<WVBridgeContext>)context {
  17. }
  18. /**
  19. * 恢复处理器 - 该方法将会在 UIViewController viewWillAppear 时调用,用于恢复 WVBridge API 的性能,例如恢复播放音乐和持续性监听器等。
  20. * 不会对 WVBridgeScopeStatic 作用域的 WVBridge 调用。
  21. *
  22. * @param context WVBridge 被恢复时的上下文。
  23. */
  24. - (void)resumeWithContext:(id<WVBridgeContext>)context {
  25. }
  26. @end

如果 JSBridge 需要在切换到新页面后自动清理,需要实现 resetWithContext:withNextRequest:;如果需要在当前 UIViewController 被其它 Native 界面覆盖时暂停和恢复,需要实现 pauseWithContext:resumeWithContex:。一般建议与 WVBridgeScopeView 同时使用。

如果需要在应用切换前后台时做特殊工作,可以自行注册 UIApplicationDidBecomeActiveNotificationUIApplicationWillResignActiveNotification 这两个通知。

新接口介绍

在新版本 JSBridge 接口,将所有功能收拢到了统一的 id<WVBridgeCallbackContext> 中,一切功能都可以通过 context 来实现,并且对 Weex 等非 WebView 环境提供了支持。具体包括:

  1. 获取环境信息,基本信息有来源 URL referrerviewviewController。以及当前的环境 env,在 WebView 环境中可以拿到当前 UIView<WVWebViewBasicProtocol> *,在 Weex 环境中可以拿到当前 WeexSDKInstance *
  2. 发送事件,提供了 dispatchEvent:withParam: 方法。
  3. 释放实例,提供了 releaseHandler: 方法。
  4. 回调,提供了 callbackSuccess:callbackFailure:withResult: 方法,以及一系列失败回调的快捷方法。
  5. 所有 JSBridge 的方法参数全部统一为 NSDictioanry * paramsid<WVBridgeCallbackContext> context,无论是动态注册,还是各类静态注册的方法。

支持懒加载的动态库

一些客户端为了提高应用打开速度,对动态库做了懒加载处理,这样可以将动态库的加载延迟到使用时,避免影响应用启动耗时。

如果动态库中提供了动态注册 JSBridge,那么在动态库被加载之前,是找不到对应的类的,也无法正常调用。因此我们懒加载的动态库做了特别的支持,允许动态库通过 plist 配置动态 JSBridge 的类名,以便 WindVane 在需要使用 JSBridge 时主动将动态库加载进来。

解决方案是在动态库的 framework 根目录,提供一个命名为 XXX_bundle.plist 的文件,其中的 XXX 部分可以任意命名,甚至同一个 framework 包含多个符合此规则的 plist 也可以。在 plist 中添加名为 windvane_bridge_list 的数组,并在数组中添加需要配置的动态 JSBridge 类名,多个 plist 的配置会自动合并。下图是一个简单的示例:

Dynamic Framework Config | left | 600x374

请确保动态库全部位于客户端的 Frameworks 目录,如果不同动态库之间的映射关系存在覆盖,会在动态库提供的 JSBridge 被首次使用时打出日志 'A' 注册的 WVBridge 'XXX' 被 'B' 覆盖了,在 WindVane 的 DEBUG 模式下还会弹出 Alert 提示。

iOS静态注册JSBridge

WindVane iOS 允许将 JSBridge 注册到全局,也可以注册到特定 WebView。前者可以在任意 WebView 中调用,而后者只能在注册的 WebView 中使用,适合需要较严格的权限控制的 JSBridge。但是注册到全局的 JSBridge,也可以通过对 ViewController 的类型或属性进行校验,已达到部分权限的控制,因此如无特别的必要,请总是将 JSBridge 注册到全局,会有更少的内存消耗。

将 JSBridge 注册到全局

这样注册的 JSBridge,在任意 WebView 或 Weex 页面中都可以使用 className 和 handlerName 调用。

  1. #import <WindVane/WindVane.h>
  2. // JSBridge 调用的 Block。
  3. WVBridgeHandler handler = ^(NSDictionary * params, id<WVBridgeCallbackContext> context) {
  4. [context callbackSuccess:nil];
  5. };
  6. // 注册 JSBridge 到全局,特别注意这里的 JSBridge 名称合并写作 @"className.handlerName" 格式。
  7. WVBridgeRegisterHandler(@"className.handlerName", handler);

将 JSBridge 注册到特定 WebView

这样注册的 JSBridge,只能在注册到的 WebView 中使用 className 和 handlerName 调用。

  1. #import <WindVane/WindVane.h>
  2. // JSBridge 调用的 Block。
  3. WVBridgeHandler handler = ^(NSDictionary * params, id<WVBridgeCallbackContext> context) {
  4. [context callbackSuccess:nil];
  5. };
  6. // 注册 JSBridge 到指定 WebView,特别注意这里的 JSBridge 名称合并写作 @"className.handlerName" 格式。
  7. [webview registerHandler:@"className.handlerName" withBlock:handler];
  8. // 注册 JSBridge 到指定 ViewController 中的 WebView,特别注意这里的 JSBridge 名称合并写作 @"className.handlerName" 格式。
  9. [wvViewController registerHandler:@"className.handlerName" withBlock:handler];

静态注册的 JSBridge,不支持复杂的生命周期管理。如果需要实现复杂的 JSBridge,请动态注册 JSBridge。

静态注册的 JSBridge,若 className 和 handlerName 都相同,那么后注册的会覆盖先注册的。同时也会覆盖同名的动态注册方法。

JSBridge别名

WindVane 提供了 JSBridge 的别名服务,可以用来对现有 ClassName 的 JSBridge 进行扩展或覆盖,也可以对 JSBridge 的升级、更新、改名和冲突提供解决方案。

允许将原始的 JSBridge className.handlerName,映射到新的别名 aliasClassName.aliasHandlerName,用户即可以使用旧的 className.handlerName 来访问,也可以使用新的 aliasClassName.aliasHandlerName 来访问。

WindVane 的别名服务,是首先将别名解析为原始名称,然后使用原始名称来查找 JSBridge;不要将别名指向其它别名,可能导致无法正确找到 JSBridge

iOS 注册全局别名

全局别名必须与全局 JSBridge 配合使用。

  1. #import <WindVane/WindVane.h>
  2. // 注册 JSBridge 别名到全局。
  3. NSDictionary * alias = @{
  4. @"类别名.方法别名": @"类名.方法名",
  5. @"aliasClassName.aliasHandlerName": @"className.handlerName"
  6. };
  7. WVBridgeRegisterAlias(alias);

JSBridge全局鉴权

WindVane 提供了 JSBridge 的全局鉴权接口,供实现 App 级别的 JSBridge API 鉴权。该鉴权层切入到 JSBridge 的调用逻辑中,可以由客户端统一自定义鉴权逻辑。

对于单个 JSBridge 的鉴权,请在该 JSBridge 的实现中自行实现。

  1. #import <WindVane/WindVane.h>
  2. // 实现 JSBridge 鉴权方法。
  3. @interface MyJSBridgeCheckerHandler : NSObject <WVBridgeCheckerProtocol>
  4. @end
  5. @implementation MyJSBridgeCheckerHandler
  6. /**
  7. * 检查指定 WVBridge 是否具有执行权限。
  8. * 总是在后台线程调用。
  9. *
  10. * @param apiName WVBridge 的名称,格式为 "类名.方法名"。
  11. * @param params WVBridge 的调用参数。
  12. * @param context WVBridge 的执行上下文。
  13. *
  14. * @return 权限检查结果。
  15. */
  16. - (WVBridgePermission *)checkPermission:(NSString *)apiName withParams:(NSDictionary *)params withContext:(id<WVBridgeContext>)context {
  17. return [WVBridgePermission permissionNotSure];
  18. }
  19. @end
  20. // 注册鉴权处理器。
  21. [WVBridgeChecker registerChecker:[[MyJSBridgeCheckerHandler alloc] init]];

在Native中使用JSBridge

WindVane iOS中,支持在 Native 中调用 JSBridge。现在提供有两种用法:

简单用法

这里适合一些简单场景下,Native 调 用与 UI 无关的 JSBridge。直接调用 [WVBridge callHandler:withParams:withCallback:]方法,传入以下参数:

  • name: 要执行的 JSBridge 名称,格式为 @"类名.方法名"
  • params: 要执行的 JSBridge 参数,与 JS 使用方式保持一致(对象用 NSDictionary 代替,数组用 NSArray 代替)
  • callback: 此次 JSBridge 调用的回调,可能在任意线程调用

复杂用法

这里适合 Native 调用全功能的 JSBridge,可以用于在自己的容器中完整接入 JSBridge。

  1. 实现 WVBridgeDelegate 协议,供 JSBridge 回调数据给 Native。
  2. 实例化自己的 WVBridge 实例,传入 env 和上面实现的 delegate
  3. 设置 WVBridge 实例的 viewviewController 属性,为 JSBridge API 提供环境信息。其它属性可以根据需要设置:
    • pageId:当前页面的 ID,变化后前一页面未调用的 JSBridge 会全部失效,避免影响到当前页面
    • openPermissionCheck:启用白名单校验,只允许符合 WindVane domain 配置中的 Ali 域名白名单的 URL 调用 JSBridge,其它 URL 的调用都会被拒绝。
  4. 调用 callHandlerWithRequest:callHandlerWithURL:callHandler:withParams:withReqId: 方法之一,就会调用实际的 JSBridge;JSBridge 的回调、事件都会通过 delegate 返回回来。

标准事件机制

WindVane 的标准事件机制,提供了 Native 和 H5 之间互发事件的标准通讯接口,便于各业务场景下的基于统一的事件模型来通讯。

Native向H5发送事件

WindVane 提供了 WVStandardEventCenter 类,供 Native 向 H5 发送事件:

参数中,webView 是要发送事件的目标 WebView,eventName 是事件的名称(字符串),eventData 是事件的数据(iOS 是 NSDictionary *,Android 是一个 JSON Object 格式的 String)。

在 iOS 平台的 JSBridge 中,不建议使用这里的方法来发送事件,而是使用 JSBridge context 的 dispatchEvent 方法来发送事件,对多种容器(WebView/Weex)提供了兼容。

  1. #import <WindVane-Basic/WVStandardEventCenter.h>
  2. // 向指定 WebView 发送事件。
  3. [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData withWebView:webView];
  4. // 向所有 WebView 发送事件,请谨慎使用。
  5. [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData];

H5用法

  1. document.addEventListener('eventName', function(data) {
  2. // 这里要注意,Native 传递过来的事件参数是在 data 的 param 属性中。
  3. alert(data.param);
  4. }, false);

H5向Native发送事件

H5用法使用 JSBridge 来发送事件,注意引入 windvane.js,详情可以参考 WVStandardEventCenter.postNotificationToNative

  1. var params = {
  2. event: 'eventName',
  3. param: {
  4. // 事件要传递的数据。
  5. }
  6. };
  7. window.WindVane.call('WVStandardEventCenter','postNotificationToNative', params, function(e) {
  8. alert('success');
  9. }, function(e) {
  10. alert('failure: ' + JSON.stringify(e));
  11. });

iOS用法使用标准的消息中心监听事件:

  1. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myEventListener) name:eventName object:nil]
  2. - (void)myEventListener:(NSNotification *)notification {
  3. // 事件参数可以从 notification.userInfo 中获取。
  4. }