前言
某个公司考虑使用React-Native的理由有很多,热更新/热部署可能是一个很大的原因。如果公司认为热更新/热部署是主要原因的话,那么这应该是一个很错误的决定。因为APP的质量和具有前瞻性的规划才是您真正需要考虑的事情,而不是热更新的补救。当然不可否认的是热更新/热部署很符合敏捷开发这个思维.......
进入正题
现在热更新的第三方服务很多:微软的CodePush,RN中文网的Pushy......国内还是推荐使用国内的服务吧。
既然有这么多第三方服务为何还要自己开发热更新/热部署功能呢?
1、安全性
2、可控性
3、实现简单的业务逻辑开发起来其实很简单
4、之前使用的微软CodePush的服务下载时出现很多问题,于是决定自己开发。
开发思路
开发步骤
1、编译项目
ios打包编译:CD到项目根目录运行命令
react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ./ios/bundle/index.ios.jsbundle --assets-dest ./ios/bundle
安卓打包请移步到打包APK
ios打包完成后在项目中的ios目录中多出了bundle文件夹,这里就是我们打包出来的RN运行文件,实际上RN运行的就是这里的.jsbundle文件。
2、压缩编译文件
把bundle文件夹压缩为ZIP包
3、把压缩的bundle的ZIP包上传到后端
4、更新后台相关的配置文件,比如:RN包的版本号、更新日志、是否强制更新、是否静默更新、是否回滚......具体有多少功能是你们的自己的事
5、下载最新的包,这里开始是写代码的时候了
5.1首先网络请求服务的RN配置内容
5.2对比本地RN的版本号
5.3弹出更新
5.4下载解压最新的包
下载和解压文件的代码:下载使用的是AFN、解压使用的是SSZipArchive
AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
//下载地址
NSURL*mgrurl = [NSURLURLWithString:self.updateUrl];
NSURLRequest*request = [NSURLRequestrequestWithURL:mgrurl];
NSURLSessionDownloadTask*download = [mgrdownloadTaskWithRequest:requestprogress:^(NSProgress*_NonnulldownloadProgress) {
//在主线程中调用
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSLog(@"进度%f",1.0*downloadProgress.completedUnitCount/ downloadProgress.totalUnitCount);
CGFloatjsw =1.0* downloadProgress.completedUnitCount/ downloadProgress.totalUnitCount* (PW-40);
self.jdView.frame=CGRectMake(0,0, jsw,5);
self.jdText.text= [NSStringstringWithFormat:@"更新进度(%.0f/100)",100.0*downloadProgress.completedUnitCount/ downloadProgress.totalUnitCount];
self.suduText.text= [NSStringstringWithFormat:@"%lldkb/s",[MLRNSingtongetInterfaceBytes]/1024/1024];
}];
}destination:^NSURL*_Nonnull(NSURL*_NonnulltargetPath,NSURLResponse*_Nonnullresponse) {
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
NSLog(@"targetPath:%@",targetPath);
NSLog(@"fullPath:%@",fullPath);
return[NSURLfileURLWithPath:fullPath];
}completionHandler:^(NSURLResponse*_Nonnullresponse,NSURL*_NullablefilePath,NSError*_Nullableerror) {
//下载完成,解压
//Caches路径
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *xxxx = [[filePath absoluteString] substringFromIndex:7];
[SSZipArchive unzipFileAtPath: xxxx toDestination:cachesPath];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[MBProgressHUD hideHUD];
UIStoryboard*storayobard = [UIStoryboardstoryboardWithName:@"main"bundle:nil];
self.view.window.rootViewController = storayobard.instantiateInitialViewController;
}];
}];
[downloadresume];
5.5运行本地的RN文件
在使用调用RN的地方(一般在AppDelegate中)
修改RCTRootView初始化方法为
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:[[RCTBridge alloc] initWithDelegate:self launchOptions:nil] moduleName:@""name"" initialProperties:nil];
添加代理RCTBridgeDelegate
实现RCTBridgeDelegate中的sourceURLForBridge方法
- (NSURL*)sourceURLForBridge:(RCTBridge*)bridge {
<#code#>
}
在sourceURLForBridge方法中确定您是要使用最新那个URL中的包
我这里的逻辑是
判断沙盒是否存在.jsbundle文件 ---> 有则使用沙盒中的.jsbundle
没有则使用本地打包的文件
- (NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL*jsCodeLocation;
// 取得沙盒目录
NSString *localPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 要检查的文件目录
NSString*filePath = [localPath stringByAppendingPathComponent:@"bundle/index.ios.jsbundle"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManagerfileExistsAtPath:filePath]) {
NSString*newUrl = [NSStringstringWithFormat:@"file://%@",filePath];
jsCodeLocation = [NSURLURLWithString:newUrl];
NSLog(@"文件存在");
returnjsCodeLocation;
}else{
#if DEBUG
// 原来的jsCodeLocation
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
#else
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/index.ios"withExtension:@"jsbundle"];
#endif
NSLog(@"文件不存在");
returnjsCodeLocation;
}
}
大功告成运行项目。
到这里最基础的热更新/热部署服务就搭建完成。如果想要更加完善还是需要花点功夫的,毕竟师傅领进门修行靠个人。