转载:http://www.jianshu.com/p/dc527de06b5e
但是这位博主,源代码没有完全开放,我只是按照他的思路将代码补全,并将源代码开放
需求
1.在打开APP的时候进行网络请求,检查是否有网络更新。
2.如果有网络更新,下载新的版本,再次打开APP的时候,就直接连接到新的内容。
具体功能的实现:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UpdateDataLoader sharedInstance] createPath];
[[UpdateDataLoader sharedInstance] getAppVersion];
[self testCopy];
//检查更新并下载,有更新则直接下载,无则保持默认配置
//定义加载bundle的URL
NSURL *jsCodeLocation;
NSString* iOSBundlePath = [[UpdateDataLoader sharedInstance] iOSFileBundlePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:iOSBundlePath]) {
jsCodeLocation = [NSURL URLWithString:[iOSBundlePath stringByAppendingString:@"/index.ios.jsbundle"]];
}else{
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
}
NSLog(@"jsCodeLocation%@",jsCodeLocation);
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"CMEngineRoom"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
[DownLoadTool defaultDownLoadTool].view = rootView;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
上述代码是在Appdelegate中的实现,关键点有两个:
1.实现对版本号的数据持久化存储。
2.如何加载沙盒路径下的js.bundle文件。
实现数据持久化的方法
在三种数据持久化得方式中,我选择了plist文件去存储版本号和下载路径。
实现从本地文件夹中获取bundle并加载
在这里,有以下几个问题需要去解决:
- 如何下载?
- 下载完毕后如何解压?
- 解压完成后如何配置文件?
首先解决如何下载的问题,在这里我选择了第三方AFNetworking 3.0来实现对文件的下载。
使得下载主动进行。
在下载完毕之后的block回调中,是在主线程中进行的,在这里就可以进行一些UI的更新。
注意在完成之后返回的文件路径filePath为:
file:///*********
直接进行使用发现,获取不到文件。
必须将file:///去掉才能解决问题。
利用nsstring提供的方法来实现。
在这里需要注意的是每个下载的设置默认是被挂起的,所以在设置完毕之后,需要调用
[downloadTask resume]
使得下载主动进行。
OK解决的下载问题,现在来解决解压问题,解压方式我选择了第三方提供的SSZipArchive.
最后,来解决一下如何配置的问题:
在各种文档里都没有找到说明如何加载一个现有的js.bundle文件。
经过我个人的几番尝试,我找到了如下方法:
NSURL *jsCodeLocation;
jsCodeLocation = [NSURL URLWithString:iOSBundlePath];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"Dingla"
initialProperties:nil
launchOptions:launchOptions];
iOSBUndlepath为沙盒内的文件路径。
至此,iOS的热更新就实现了。
源码实现
DownloadTool.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface DownLoadTool : NSObject
@property (nonatomic, strong) NSString *zipPath;
@property (nonatomic, strong) UIView* view;
+ (DownLoadTool *) defaultDownLoadTool;
//根据url下载相关文件
-(void)downLoadWithUrl:(NSString*)url;
//解压压缩包
-(BOOL)unZip;
//删除压缩包
-(void)deleteZip;
@end
DownloadTool.m
#import "DownLoadTool.h"
#import "SSZipArchive.h"
#import "AFURLSessionManager.h"
#import "UpdateDataLoader.h"
@implementation DownLoadTool
+ (DownLoadTool *) defaultDownLoadTool{
static DownLoadTool *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[DownLoadTool alloc] init];
});
return sharedInstance;
}
//下载了一个dmg文件
//https://www.baidu.com/link?url=AH4YJUtQK3tB6D6BARqSkZzQsax38iBawDvAXK1wVGVplkkmuhf7mkpc6barjlavO6ysikDjimVG7d0l8KAFvorDmM3K_T5aIES89-JdQEG&wd=&eqid=e165a7c1000050c3000000035993b22c
-(void)downLoadWithUrl:(NSString*)url{
//根据url下载相关文件
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
//获取下载进度
NSLog(@"Progress is %f", downloadProgress.fractionCompleted);
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
//有返回值的block,返回文件存储路径
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
NSURL* targetPathUrl = [documentsDirectoryURL URLByAppendingPathComponent:@"kiOSFileName"];
return [targetPathUrl URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if(error){
//下载出现错误
NSLog(@"%@",error);
}else{
// [self showPromptWithStr:@"更新完毕。请重新启动******!"];
//下载成功
NSLog(@"File downloaded to: %@", filePath);
self.zipPath = [[filePath absoluteString] substringFromIndex:7];
//下载成功后更新本地存储信息
NSDictionary*infoDic=@{@"bundleVersion":@3,@"downloadUrl":url};
[UpdateDataLoader sharedInstance].versionInfo=infoDic;
[[UpdateDataLoader sharedInstance] writeAppVersionInfoWithDictiony:[UpdateDataLoader sharedInstance].versionInfo];
//解压并删除压缩包
if ([self unZip]) {
[self deleteZip];
};
}
}];
[downloadTask resume];
}
//解压压缩包
-(BOOL)unZip{
if (self.zipPath == nil) {
return NO;
}
//检查Document里有没有bundle文件夹
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* bundlePath = [path stringByAppendingPathComponent:@"kiOSfileSetName"];
BOOL isDir;
//如果有,则删除后解压,如果没有则直接解压
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:&isDir]&&isDir) {
[[NSFileManager defaultManager] removeItemAtPath:bundlePath error:nil];
}
NSString *zipPath = self.zipPath;
NSString *destinationPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingString:@"/IOSBundle"];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath
toDestination:destinationPath];
return success;
}
//删除压缩包
-(void)deleteZip{
NSError* error = nil;
[[NSFileManager defaultManager] removeItemAtPath:self.zipPath error:&error];
}
@end
UpdateDataLoader.h
#import <Foundation/Foundation.h>
@interface UpdateDataLoader : NSObject
@property (nonatomic, strong) NSDictionary* versionInfo;
+ (UpdateDataLoader *) sharedInstance;
//创建bundle路径
-(void)createPath;
//获取版本信息
-(void)getAppVersion;
-(void)writeAppVersionInfoWithDictiony:(NSDictionary*)info;
-(NSString*)iOSFileBundlePath;
@end
UpdateDataLoader.m
#import "UpdateDataLoader.h"
#import "DownLoadTool.h"
@implementation UpdateDataLoader
+ (UpdateDataLoader *) sharedInstance
{
static UpdateDataLoader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[UpdateDataLoader alloc] init];
});
return sharedInstance;
}
//创建bundle路径
-(void)createPath{
if([self getVersionPlistPath]){
return;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *path = [paths lastObject];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *directryPath = [path stringByAppendingPathComponent:@"IOSBundle"];
[fileManager createDirectoryAtPath:directryPath withIntermediateDirectories:YES attributes:nil error:nil];
NSString *filePath = [directryPath stringByAppendingPathComponent:@"Version.plist"];
[fileManager createFileAtPath:filePath contents:nil attributes:nil];
}
//获取版本信息
-(void)getAppVersion{
//从服务器上获取版本信息,与本地plist存储的版本进行比较
//假定返回的结果集
/*{
bundleVersion = 2;
downloadUrl = "www.baidu.com";
}*/
//1.获取本地plist文件的版本号 假定为2
NSString* plistPath=[self getVersionPlistPath];
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
NSInteger localV=[data[@"bundleVersion"]integerValue];
localV=2;
//2 服务器版本 假定为3
NSInteger serviceV=3 ;
if(serviceV>localV){
//下载bundle文件 存储在 Doucuments/IOSBundle/下
NSString*url=@"https://www.baidu.com/link?url=AH4YJUtQK3tB6D6BARqSkZzQsax38iBawDvAXK1wVGVplkkmuhf7mkpc6barjlavO6ysikDjimVG7d0l8KAFvorDmM3K_T5aIES89-JdQEG&wd=&eqid=e165a7c1000050c3000000035993b22c";
[[DownLoadTool defaultDownLoadTool] downLoadWithUrl:url];
}else{
}
}
//获取Bundle 路径
-(NSString*)iOSFileBundlePath{
//获取沙盒路径
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* path = [paths objectAtIndex:0];
NSLog(@"the save version file's path is :%@",path);
//填写文件名
NSString* filePath = [path stringByAppendingPathComponent:@"/IOSBundle"];
return filePath;
}
//获取版本信息储存的文件路径
-(NSString*)getVersionPlistPath{
//获取沙盒路径
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* path = [paths objectAtIndex:0];
NSLog(@"the save version file's path is :%@",path);
//填写文件名
NSString* filePath = [path stringByAppendingPathComponent:@"/IOSBundle/Version.plist"];
NSLog(@"文件路径为:%@",filePath);
return filePath;
}
//创建或修改版本信息
-(void)writeAppVersionInfoWithDictiony:(NSDictionary*)dictionary{
NSString* filePath = [self getVersionPlistPath];
[dictionary writeToFile:filePath atomically:YES];
}