最佳使用实践-离线包
离线包简介
通过 TMF 的离线包组件,可以将网页的静态资源(html、js、css、图片等)缓存在本地,当用户访问 H5 页面时,这些资源都不需要从服务器即时下载,离线包组件会拦截 WebView 的资源请求并直接读取本地的离线资源,达到“瞬间秒开”的效果。
TMF 控制台实现了离线包的自动打包、状态管理和发布功能,支持推拉结合的更新方式及增量更新机制,可按多种维度进行灰度下发,且能便捷地按 H5 的版本采集灰度数据,对不同版本的业务数据进行对比分析。对于重要业务(如一级页面),还可以直接将离线包预置在发布包中,首次进入页面无需下载即可使用,大幅提升 H5 业务的用户体验。当本地离线包没有准备好时,则直接访问在线资源,不影响正常使用。
离线包最佳实践
规划您的离线包
离线包分类
TMF离线包可以分为两种类型:
- 主包:一个或一组业务对应的H5应用离线资源合集。
- 公共包:多个主包的公共资源,可供多个主包共同使用。
主包与公共包使用规则:
- 主包之间不能相互依赖,主包资源为业务单独所有。
- 一个主包可以依赖多个公共包。
按照资源类型规划您的离线包
- 将多个H5应用的公共资源、基础库提取为公共包,供多个业务共同使用。
- 为了减少公共包更新频率,将公共资源进一步拆分为稳定公共资源与易变公共资源,根据变化频率拆分为不同的公共包。
- 根据H5应用业务划分,将不同应用或者不同业务拆分为独立的业务主包。
- 考虑到离线包更新下载效率问题,尽量控制离线包的大小,建议大小为2MB左右。
离线包打包
请参考打包离线包文档进行离线包打包操作。
预置离线包
请参考预置离线包文档完成离线包本地预置功能。
打开离线包
概念说明
- BID: 离线包ID。
- URL: 离线包业务入口URL,对应H5应用的主页面路径。
离线包URL规则
假设离线包对应的H5应用主页面路径为
http://pimweb.cs0309.3g.qq.com/testAk/index.html
离线包BID为weboffline_test_123。 使用离线包的接口检查更新、下载和加载时,URL分为两种情况:
第一种
url?_bid=xxx,表示访问这个 URL 时,它所需要的资源(例如图片、js 文件)会先从本地 bid 为xxx的离线包资源寻找,如果存在就加载本地资源,否则访问服务端资源。该方式URL如下:http://pimweb.cs0309.3g.qq.com/testAk/index.html?_bid=weboffline_test_123
第二种,依赖公共包资源
url?_bids=main+depends,main 表示主包,主包必须放在最前面(index.html 资源所在的包),depends 表示依赖包(公共资源所在的包)。访问这个 URL 时它所需要的资源(例如图片、js 文件)会先从本地 BID 为 main 主包中加载,然后再到依赖包 depends 中加载,如果本地没有则访问在线资源。假设离线包依赖的公共包BID时common,URL如下:
http://pimweb.cs0309.3g.qq.com/testAk/index.html?_bids=weboffline_test_123+common
初始化
具体初始化流程请参考《离线包》接入手册。
加载离线包
Android
使用
loadUrlAsync
方法将 URL_WITH_BID_PARAM 转换为离线包 URL,并使用 webview 加载转换后的 URL。其中URL_WITH_BID_PARAM获取方法请参考离线包URL规则 。loadUrlAsync默认会触发离线包检查更新,合理的检查更新时机及更新策略请参考检查离线包更新。
mOfflineManager.loadUrlAysn(URL_WITH_BID_PARAM, new SimpleCallback<String>() { @Override public void callback(String transedUrl) { webview.loadUrl(transedUrl); } });
创建 H5 容器,H5 容器的 WebView 需要重写 WebViewClient 的 shouldOverrideUrlLoading 方法,在该方法中需要使用 OfflineManager 类中的 shouldInterceptRequest 方法检查并使用本地离线包中的资源。
OfflineManager 类中的 shouldInterceptRequest 方法使用如下:
private class TestWebViewClient extends WebViewClient { @Override public WebResourceResponse shouldInterceptRequest(final WebView view, final String url) { TMFWebResourceResponse response = mOfflineManager.shouldInterceptRequest(url); if (response != null && response.getResourceResponse() != null) { return response.getResourceResponse(); } //注意这里必须返回父类的shouldInterceptRequest,不能直接返回null return super.shouldInterceptRequest(view, url); } }
iOS
创建包含WebView的ViewController,并遵守
TMFWebOfflineWebViewControllerProtocol
协议创建tmf_webOfflineHandler。同时实现WebView的代理。使用
determinedURLWithURL:
方法对originURL进行处理,返回webview最终加载的URL。NSURL *determinedURL = [self.tmf_webOfflineHandler determinedURLWithURL:originURL]; // 由 WebOffline Handler 来决定最终要访问的 URL,例如开启 fallback 功能,此处会对 URL 进行转换 NSURLRequest *request = [NSURLRequest requestWithURL:determinedURL]; [self.webView loadRequest:request];
在webView的代理方法中,设置离线包拦截。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { // Handle the WebOfflineHandler [self.tmf_webOfflineHandler handleRequest:navigationAction.request]; decisionHandler(WKNavigationActionPolicyAllow); }
使用公共包
如果多个离线包使用了相同的图片或css资源,可以将公共的资源放置在公共包中来降低离线包的整体大小。公共包打包及上传的步骤如下:
修改离线包的加载URL,在bid信息中添加上公共包信息(本例中的common)。
NSString *homeURLString = [NSString stringWithFormat:@"https://3gimg.qq.com/webapp_scan/TMF/TMF_intro/index.html?_bids=welcome+common&_t=%ld", (long)[[NSDate date] timeIntervalSince1970]];
离线包升级
灰度更新
离线包发布时,可以先进行灰度下发,测试无异常之后再进行正式下发。离线包灰度发布步骤如下:
主包升级注意事项
- 主包升级如果依赖新版公共包,请同时更新公共包。
- 客户端已经检测到主包更新,但还未下载完成时会触发fallback地址流程,需遵守fallback规范,所有相对路径资源需完整打包进主包,否则使用 fallback_url时会找不到该资源。
- 主包使用公共包中的资源必须使用完整URL,不能是相对路径。
公共包升级注意事项
公共包是被多个离线包共同依赖的,因此公共包升级一定要考虑兼容所有离线包,包括旧版本离线包,原则上公共包中资源只增不减。
回滚离线包
在线上离线包出现异常时,可以通过回滚离线包功能。离线包回滚步骤如下:
检查离线包更新
离线包检查更新支持如下几种方式:
- 打开离线包时自动触发检查。
- 通过推送下发触发检查。
- 在合适的时间手动触发,支持全部、批量、指定离线包检查更新。手动触发检查更新方式可参考《离线包接入手册》
所有检查更新动作默认都会受检查更新频率限制,您可以通过手动更新触发强制更新。手动触发强制检查更新方法:
- Android
// 指定离线包检查更新
UpdateSetting setting = new UpdateSetting();
setting.ignoreFreqLimit = true;//绕过更新频率控制
mOfflineManager.checkLatestUpdate(bid, setting, new DefaultUpdateCallback());
- iOS
// 指定离线包检查更新
TMFWebOfflineServiceOptions *options = [TMFWebOfflineServiceOptions options];
options.ignoresFrequency = YES;
[TMFWebOfflineService checkAndUpdateWithBID:@"bid" options:options completionHandler:^(BOOL isUpdated, NSError * _Nullable error) {
// do something.
}];
检查更新时机建议:
- 打开离线包是触发检查更新,该机制默认是打开的。
- 离线包升级时默认下发推送,触发推送更新流程。
- 对于使用频率较高的离线包,推荐使用预检查机制,比如在程序启动时就触发首页离线包检查更新,但该方式需要谨慎使用,启动时触发过多离线包检查更新会占用网络与计算资源,影响用户体验。
下载离线包
仅Wi-Fi下载(仅Android)
Android离线包支持设置仅Wi-Fi下载,具体设置方法如下:
UpdateSetting setting = new UpdateSetting();
setting.downloadOnlyInWiFi = true;
mOfflineManager.checkLatestUpdate(bids, setting, new DefaultUpdateCallback());
离线包更新推送下载配置
离线包更新推送下载支持三种方式:
- 忽略推送
- 收到推送时立即下载离线包
- 收到推送时仅Wi-Fi下载离线包
默认为收到推送时立即下载离线包,如您需调整策略,请参考如下设置方法:
- Android
public class OfflineConfig {
/**
* 接收到离线包推送时忽略
*/
public static final int DOWNLOAD_IGNORE_ON_PUSH = 1;
/**
* 接收到离线包推送时立刻下载离线包
*/
public static final int DOWNLOAD_RIGHTNOW_ON_PUSH = 2;
/**
* 接收到离线包推送时Wi-Fi环境下才下载离线包
*/
public static final int DOWNLOAD_ONLY_WIFI_ON_PUSH = 3;
}
OfflineManager.init(context,
OfflineConfig.builder(SharkService.getSharkWithInit())
//其它设置
.downloadModeOnPush(OfflineConfig.DOWNLOAD_ONLY_WIFI_ON_PUSH)
.build());
- iOS
typedef NS_ENUM(NSInteger, TMFWebOfflinePushHandlePolicy) {
TMFWebOfflinePushHandlePolicyNone = 0, ///< 收到推送后不更新,表示SDK不处理推送,同时将推送广播
TMFWebOfflinePushHandlePolicyWiFiOnly = 1, ///< 收到推送后仅在Wi-Fi下更新
TMFWebOfflinePushHandlePolicyWiFiAndCellular = 2, ///< 收到推送后任意网络下更新
};
// 离线包启动
TMFWebOfflineConfiguration *configuration = [TMFWebOfflineConfiguration configuration];
configuration.pushHandlePolicy = TMFWebOfflinePushHandlePolicyWiFiAndCellular;
[TMFWebOfflineService activateWithConfiguration:configuration];
设置离线包存储位置(仅安卓)
Android离线包支持外置和内置两种存储,默认是存储在内置存储中的,如果您想调整为外置存储,方法如下:
OfflineManager.init(context,
OfflineConfig.builder(SharkService.getSharkWithInit())
//其它设置
.storeInSDCard(true)
.build());
移除离线包
为了减少应用存储占用,您可以将不再使用的离线包移除。具体移除方法请参考《离线包》接入手册。
离线包在线资源与fallback资源的关系
在线资源
部署在web服务器上,可以通过浏览器进行在线访问的称为在线资源。由您进行部署并进行访问控制和版本控制。例如:https://3gimg.qq.com/webapp_scan/TMF/TMF_intro/index.html。
fallback资源
在TMF控制台上传的离线包,控制台会将离线包文件解压后放在指定目录下形成fallback资源。它也能够通过浏览器进行访问。例如:https://tmfmp.qq.com:30010/cdn/WebOffline_Demo_1/658/658/3gimg.qq.com/webapp_scan/TMF/TMF_intro/index.html。
fallback地址组成
一个完整的fallback地址有两部分组成,Fallback Base URL和Original Path。Fallback Base URL也是由CDN地址和离线包信息组成的。
使用fallback的离线包加载
离线包加载流程:
H5页面问题修复
iOS如何开启离线包调试日志
设置合适的离线包的
logLevels
。离线包日志等级分为Debug、Info、Warn、Error。例如
TMFWebOfflineService.logLevels = TMFBaseCoreLogLevelDebug | TMFBaseCoreLogLevelInfo
表示输出离线包的Debug和Info日志。如果想输出全部的日志,可以使用TMFBaseCoreLogLevelAll
。合理地开启/关闭
TMFBaseCore
的日志筛选功能。TMFBaseCore
的allowBaseCoreLogFilter
可设置是否对离线包的日志进行筛选,且默认开启。它和TMFLogOptions
共同控制是否对离线包的控制台日志进行筛选。
iOS JS报错导致白屏问题修复
离线包校验失败问题修复
iOS
TMFWebOfflineFileVerifierErrorDomain
错误信息 | 错误码 | 说明 |
---|---|---|
TMFWebOfflineFileVerifierErrorPublicKeyInvalid | -1 | 离线包公钥无效。可能是没有设置公钥或者公钥格式错误。 |
TMFWebOfflineFileVerifierErrorPackageJsonInvalid | -2 | 找不到离线包文件的Hash记录文件,无法校验。 |
TMFWebOfflineFileVerifierErrorSignatureLoss | -3 | 找不到离线包签名文件,无法校验。 |
TMFWebOfflineFileVerifierErrorSignatureInvalid | -4 | 离线包校验失败,最常见的是公私钥不匹配。 |
TMFWebOfflineFileVerifierErrorFilesTampered | -5 | 校验文件或目录出现问题,退出校验。 |
问题修复
- TMFWebOfflineFileVerifierErrorPublicKeyInvalid
- 检查终端是否设置了公钥文件。
- TMFWebOfflineFileVerifierErrorPackageJsonInvalid
- 检查离线包服务部署是否出现异常。
- TMFWebOfflineFileVerifierErrorSignatureLoss
- 检查离线包服务部署是否出现异常。
- TMFWebOfflineFileVerifierErrorSignatureInvalid
- 如果更换终端公钥文件,修改完毕后直接运行即可修复。
- 如果修改控制台私钥文件,则需要重新上传离线包并下发。
Android
校验错误码
错误信息 | 错误码 | 说明 |
---|---|---|
DOWNLOAD_CODE_ERROR_S_FAIL | 5 | 离线包签名校验失败 |
DOWNLOAD_CODE_ERROR_FULL_PACKAGE_CHECK_FAIL | 6 | 离线包整包MD5校验失败 |
问题修复
- DOWNLOAD_CODE_ERROR_S_FAIL 该错误在关闭快速校验时才可能出现,请检查离线包公钥是否配置正确。
- DOWNLOAD_CODE_ERROR_FULL_PACKAGE_CHECK_FAIL 该错误在打开快速校验时才可能出现,可能是下载传输过程出错导致,请稍后重试。
快速校验设置
快速校验打开后,只校验离线包MD5,不会校验离线包签名信息,效率更高。Android默认实现是打开快速校验的,如果您想要更高的安全性,可以关闭快速校验。关闭方法如下:
OfflineManager.init(context, OfflineConfig.builder(SharkService.getSharkWithInit())
//其它设置
.quickVerify(false)
.build());
iOS页面请求数据丢失
由于离线包的工作机制原因,H5页面的请求会被拦截,引起如post请求body丢失问题。解决此情况可采用下面两种方案:
使用
TMFWKWebView
替换WKWebView
。在进入第三方页面是停止离线包拦截,离开第三方页面是继续拦截。
停止拦截
[TMFWebOfflineService pauseHandler];
开启拦截
[TMFWebOfflineService resumeHandler];