流程图:
简单实现:
iOS Demo地址:https://github.com/yushengchu/Incremental-hot-update
// 热更细管理类
// HotUpdataManage.h
// hotUpdataDemo
//
// Created by joker on 2017/9/7.
// Copyright © 2017年 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
@interface HotUpdataManage : NSObject
//单例方法
+ (HotUpdataManage*)getInstance;
//获取加载URL
- (NSURL*)getBridge;
//检查更新
- (void)checkUpdate:(NSString*)checkUrl;
//bridge对象 用于重新载入jsbundle
@property (nonatomic,strong) RCTBridge *bridge;
@end
//
// HotUpdataManage.m
// hotUpdataDemo
//
// Created by joker on 2017/9/7.
// Copyright © 2017年 Facebook. All rights reserved.
//
#import "HotUpdataManage.h"
#import "MXHZIPArchive.h"
#import "DiffPatch.h"
#define HOT_MAIN_DOC_PATH [NSString stringWithFormat:@"%@/HOTSDK/main",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]]
#define HOT_JS_PATH [NSString stringWithFormat:@"%@/HOTSDK/main/%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0],@"main.jsbundle"]
@implementation HotUpdataManage
+ (HotUpdataManage*)getInstance{
static HotUpdataManage *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[HotUpdataManage alloc] init];
});
return manager;
}
#pragma mark 获取bridge
- (NSURL*)getBridge{
NSFileManager *fileManager =[NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:HOT_JS_PATH]) {
NSString* zipPatch = [[NSBundle mainBundle] pathForResource:@"bundle" ofType:@"zip"];
NSLog(@"zipPatch ---> %@",zipPatch);
if([fileManager fileExistsAtPath:zipPatch]){
BOOL isReload = [self unzipBundleAndReload:zipPatch];
//复制jsbundle文件和assest文件到到对应目录
if (isReload) {
return [NSURL URLWithString:HOT_JS_PATH];
}
}
}
return [NSURL URLWithString:HOT_JS_PATH];
}
#pragma mark 检查更新
- (void)checkUpdate:(NSString*)urlStr{
NSURL *url = [NSURL URLWithString: urlStr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10];
[request setHTTPMethod: @"GET"];
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil
error:nil];
if (data){
NSDictionary *resultInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if ([resultInfo[@"isUpdate"] boolValue]) {
[self downLoadFile:[resultInfo objectForKey:@"updataUrl"]];
}
}
}
#pragma mark 下载
- (void)downLoadFile:(NSString *)urlString{
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10];
[request setHTTPMethod: @"GET"];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if (data){
NSLog(@"diff下载成功");
NSString *pacthPath = [self getFilePath:@"diff.patch"];
if ([data writeToFile:pacthPath atomically:YES]) {
[self patchBundle:pacthPath];
}else{
NSLog(@"diff保存失败.");
}
}
else {
NSLog(@"diff下载失败");
}
}
- (void)patchBundle:(NSString*)pacthPath{
NSString* zipPatch = [[NSBundle mainBundle] pathForResource:@"bundle" ofType:@"zip"];
NSString* tmpZipPath = [NSString stringWithFormat:@"%@/tmp.zip",HOT_MAIN_DOC_PATH];
// 创建最新jsbundel文件
BOOL writeBundel = [DiffPatch beginPatch:pacthPath origin:zipPatch toDestination:tmpZipPath];
if (!writeBundel) {
NSLog(@"bundel写入失败");
return;
}
if ([self unzipBundleAndReload:tmpZipPath]) {
[self reloadNow];
}
NSLog(@"更新成功");
}
#pragma mark 解压
-(BOOL)unzipBundleAndReload:(NSString*)zipPath{
//获取zipPath
NSError *error;
NSString *reload_zipPath = zipPath;
NSString *desPath = HOT_MAIN_DOC_PATH;
BOOL pathExist = [[NSFileManager defaultManager] fileExistsAtPath:desPath];
if(!pathExist){
[[NSFileManager defaultManager] createDirectoryAtPath:desPath withIntermediateDirectories:YES attributes:nil error:nil];
}
// NSLog(@"start unzip zip Path:%@",reload_zipPath);
[MXHZIPArchive unzipFileAtPath:reload_zipPath toDestination:desPath overwrite:YES password:nil error:&error];
if(!error){
NSLog(@"解压成功,路径:%@",desPath);
return true;
}else{
NSLog(@"解压失败,路径:%@,错误原因:%@",desPath,[error description]);
return false;
}
}
#pragma mark 重新加载
-(void)reloadNow{
[self.bridge reload];
NSLog(@"Bridge reload");
}
- (NSString*)getFilePath:(NSString*)fileName{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory =[paths objectAtIndex:0];
NSString *filePath =[documentsDirectory stringByAppendingPathComponent:fileName];
return filePath;
}
@end
//AppDelegate中使用
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "HotUpdataManage.h"
@interface AppDelegate()
@property (nonatomic,strong) RCTBridge *bridge;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
HotUpdataManage* manage = [HotUpdataManage getInstance];
NSURL* localUrl = nil;
#ifdef DEBUG
localUrl = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
#else
localUrl = [manage getBridge];
#endif
//使用bridge的方式创建rootView
_bridge = [[RCTBridge alloc] initWithBundleURL:localUrl moduleProvider:nil launchOptions:nil];
manage.bridge = _bridge;
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge moduleName:@"hotUpdataDemo" initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
//检查更新 这里使用的是阿里的rap mock接口
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[manage checkUpdate:@"http://rapapi.org/mockjsdata/13203/checkUpdate"];
});
return YES;
}
@end
后言
包括pushy在内的许多支持增量热更新的类库,实际上只是对jsbundle文件进行了diff算法.
图片是通过在服务器进行筛选获取新增或者更改的图片,然后与jsbundle的diff文件一同打包成一个压缩包给客户端进行热更新.
本Demo采用的是对整个bundle.zip文件进行diff,直接获得一个针对旧版bundle.zip文件的热更新文件.
iOS打包中,会默认将jsbundle和assest文件夹打入ipa包中,并不会将bundle.zip文件打入.
这一块需要通过打包脚本来实现默认将bundle.zip打入ipa包内.
同时通过将Xcode -- targets -- Build Phases -- Bundle React Native code and images中的shell脚本删除,可以在打包时候不将jsbundle和assest文件夹打入包内.
在第一次打开APP时使用默认打入的bundle.zip来解压获得jsbundle和assest运行React-Native项目.