Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。
1.首次打开App
第一次打开App,自然是先解压Hybrid Zip啦。通过‘CFBundleShortVersionString.CFBundleVersion’生成的版本标识符来判断是否需要重新解压Zip包,主要针对的是app通过更新上来需要解压新安装包中的Zip包。
// 解压Hybrid Zip包
- (void)unzipH5ResourcesFile {
// 解压代理
[BLNHybridDelegate sharedInstance].zipDelegate = self;
[H5ResourceFileManager sharedInstance].versionDict = @{
@"venue" : @"0",
};
// 版本标识符
NSString *key = @"BLN_APP_BUILD_VERSION";
NSString *value = [[NSUserDefaults standardUserDefaults] valueForKey:key];
NSString *versionStr = [NSString stringWithFormat:@"%@.%@",APP_VERSION,APP_BUILD_VERSION];
// AppStore更新App,则删除本地解压的Zip包及资源
if (![value isEqualToString:versionStr]) {
[[H5ResourceFileManager sharedInstance] clearH5Resource];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"BLN_HTML_ISUNZIP"];
}
// 解压Zip
@weakify(self)
[[H5ResourceFileManager sharedInstance] setupHtmlFileWithName:@"venue"
finishblock:^(id obj, NSInteger err) {
// 记录版本及标识符,并检查Zip版本更新
@normalize(self)
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"BLN_HTML_ISUNZIP"]) {
[[NSUserDefaults standardUserDefaults] setObject:@"0" forKey:@"BLN_HTML_VERSION"];
[[NSUserDefaults standardUserDefaults] setObject:@1 forKey:@"BLN_HTML_ISUNZIP"];
[[NSUserDefaults standardUserDefaults] setObject:versionStr forKey:key];
[self checkH5ResourcesFile];
}
else
[self checkH5ResourcesFile];
}];
}
#pragma mark - SSZipArchiveDelegate
// 解压代理
- (BOOL)filePath:(NSString *)filePath unZipToPath:(NSString *)toPath {
if ([SSZipArchive unzipFileAtPath:filePath toDestination:toPath]) {
return YES;
}
else {
return NO;
}
}
2.检查版本更新
需要注意的是,为了避免App长时间停留在后台而导致无法及时更新Zip资源包,我们还需要在App后台进入前台的时候,做一次检查更新。
// 后台进入前台
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[H5ResourceFileManager sharedInstance] checkH5ResourcesFile];
}
#pragma mark – Private Methods
// 检查更新
- (void)checkH5ResourcesFile {
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"BLN_HTML_ISUNZIP"]) {
[[H5ResourceFileManager sharedInstance] checkH5ResourcesFile];
}
}
3.获取本地版本号
self.loactionVersion = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:@"BLN_HTML_VERSION"];
4.获取服务器最新版本
/**
拼接json文件请求地址
@return 下载地址
*/
- (NSString *)getH5ResourcesDownLoadURL {
NSArray *numberArry = [APP_VERSION componentsSeparatedByString:@"."];
NSMutableString *localVersion = [[NSMutableString alloc] initWithString:self.baseURL];
for (int i = 0; i < numberArry.count; i++) {
NSString *value = numberArry[i];
[localVersion appendString:[NSString stringWithFormat:@"_%@",value]];
}
[localVersion appendString:@".json"];
return localVersion;
}
5.服务器JSON内容
JSON内容如下
{
"lastVersion": "20161201173611",// 最新版本
"md5": "be85803fbb78fa2d4d1a95a6f09a6183",// Zip的MD5校验码
"url": 最新版Zip下载地址
"data": [
{
"version": "20161123194301",// Zip版本
"url": 下载地址
"md5": "bd9da2ce1a56a59760483d5097bdd76b"// Zip的MD5校验码
},
{
"version": "20161130140816",// Zip版本
"url": 下载地址
"md5": "03d6174f95934ef78c4af3b904096992"// Zip的MD5校验码
}
]
}
6.对比版本号 全量/增量更新
如果本地Zip版本号可以在data数组中找到,则执行执行增量更新,如果找不到则做全量更新,全量更新需要删除本地解压的资源。
NSDictionary *dic = [responseObject mj_JSONObject];
if(dic && [dic objectForKey:@"lastVersion"]) {
self.lastVersion = [dic valueForKey:@"lastVersion"];
NSLog(@"%s LastVersion zip verson is %@",__FUNCTION__,self.lastVersion);
// 校验是否是最新版本
if([self.lastVersion longLongValue] > [self.loactionVersion longLongValue]) {
NSArray *data = dic[@"data"];
if (!data) {
_requestLoadTask = nil;
return;
}
// 增量更新
for (NSDictionary *info in data) {
if ([info[@"version"] isEqualToString:self.loactionVersion]) {
self.lastHashString = info[@"md5"];
[self downloadH5ResourcesZipWithURL:info[@"url"] clearHTMLResource:NO fractionCompleted:^(double count) {
}];
return;
}
}
// 全量更新
self.lastHashString = dic[@"md5"];
[self downloadH5ResourcesZipWithURL:dic[@"url"] clearHTMLResource:YES fractionCompleted:^(double count) {
}];
}
else {
_requestLoadTask = nil;
NSLog(@"%s This zip is lastVersion",__FUNCTION__);
}
}
else {
_requestLoadTask = nil;
NSLog(@"%s No json file",__FUNCTION__);
}
7.下载Hybrid Zip
下载过程中增加SVProgressHUD显示下进度条。
MJWeakSelf
_downLoadTask = [[BFHTTPManager sharedInstance] downloadTaskWithRequest:request
progress:^(NSProgress * _Nonnull downloadProgress) {
// 进度条
dispatch_async(dispatch_get_main_queue(), ^{
if (downloadProgress)
{
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeGradient];
[SVProgressHUD showProgress:downloadProgress.fractionCompleted status:@"更新中..."];
}
});
}
destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
// 存放路径
NSString *cachesPath = [LKFilePath cachesPath];
NSString *finalPath = [cachesPath stringByAppendingString:@"/Resources.zip"];
return [NSURL fileURLWithPath:finalPath];
}
completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone];
[SVProgressHUD dismiss];
// 错误处理
···
// 下载完成
···
});
}];
[_downLoadTask resume];
8.安全校验(MD5值校验)
为了防止Zip包被拦截篡改,对下载到本地的Zip进行MD5值的校验。
//目前文件所在地址
NSString *zipFilePath = [filePath path];// 将NSURL转成NSString
YYFileHash *fileHashSting = [YYFileHash hashForFile:zipFilePath types:YYFileHashTypeMD5];
BOOL same = ([weakSelf.lastHashString compare:fileHashSting.md5String options:NSCaseInsensitiveSearch | NSNumericSearch] == NSOrderedSame);
if (!same) {
dispatch_async(dispatch_get_main_queue(), ^{
[BFCustomHUD showInfoWithStatus:@"文件MD5值校验失败"];
});
return;
}
9.解压
全量更新
如果是全量更新,则删除原先解压的资源。
// 删除h5资源
[[H5ResourceFileManager sharedInstance] clearH5Resource];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"BLN_HTML_ISUNZIP"];
解压
// 目标文件夹地址
NSString *destnation = [[LKFilePath documentPath] stringByAppendingFormat:@"/html5/%@",self.htmlVersion];
if ([LKFilePath touchDirectory:destnation]) {
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
[SVProgressHUD showWithStatus:@"解压中..."];
});
bool unzipSuccess = [SSZipArchive unzipFileAtPath:zipFilePath toDestination:destnation];
if (unzipSuccess) {
weakSelf.loactionVersion = weakSelf.lastVersion;
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
[BFCustomHUD showSuccessWithStatus:@"更新成功"];
// 如果有H5页面 返回首页
for (UIViewController *vc in [LKGlobalNavigationController sharedInstance].viewControllers) {
if ([vc isKindOfClass:[BLNHybridViewController class]]) {
[[LKGlobalNavigationController sharedInstance] popToRootViewControllerAnimated:NO];
return ;
}
}
});
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *toPath = [[LKFilePath documentPath] stringByAppendingFormat:@"/html5/%@",weakSelf.lastVersion];
// prePath 为原路径,cenPath 为目标路径
if([fileManager moveItemAtPath:destnation toPath:toPath error:nil] != YES) {
NSLog(@"移动文件失败");
[BFCustomHUD showInfoWithStatus:@"升级失败"];
return;
}
else {
NSLog(@"移动文件成功");
}
}
}
10.更新版本版本号等标识符
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:@"%@",weakSelf.lastVersion] forKey:@"BLN_HTML_VERSION"];
[[NSUserDefaults standardUserDefaults] setObject:@1 forKey:@"BLN_HTML_ISUNZIP"];
[BLNReadAndSavePlist savePlistContent:weakSelf.lastVersion
withContentKey:@"venue_H5Version"
withPath:ph_updateResourcePlistName];
//初始化模块的最后更新时间
[BLNReadAndSavePlist savePlistContent:[NSDate date]
withContentKey:@"venue_H5LastUpdateTime"
withPath:ph_updateResourcePlistName];