全部产品
云市场

规范与约束

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

UA规范

WindVane 会在 所设置的默认UA 的后面跟上 WindVane 标记、WindVaneSDK 的版本号、客户端补充 UA 和当前屏幕的分辨率,便于通过判断 UA 的方式来检查环境。完整的 UA 格式为:

默认UA (App名称/App版本号) WindVane/WindVaneSDK版本号 客户端补充UA 分辨率宽x分辨率高

  • WindVane iOS 需要通过 setAppUA 方法自行设置客户端补充 UA,且分辨率中使用的是小写字母 x
  • WindVane Android 没有提供设置客户端补充 UA 的方法,分辨率中使用的是大写字母 X
  • Win10 桌面版中,由于只能取到窗口大小(会发生变化),因此未加入分辨率。

警告:H5 开发者不要将客户端补充 UA 作为识别 iOS/Android 平台,或客户端版本的依据。

可以使用 amfe-env 库来做环境判断,或者通过 JS 自行判断环境:

  1. 示例:判断是否接入了 WindVane
  2. alert('获取到客户端的 UA: ' + window.navigator.userAgent);
  3. if (window.navigator.userAgent.match(/WindVane/i)) {
  4. alert('当前运行环境为接入了 WindVane Framework 的客户端 WebView');
  5. }
  1. 示例:判断手机平台
  2. alert('获取到客户端的 UA: ' + window.navigator.userAgent);
  3. if ((/Windows\s(Phone(?:\sOS)?|NT)\s[\d\.]+/i).test(window.navigator.userAgent)) {
  4. alert('当前运行环境是 Windows 客户端');
  5. } else if ((/iPhone|iPad|iPod/i).test(window.navigator.userAgent)) {
  6. alert('当前运行环境是 iOS 客户端');
  7. } else if ((/Android/i).test(window.navigator.userAgent)) {
  8. // 注意:由于 Windows Phone 的 UA 中也包含 Android 字符串,因此独立使用时注意兼容问题。
  9. alert('当前运行环境是 Android 客户端');
  10. }
  1. 示例:判断 UC 内核
  2. alert('获取到客户端的 UA: ' + window.navigator.userAgent);
  3. if ((/ UCBrowser\//).test(window.navigator.userAgent)) {
  4. if ((/ U3\//).test(window.navigator.userAgent)) {
  5. alert('当前运行环境是 U3 内核');
  6. } else if ((/ UCBS\//).test(window.navigator.userAgent)) {
  7. alert('当前运行环境是 U4 内核');
  8. } else {
  9. alert('当前运行环境是 UC 内核');
  10. }
  11. }
  12. } else {
  13. alert('当前运行环境不是 UC 内核');
  14. }

WebGL 使用规范

iOS平台下,若客户端已退到后台,但是 HTML 中仍在使用 webgl 进行 canvas 渲染,那么就会导致客户端 Crash。Crash 堆栈中一般会带有 _gpus_ReturnNotPermittedKillClient 或类似的堆栈。

原因是 iOS 下的 UIWebView 使用 webgl 渲染时,WebCore 会调用到 OpenGL ES 进行渲染,而苹果发现有在后台调用 OpenGL ES,就会直接结束 App。相关文档可以参考这里

所以前端禁止在 App 退到后台后做任何 webgl 操作,我们提供了以下四种解决方案:

  1. 切换到 WKWebView。
  2. 使用 @ali/game-loop 或类似前端库,实现生命周期管理,内部封装了下面提到的切换前后台事件。
  3. 在需要做 webgl 渲染时,调用 JSBridge WVApplication.appState 判断 App 状态,如果不是 'active',那么说明 App 不在前台,不能够做 webgl 渲染。由于 JSBridge 的调用有消耗,所以适合于偶尔渲染一次的情况。
  4. 监听 WindVane 提供的切换前后台事件,在事件中主动暂停/恢复 webgl 的渲染。适合一直在进行绘制的情况,可以参考以下代码:
  1. document.addEventListener('WV.Event.APP.Background', function(e) {
  2. if (e.param.to && e.param.to === 'background') {
  3. // 退到后台,暂停 webgl 渲染操作。
  4. }
  5. }, false);
  6. document.addEventListener('WV.Event.APP.Lock', function(e) {
  7. // 触发锁屏,暂停 webgl 渲染操作。
  8. }, false);
  9. document.addEventListener('WV.Event.APP.Active', function(e) {
  10. if (e.param.from && e.param.from === 'background') {
  11. // 回到前台,恢复 webgl 渲染操作。
  12. }
  13. }, false);

注意 WV.Event.APP.BackgroundWV.Event.APP.Lock 事件可能会都被触发,所以逻辑上请处理好同时触发的情况。

**警告:**在后台执行 getContext('webgl') 就可能会产生 Crash,原因是 WebView 会在初始化 WebGLRenderingContext 时调用 OpenGL ES 做一次 reshape,就会导致 Crash 产生。因此要注意 getContext('webgl') 的时机,如果可以的话最好用 WVApplication.appState 判断一下 App 状态。

https 使用规范

注意:在 WWDC 2016 开发者大会上,苹果宣布 2017 年 1 月 1 日起,苹果 App Store 中的所有 App 都必须启用 App Transport Security (ATS)。

因此 iOS 平台的 App,需要使用 https 发请求,包括 H5 页面,以及页面加载的任何资源求也同样需要切换为 https,请前端同学务必重视这件事情!

目前苹果为 iOS 客户端提供了以下配置,来对 http 协议做兼容,但都有很严格的审核限制,会强制触发 AppStore review 并需要提供解释,客户端未必能够配置:

  1. NSAllowsArbitraryLoads,设为为 YES 的话就会允许任意的 http 请求,但是基本无法通过苹果的审核,所以客户端不会配置。
  2. NSAllowsArbitraryLoadsInWebContent,设为为 YES 的话就会允许 UIWebView/WKWebView 发出的任意 http 请求(除了视频),但是也存在无法通过 AppStore 审核的可能,所以客户端未必会配置。
  3. NSAllowsArbitraryLoadsForMedia,设为为 YES 的话就会允许发出任意的视频 http 请求(包括 H5 和 Native),但是要求仅当视频已使用 FairPlaysecure HLS 之类的技术加密的前提下才可以配置,所以客户端基本不会配置。

已知系统问题

  1. 在 iOS 系统的 UIWebView 上,如果页面中同步请求了大量资源(比如同步三四十个请求),或用户频繁创建和销毁 WebView,有可能会造成 WebThread 死循环,最后导致主线程卡死。其特征是,主线程的堆栈包含:
  1. 0 libsystem_kernel.dylib 0x00000001975d30c0 ___psynch_mutexwait :8 (in libsystem_kernel.dylib)
  2. 1 libsystem_pthread.dylib 0x000000019766d54c __pthread_mutex_lock :420 (in libsystem_pthread.dylib)
  3. 2 WebCore 0x0000000194125d84 __ZL17_WebTryThreadLockb :52 (in WebCore)
  4. 3 WebCore 0x0000000194126778 _WebThreadLock :104 (in WebCore)

且 WebThread 以下面或类似的堆栈结束,包含 WTF::HashMap 的操作:

  1. thread #74: tid = 0x38cabd, 0x31382130 WebCore`WTF::HashMap<unsigned long, WTF::RefPtr<WebCore::ResourceLoader>, WTF::IntHash<unsigned long>, WTF::HashTraits<unsigned long>, WTF::HashTraits<WTF::RefPtr<WebCore::ResourceLoader> > >::remove(unsigned long const&) + 104, name = 'WebThread'

这个问题的原因,是 WTF::HashMap 本身出现了死循环,怀疑与并发读写内存,或内存溢出有关,但尚未明确。

目前的规避方案,是减少页面同步加载的资源个数,或者使用懒加载的方案,将同步资源请求数限制在一二十个以内就不太容易出现问题。

  1. 在 iOS 9.3.X 上,Web 页面中使用 MutationObserver 会偶现 Crash;前端在此版本上请不要使用 MutationObserver,或者使用其它技术代替。

    注意: WindVane iOS会在 iOS 9.3.X 中默认引入 polyfill,将 MutationObserver 切换到 Mutation Events 的实现以避免 Crash。但由于 iOS 系统的限制,attributes 的改变和 Contenteditable 元素的内容改变都不会触发 MutationObserver。 如果希望继续使用 MutationObserver,可以使用 window.WebKitMutationObserver,并对 Crash 率上升做好预期。

  2. 在 iOS 11.X 上,如果页面用到了 WebWorker,有可能出现以下两种 Crash:
  1. Thread 21 WebThread Crashed:
  2. 0 JavaScriptCore 0x0000000188eae188 _ZN3WTF20installSignalHandlerENS_6SignalEONS_8FunctionIFNS_12SignalActionES0_RNS_7SigInfoER27__darwin_arm_thread_state64EEE :700 (in JavaScriptCore)
  3. 1 JavaScriptCore 0x0000000188e16b70 _ZNSt3__117__call_once_proxyINS_5tupleIJOZN3JSC7VMTraps12SignalSenderC1ERKN3WTF14AbstractLockerERNS2_2VMEEUlvE_EEEEEvPv :52 (in JavaScriptCore)
  4. 2 libc++.1.dylib 0x000000018114c944 std::__1::__call_once(unsigned long volatile&, void*, void (*)(void*)) :160 (in libc++.1.dylib)
  5. 3 JavaScriptCore 0x0000000188e16904 JSC::VMTraps::SignalSender::SignalSender(WTF::AbstractLocker const&, JSC::VM&) :260 (in JavaScriptCore)
  6. 4 JavaScriptCore 0x0000000188e1641c JSC::VMTraps::fireTrap(JSC::VMTraps::EventType) :240 (in JavaScriptCore)
  7. 5 WebCore 0x000000018ab3b120 WebCore::WorkerThread::stop() :76 (in WebCore)
  8. 6 WebCore 0x00000001899c1420 WebCore::ScriptExecutionContext::stopActiveDOMObjects() :256 (in WebCore)
  9. 7 WebCore 0x00000001899c1778 WebCore::Document::prepareForDestruction() :564 (in WebCore)
  10. 8 WebCore 0x0000000189e59f48 _ZN7WebCore5Frame7setViewEON3WTF6RefPtrINS_9FrameViewEEE :148 (in WebCore)
  11. 9 WebCore 0x0000000189e656f0 WebCore::FrameLoader::detachFromParent() :520 (in WebCore)
  12. 10 WebKitLegacy 0x000000018ade0588 __29-[WebView(WebPrivate) _close]_block_invoke :344 (in WebKitLegacy)
  1. Thread 24 WebThread Crashed:
  2. 0 JavaScriptCore 0x000000018b012188 _ZN3WTF20installSignalHandlerENS_6SignalEONS_8FunctionIFNS_12SignalActionES0_RNS_7SigInfoER27__darwin_arm_thread_state64EEE :700 (in JavaScriptCore)
  3. 1 JavaScriptCore 0x000000018af7ab70 _ZNSt3__117__call_once_proxyINS_5tupleIJOZN3JSC7VMTraps12SignalSenderC1ERKN3WTF14AbstractLockerERNS2_2VMEEUlvE_EEEEEvPv :52 (in JavaScriptCore)
  4. 2 libc++.1.dylib 0x00000001832b0944 std::__1::__call_once(unsigned long volatile&, void*, void (*)(void*)) :160 (in libc++.1.dylib)
  5. 3 JavaScriptCore 0x000000018af7a904 JSC::VMTraps::SignalSender::SignalSender(WTF::AbstractLocker const&, JSC::VM&) :260 (in JavaScriptCore)
  6. 4 JavaScriptCore 0x000000018af7a41c JSC::VMTraps::fireTrap(JSC::VMTraps::EventType) :240 (in JavaScriptCore)
  7. 5 WebCore 0x000000018cc9f120 WebCore::WorkerThread::stop() :76 (in WebCore)
  8. 6 WebCore 0x000000018c671e50 WebCore::jsWorkerPrototypeFunctionTerminate(JSC::ExecState*) :136 (in WebCore)

此问题可能与 JS 主动调用 WebWorker.terminate() 有关,WebView 本来也会在销毁时主动结束 WebWorker,JS 可以在 iOS11.X 上不主动销毁。

使用 WKWebView

  1. 判断是 UIWebView 还是 WKWebView 环境,可以直接检查 window.webkit.messageHandlers 是否存在:
  1. var isWKWebView = window.webkit && window.webkit.messageHandlers;
  1. WKWebView 中无法通过 WVScreen.capture 截取 <canvas> 内容。

可以在截屏前将 <canvas> 内容渲染到一个 <img> 上,并将 <img> 覆盖到 <canvas> 之上实现截屏功能。

使用JSBridge

JSBridge 极大的扩展了 H5 的能力,它提供了 H5 与客户端通讯的机制,允许 H5 调用服客户端提供的功能,并接受客户端返回的数据。如果需要通过 UA 判断客户端情况,请参考上述 UA 规范

JSBridge 不支持在 iframe 内部调用,因为 Native 无法回调过去。

使用 window.WindVane.call(className, methodName, params, successCallback, failureCallback, timeout) 来调用客户端提供的功能。

输入参数

类型 名称 描述
string className 要调用的客户端类名。
string methodName 要调用的客户端方法名。
object params 要传递给客户端的参数。
function successCallback 执行成功后的回调。
function failureCallback 执行失败后的回调。
number timeout 可选 执行超时,超时后自动以 {ret:['HY_TIMEOUT']} 参数调用失败回调。

successCallback 和 failureCallback 都是由 Native 调用过来的,因此如果需要 try...catch 捕获异常,需要将捕获写到回调函数内部。

为了简便起见,文档中会将要调用的客户端类名和方法,记作 className.methodName 的格式。

回调参数如果成功执行客户端接口,则进入 success 回调,否则进入 failure 回调,回调函数的参数与被调用的接口相关。

可能的回调 ret回调参数中可能包含 ret 属性,表示此次执行的状态,其可能的值为:

描述
'HY_SUCCESS' 表示 API 执行成功了,该值只会在 success 回调中使用。
'HY_CLOSED' 表示 JSBridge 功能被强制关闭,一般由于页面 URL 不在信任域名内,或 Native 端未开启 JSBridge 功能,请检查 URL 或者联系 H5 的容器 Native 开发排查。
'HY_NO_HANDLER' 表示当前客户端没有提供这个 API,请检查调用的 JSBridge 是否正确,并联系 JSBridge 的 Native 提供方排查。
'HY_PARAM_ERR' 表示传入的参数错误,请检查传入的参数是否有误。
'HY_NO_PERMISSION' 表示当前页面没有权限调用这个接口。
'HY_FAILED' 表示 API 执行失败了,请联系 JSBridge 的 Native 提供方排查。
'HY_EXCEPTION' 表示 API 执行时发生了异常,请联系 JSBridge 的 Native 提供方排查。
'HY_USER_DENIED' 表示当前 API 缺少用户授权,例如需要访问相册的接口,但用户并未允许客户端访问相册。
'HY_USER_CANCELLED' 表示用户主动取消了当前 API 的功能执行。
'HY_RET_PHOTO_CANCLE' 表示使用 WVCamera 提供的接口拍照时,被用户取消。
'HY_TIMEOUT' 表示 JSBridge 调用超时。
'HY_RESULT_PARSE_ERROR' 表示 Native 传递给 H5 的数据出现格式错误
'HY_NOT_IN_WINDVANE' 表示当前不是 WindVane 环境(如浏览器),或者客户端未正确提供 WindVane 的 UserAgent。也有可能是页面 JS 错误的修改了 windvane.js 中的环境变量(例如设置了 WindVane.isAvailable = false)。
'HY_NOT_SUPPORT_DEVICE' 表示 JSBridge 在不支持的设备中(如浏览器)被调用。也有可能是页面 JS 错误的修改了 windvane.js 中的环境变量(例如设置了 WindVane.isAvailable = false)。
'HY_NO_HANDLER_ON_WP' 表示 JSBridge 在不支持的 Windows 设备中(如浏览器)被调用。
'WV_ERR::PARAM_PARSE_ERROR' 表示 Native 传递给 H5 的数据出现格式错误。

根据调用的 JSBridge API 的不同,ret 属性可能不存在,也可能是一个字符串,也可能是一个数组,其中 ret[0] 包含了客户端接口的执行状态。并不是所有 JSBridge 都会调用回调函数,这与客户端的实现有关。JSBridge 文档中总会指出什么情况下会进入成功回调或失败回调。

下面以调用 MyJSBridge.firstAPI 为例:

  1. var params = {
  2. 'myParam': 'Something to Native',
  3. 'myParam2': {
  4. 'p': 'ps'
  5. }
  6. };
  7. window.WindVane.call('MyJSBridge', 'firstAPI', params, function(e) {
  8. alert('success' + JSON.stringify(e));
  9. }, function(e) {
  10. alert('failure' + JSON.stringify(e));
  11. });