一:数据存储
这一块的知识只要是针对小程序列表和web资源包资源路径进行存储和查询。具体的详细设计如下:
小程序列表数据存储
小程序接口列表接口返回数据设计
数据存储通过数据库进行存储、数据库表设计如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| "list": [{ "minProgramId": "", "name": "" "content": "", "version": "", "build": 1, "h5Url": "", "detail": { "fullUrl": "", "partUrl": "", "fullMd5": "", "partMd5": "" }, }]
|
表名:webappinfos
表字段:
- id:小程序ID
- name:小程序名称
- version:小程序版本
- fullMd5:全量包资源md5
- fullUrl:全量包资源地址URL
- diffMd5:差量包资源md5
- diffUrl:差量包资源地址URL
- localPath:小程序资源包本地存储路径
| id |
name |
version |
fullMd5 |
fullUrl |
diffMd5 |
diffUrl |
localPath |
| 10001 |
小程序1 |
1.0.0 |
Ho…A== |
..zip |
R9…B== |
..zip |
webapps/10001 |
| 10002 |
小程序2 |
1.0.0 |
Ho…A== |
..zip |
R9…B== |
..zip |
webapps/10002 |
web资源包资源路径存储
web资源包文件目录如下:

数据存储通过数据库进行存储、数据库表设计如下:
表名:pathIndexInfos
表字段:
- url:web资源url路径
- localPath:离线包资源本地路径
- md5:离线包资源文件MD5值
- miniProgramId:小程序ID
| url |
localPath |
md5 |
miniProgramId |
| index.html |
webapps/1001/res/package/index.html |
0u…== |
1001 |
| images/…/.png |
webapps/1001/res/package/subPackagesA/static/images/pay/pay_close.png |
Ho…== |
305 |
| style/common.css |
webapps/1001/res/package/static/style/common.css |
NE…== |
1001 |
| static/js/index.js |
webapps/1001/res/package/static/js/index.js |
Ub…== |
1001 |
二:离线包资源更新
离线包资源更新分为全量更新和差量更新
全量更新
全量更新相对来说比较简单、通过小程序列表接口拉取小程序最新版本信息和本地当前运行版本进行对比,如果有新版本需要更新直接通过fullUrl下载最新版本的离线包资源。下载完成后解压替换本地的离线包资源即可。
差量更新
差量更新相对来说情况比较复杂一点,设计到的情况也比较多。具体更新逻辑如下:
假设:
- A:本地资源旧包
- B:差量资源包
- C:最新全量资源包 c_md5(最新全量资源包的MD5值)
B和A通过bsdiff库进行差量合并合并生成新的资源包D
获取D的md5值为d_md5
d_md5和c_md5值进行比较如果d_md5==c_md5将新生成的资源包D替换本地离线包资源
如果d_md5!=c_md5说明差量资源包和本地就资源包合并之后的资源不是服务器上上传的最新资源包此时需要进行全量更新下载全量资源到本地并替换本来离线包
出现无法进行差量更新情况场景分析:
假设此时要更新的小程序版本为: 1.1.2版本
如果用户当前版本为上个版本例如:1.1.1此时进行差量合并更新是OK的。
如果当前用户是上个版本之前的版本(和要更新的版本中间间隔至少一个版本)此时进行差量更新会失败,从而进行全量更新。
三:web资源拦截
web资源拦截主要针对ios的实现进行讲解利用NSURLProtocol拦截WKWebView的请求
具体的实现如下:
注册把http和https请求交给NSURLProtocol处理
监听web相关请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| [NSURLProtocol registerClass:[KKWebViewProtocol class]]; @interface KKWebViewProtocol : NSURLProtocol @end
+ (BOOL)canInitWithRequest:(NSURLRequest *)request { return YES; }
+ (void)startLoading { NSURL *url = self.request.URL; NSData* cacheData = nil; if ([webViewProtocolDelegate respondsToSelector:@selector(dataForWebViewProtocolWithURL:)]) { cacheData = [webViewProtocolDelegate dataForWebViewProtocolWithURL:[url absoluteString]]; if (cacheData == nil) { self.downloadTask = [self.session dataTaskWithRequest:self.request]; [self.downloadTask resume]; } else{ NSDictionary *dict = [NSDictionary dictionary]; NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"HTTP/2.0" headerFields:dict]; [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [self.client URLProtocol:self didLoadData:cacheData]; [self.client URLProtocolDidFinishLoading:self]; } } else { self.downloadTask = [self.session dataTaskWithRequest:self.request]; [self.downloadTask resume]; } }
-(void)stopLoading{ [self.downloadTask cancel]; self.downloadTask = nil; }
+ (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask*)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; completionHandler(NSURLSessionResponseAllow); self.cacheData = [NSMutableData data]; }
+ (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask*)dataTask didReceiveData:(nonnull NSData *)data{ [self.client URLProtocol:self didLoadData:data]; [self.cacheData appendData:data]; }
+ (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task didCompleteWithError:(NSError *)error { if (error) { [self.client URLProtocol:self didFailWithError:error]; } else { [self.cacheData writeToFile:[self filePathWithUrlString:self.request.URL.absoluteString] atomically:YES]; [self.client URLProtocolDidFinishLoading:self]; } }
|
WKURLSchemeHandler 拦截
iOS11之后,苹果公司,自己提供一个看似网友们都十分欢喜的新的API:
1
| + (void)setURLSchemeHandler:(nullable id <WKURLSchemeHandler>)urlSchemeHandler forURLScheme:(NSString *)urlScheme API_AVAILABLE(macos(10.13), ios(11.0));
|
四:如何检测离线包功能生效
完成以上离线包功能的开发、如何去验证APP是加载的离线资源而不是线上资源呢?
- 真机运行,体验正常加载和离线包加载两种方式进入小程序的速度
- 代码断点调试或者log打印是否走离线资源加载
- 抓包分析验证资源加载情况
离线包和非离线包加载的区别是:
- 离线包不需要加载web资源
- 非离线包需要请求加载web资源
六:总结
用到的知识点汇总:
- 资源文件下载
- zip文件解压
- 数据库存储
- bsdiff差分包库
- 文件md5值校验
- web请求拦截