微软的CodePush热更新很是难用你们都知道,速度跟被墙了没什么区别。react
另一方面,咱们不但愿把代码放到别人的服务器。本身写接口更新总归感受安全一点。ios
so,就来本身搞个React-Native APP的热更新管理工具吧。暂且命名为hotdog。git
/**************************************************/github
首先咱们要弄清react-native启动的原理,是直接调用jslocation的jsbundle文件和assets资源文件。react-native
由此,咱们能够本身经过的服务器接口去判断版本,并下载最新的而后替换相应的文件,而后从这个文件调用启动APP。这就像以前的一些H5APP同样作版本的管理。缓存
以iOS为例,咱们须要分如下几步去搭建这个本身的RN升级插件:安全
1、设置默认jsbundle地址(好比document文件夹):服务器
1.首先打包的时候把jsbundle和assets放入copy bundle resource,每次启动后,检测document文件夹是否存在,不存在则拷贝到document文件夹,而后给RN框架读取启动。app
咱们创建以下的bundle文件管理类:框架
MXBundleHelper.h
#import <Foundation/Foundation.h> @interface MXBundleHelper : NSObject +(NSURL *)getBundlePath; @end
MXBundleHelper.m
#import "MXBundleHelper.h" #import "RCTBundleURLProvider.h" @implementation MXBundleHelper +(NSURL *)getBundlePath{ #ifdef DEBUG NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; return jsCodeLocation; #else //须要存放和读取的document路径 //jsbundle地址 NSString *jsCachePath = [NSString stringWithFormat:@"%@/\%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0],@"main.jsbundle"]; //assets文件夹地址 NSString *assetsCachePath = [NSString stringWithFormat:@"%@/\%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0],@"assets"]; //判断JSBundle是否存在 BOOL jsExist = [[NSFileManager defaultManager] fileExistsAtPath:jsCachePath]; //若是已存在 if(jsExist){ NSLog(@"js已存在: %@",jsCachePath); //若是不存在 }else{ NSString *jsBundlePath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"jsbundle"]; [[NSFileManager defaultManager] copyItemAtPath:jsBundlePath toPath:jsCachePath error:nil]; NSLog(@"js已拷贝至Document: %@",jsCachePath); } //判断assets是否存在 BOOL assetsExist = [[NSFileManager defaultManager] fileExistsAtPath:assetsCachePath]; //若是已存在 if(assetsExist){ NSLog(@"assets已存在: %@",assetsCachePath); //若是不存在 }else{ NSString *assetsBundlePath = [[NSBundle mainBundle] pathForResource:@"assets" ofType:nil]; [[NSFileManager defaultManager] copyItemAtPath:assetsBundlePath toPath:assetsCachePath error:nil]; NSLog(@"assets已拷贝至Document: %@",assetsCachePath); } return [NSURL URLWithString:jsCachePath]; #endif }
2、作升级检测,有更新则下载,而后对本地文件进行替换:
假如咱们不当即作更新,能够更新后替换,而后不会影响本次APP的使用,下次使用就会默认是最新的了。
若是当即更新的话,须要使用到RCTBridge类里的reload函数进行重启。
这里经过NSURLSession进行下载,而后zip解压缩等方法来实现文件的替换。
MXUpdateHelper.h
#import <Foundation/Foundation.h> typedef void(^FinishBlock) (NSInteger status,id data); @interface MXUpdateHelper : NSObject +(void)checkUpdate:(FinishBlock)finish; @end
MXUpdateHelper.m
#import "MXUpdateHelper.h" @implementation MXUpdateHelper +(void)checkUpdate:(FinishBlock)finish{ NSString *url = @"http://www.xxx.com/xxxxxxx"; NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; [newRequest setHTTPMethod:@"GET"]; [NSURLConnection sendAsynchronousRequest:newRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError) { if(connectionError == nil){ //请求本身服务器的API,判断当前的JS版本是否最新 /* { "version":"1.0.5", "fileUrl":"http://www.xxxx.com/xxx.zip", "message":"有新版本,请更新到咱们最新的版本", "forceUpdate:"NO" } */ //假如须要更新 NSString *curVersion = @"1.0.0"; NSString *newVersion = @"2.0.0"; //通常状况下不同,就是旧版本了 if(![curVersion isEqualToString:newVersion]){ finish(1,data); }else{ finish(0,nil); } } }]; } @end
3、APPdelegate中的定制,弹框,直接强制更新等
若是须要强制刷新reload,咱们新建RCTView的方式也须要稍微改下,经过新建一个RCTBridge的对象。
由于RCTBridge中有reload的接口可使用。
#import "AppDelegate.h" #import "RCTBundleURLProvider.h" #import "RCTRootView.h" #import "MXBundleHelper.h" #import "MXUpdateHelper.h" #import "MXFileHelper.h" #import "SSZipArchive.h" @interface AppDelegate()<UIAlertViewDelegate> @property (nonatomic,strong) RCTBridge *bridge; @property (nonatomic,strong) NSDictionary *versionDic; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; jsCodeLocation = [MXBundleHelper getBundlePath]; _bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge moduleName:@"MXVersionManager" 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]; __weak AppDelegate *weakself = self; //更新检测 [MXUpdateHelper checkUpdate:^(NSInteger status, id data) { if(status == 1){ weakself.versionDic = data; /* 这里具体关乎用户体验的方式就多种多样了,好比自动当即更新,弹框当即更新,自动下载下次打开再更新等。 */ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:data[@"message"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"如今更新", nil]; [alert show]; //进行下载,并更新 //下载完,覆盖JS和assets,并reload界面 // [weakself.bridge reload]; } }]; return YES; } - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if(buttonIndex == 1){ //更新 [[MXFileHelper shared] downloadFileWithURLString:_versionDic[@"fileurl"] finish:^(NSInteger status, id data) { if(status == 1){ NSLog(@"下载完成"); NSError *error; NSString *filePath = (NSString *)data; NSString *desPath = [NSString stringWithFormat:@"%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]]; [SSZipArchive unzipFileAtPath:filePath toDestination:desPath overwrite:YES password:nil error:&error]; if(!error){ NSLog(@"解压成功"); [_bridge reload]; }else{ NSLog(@"解压失败"); } } }]; } }
流程简单,经过接口请求版本,而后下载到document去访问。 其中须要作版本缓存,Zip的解压缩,以及文件拷贝等。
运行iOS工程能够看到效果。 初始为1.0.0版本,而后更新后升级到1.0.1版本。