配色: 字号:
实现iOS图片等资源文件的热更新化(四) 一个最小化的补丁更新逻辑
2016-10-10 | 阅:  转:  |  分享 
  
实现iOS图片等资源文件的热更新化(四):一个最小化的补丁更新逻辑



这么做的意义

先交代动机和意义,或许应该成为自己博客的一个标准框架内容之一,不然以后自己需要看着,也不过是一堆干瘪的代码.基本的逻辑图,如上!此处,我就从简!



从简的原因有3:



补丁更新,状态可以设计的很复杂,就像开头那篇文章提到的那样,但是我感觉没多大必要,至少在我们的App中;

我想演示一个相对完整的逻辑,但是又不想耗费太多的时间构建场景;

从简后的方案,简单但够用了,至少目前针对我们的项目来说;

所以说:这篇文章的意义,其实是在于简化已有的热更新代码,越简单越好维护.



基本思路

App启动时,判断特定的服务器接口所返回的图片url是否为最新,判断方式就是比对返回值中的md5字段与本地保存的资源的url是否一致;

如果图片资源有更新,则下载解压到指定的缓存目录,初步打算以资源文件的md5来划分文件夹,来避免冲突;

读取图片时,优先从缓存目录读取,缓存目录不存在再从ipa资源包中读取;

下面就一步一步来实现了.



App启动时,判断有无最新图片资源

此处主要涉及到的可能的技术点:



1.如何用基础的网络类库发送网络请求?

先简单封装一个函数来获取,用到了block.block经常用,但到现在都记不太清形式,大都是从其他处copy下,然后改改参数.记不住,也懒得记!



-(void)fetchPatchInfo:(NSString)urlStrcompletionHandler:(void(^)(NSDictionarypatchInfo,NSErrorerror))completionHandler

{

NSURLSessionConfigurationdefaultConfigObject=[NSURLSessionConfigurationdefaultSessionConfiguration];

NSURLSessiondefaultSession=[NSURLSessionsessionWithConfiguration:defaultConfigObjectdelegate:selfdelegateQueue:[NSOperationQueuemainQueue]];



NSURLurl=[NSURLURLWithString:urlStr];



NSURLSessionDataTaskdataTask=[defaultSessiondataTaskWithURL:url

completionHandler:^(NSDatadata,NSURLResponseresponse,NSErrorerror){

NSDictionarypatchInfo=[NSJSONSerializationJSONObjectWithData:dataoptions:0error:&error];;



completionHandler(patchInfo,error);

}];



[dataTaskresume];

}





基于block,调用的代码也就很简答了.

[selffetchPatchInfo:@"https://raw.githubusercontent.com/ios122/ios_assets_hot_update/master/res/patch_04.json"

completionHandler:^(NSDictionarypatchInfo,NSErrorerror){

if(!error){

NSLog(@"patchInfo:%@",patchInfo);

}else

{

NSLog(@"fetchPatchInfoerror:%@",error);

}

}];





好吧,我承认AFNetworking用习惯了,好久没用原始的网络请求的代码了,有点low,莫怪!



2.如何校验下载的文件的md5值,如果你需要的话?

开头那篇文章链接里,有提到.核心,其实是在于下载文件之后,md5值的计算,剩余的就是字符串比较操作了.



注意要先引入系统库



1

#include

/

获取文件的md5信息.



@parampath文件路径.



@return文件的md5值.

/

-(NSString)mcMd5HashOfPath:(NSString)path

{

NSFileManagerfileManager=[NSFileManagerdefaultManager];



//确保文件存在.

if([fileManagerfileExistsAtPath:pathisDirectory:nil])

{

NSDatadata=[NSDatadataWithContentsOfFile:path];

unsignedchardigest[CC_MD5_DIGEST_LENGTH];

CC_MD5(data.bytes,(CC_LONG)data.length,digest);



NSMutableStringoutput=[NSMutableStringstringWithCapacity:CC_MD5_DIGEST_LENGTH2];



for(inti=0;i
{

[outputappendFormat:@"%02x",digest[i]];

}



returnoutput;

}

else

{

return@"";

}

}





3.使用什么保存与获取本地缓存资源的md5等信息?

好吧,我打算直接使用用户配置文件,



NSStringsource_patch_key=@"SOURCE_PATCH";



[[NSUserDefaultsstandardUserDefaults]setObject:patchInfoforKey:source_patch_key];

patchInfo=[[NSUserDefaultsstandardUserDefaults]objectForKey:source_patch_key];



NSLog(@"patchInfo:%@",patchInfo);





补丁下载与解压

此处主要涉及到的可能的技术点:



1.如何基于图片缓存信息来找到指定的缓存目录?

问题本身有些绕口,其实我想做的就是根据补丁的md5,放到不同的缓存文件夹,如补丁md5为e963ed645c50a004697530fa596f180b,则对应放到patch/e963ed645c50a004697530fa596f180b文件夹.封装一个简单的根据md5返回缓存路径的方法吧:

-(NSString)cachePathFor:(NSString)patchMd5

{

NSArrayLibraryPaths=NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);



NSStringcachePath=[[[LibraryPathsobjectAtIndex:0]stringByAppendingFormat:@"/Caches/patch"]stringByAppendingPathComponent:patchMd5];



returncachePath;

}





使用时,类似这样:



NSStringurlStr=[patchInfoobjectForKey:@"url"];



[weak_selfdownloadFileFrom:urlStrcompletionHandler:^(NSURLlocation,NSErrorerror){

if(error){

NSLog(@"downloadfileurl:%@error:%@",urlStr,error);

return;

}



NSStringcachePath=[weak_selfcachePathFor:[patchInfoobjectForKey:@"md5"]];

NSLog(@"location:%@cachePath:%@",location,cachePath);



}];





2.如何解压文件到指定目录?





如果需要安装CocoaPods,建议使用brew:



1

brewinstallCocoaPods





解压本身推荐SSZipArchive库,一行代码搞定:



1

[SSZipArchiveunzipFileAtPath:location.pathtoDestination:patchCachePathoverwrite:YESpassword:nilerror:&error];





3.在什么时候更新本地的缓存资源的相关信息?

建议是在下载并解压资源文件到指定缓存目录后,再更新补丁的相关缓存信息,因为这个信息,读取图片时,也是需要的.如果删除某个补丁,按照目前的设计,一种比较偷懒的方案就是,在服务器上放上一个新的空资源文件就可以了.

NSStringsource_patch_key=@"SOURCE_PATCH";



[[NSUserDefaultsstandardUserDefaults]setObject:patchInfoforKey:source_patch_key];





读取图片功能扩展

此处主要涉及到的可能的技术点:



1.如何用基础的网络类库下载文件?

依然是要封装一个简单函数,下载完成后,通过block传出文件临时的保存位置:

-(void)downloadFileFrom:(NSString)urlStrcompletionHandler:(void(^)(NSURLlocation,NSErrorerror))completionHandler

{

NSURLurl=[NSURLURLWithString:urlStr];



NSURLSessionConfigurationdefaultConfigObject=[NSURLSessionConfigurationdefaultSessionConfiguration];

NSURLSessiondefaultSession=[NSURLSessionsessionWithConfiguration:defaultConfigObjectdelegate:selfdelegateQueue:[NSOperationQueuemainQueue]];



NSURLSessionDownloadTaskdownloadTask=[defaultSessiondownloadTaskWithURL:url

completionHandler:^(NSURLlocation,NSURLResponseresponse,NSErrorerror)

{



completionHandler(location,error);



}];

[downloadTaskresume];



}





2.如何判断bundle中是否含有某文件?

可以使用fileExistsAtPath,但其实使用-pathForResource:ofType:就够了,因为找不到资源问加你时,它返回nil,所以我们直接调用它,然后判断返回是否为nil即可:



1

NSStringimgPath=[mainBundlepathForResource:imgNameofType:@"png"];





3.将代码如何与原有的imageNamed:逻辑合并?

不需要初始复制到缓存目录+初始请求最新的资源补丁信息+代码迁移合并+接口优化



相对完整的逻辑代码

注意,按照目前的设计,就不需要初始把原来ipa中的bundle复制到缓存目录了;当缓存目录中没有相关资源时,会自动尝试从ipa中的bundle读取,bundle约定统一使用main.bundle来简化操作,



类目,对外暴露两个方法:

#import



@interfaceUIImage(imageNamed_bundle_)

/loadimgsmart./

+(UIImage)yf_imageNamed:(NSString)imgName;



/smartupdateforpatch/

+(void)yf_updatePatchFrom:(NSString)pathInfoUrlStr;

@end





App启动时,或在其他合适的地方,要注意检查有无更新:

-(BOOL)application:(UIApplication)applicationdidFinishLaunchingWithOptions:(NSDictionary)launchOptions{

//Overridepointforcustomizationafterapplicationlaunch.



/fetchpathcinfoeverytime/

NSStringpatchUrlStr=@"https://raw.githubusercontent.com/ios122/ios_assets_hot_update/master/res/patch_04.json";

[UIImageyf_updatePatchFrom:patchUrlStr];



returnYES;

}





内部实现,优化了许多,但也算不上复杂:

#import"UIImage+imageNamed_bundle_.h"

#import



@implementationUIImage(imageNamed_bundle_)



+(NSString)yf_sourcePatchKey{

return@"SOURCE_PATCH";

}



+(void)yf_updatePatchFrom:(NSString)pathInfoUrlStr

{

[selfyf_fetchPatchInfo:pathInfoUrlStr

completionHandler:^(NSDictionarypatchInfo,NSErrorerror){

if(error){

NSLog(@"fetchPatchInfoerror:%@",error);

return;

}



NSStringurlStr=[patchInfoobjectForKey:@"url"];

NSStringmd5=[patchInfoobjectForKey:@"md5"];



NSStringoriMd5=[[[NSUserDefaultsstandardUserDefaults]objectForKey:[selfyf_sourcePatchKey]]objectForKey:@"md5"];

if([oriMd5isEqualToString:md5]){//noupdate

return;

}



[selfyf_downloadFileFrom:urlStrcompletionHandler:^(NSURLlocation,NSErrorerror){

if(error){

NSLog(@"downloadfileurl:%@error:%@",urlStr,error);

return;

}



NSStringpatchCachePath=[selfyf_cachePathFor:md5];

[SSZipArchiveunzipFileAtPath:location.pathtoDestination:patchCachePathoverwrite:YESpassword:nilerror:&error];



if(error){

NSLog(@"unzipandmovefileerror,withurlStr:%@error:%@",urlStr,error);

return;

}



/updatepatchinfo./

NSStringsource_patch_key=[selfyf_sourcePatchKey];

[[NSUserDefaultsstandardUserDefaults]setObject:patchInfoforKey:source_patch_key];

}];

}];



}



+(NSString)yf_relativeCachePathFor:(NSString)md5

{

return[@"patch"stringByAppendingPathComponent:md5];

}



+(UIImage)yf_imageNamed:(NSString)imgName{

NSStringbundleName=@"main";



/cachedir/

NSStringmd5=[[[NSUserDefaultsstandardUserDefaults]objectForKey:[selfyf_sourcePatchKey]]objectForKey:@"md5"];



NSStringrelativeCachePath=[selfyf_relativeCachePathFor:md5];



return[selfyf_imageNamed:imgNamebundle:bundleNamecacheDir:relativeCachePath];

}



+(UIImage)yf_imageNamed:(NSString)imgNamebundle:(NSString)bundleNamecacheDir:(NSString)cacheDir

{

NSArrayLibraryPaths=NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);



bundleName=[NSStringstringWithFormat:@"%@.bundle",bundleName];



NSStringipaBundleDir=[NSBundlemainBundle].resourcePath;

NSStringcacheBundleDir=ipaBundleDir;



if(cacheDir){

cacheBundleDir=[[[LibraryPathsobjectAtIndex:0]stringByAppendingFormat:@"/Caches"]stringByAppendingPathComponent:cacheDir];

}



imgName=[NSStringstringWithFormat:@"%@@3x",imgName];



NSStringbundlePath=[cacheBundleDirstringByAppendingPathComponent:bundleName];

NSBundlemainBundle=[NSBundlebundleWithPath:www.baiyuewang.netbundlePath];

NSStringimgPath=[mainBundlepathForResource:imgNameofType:@"png"];



/tryloadfromipa!/

if(!imgPath&&![ipaBundleDirisEqualToString:cacheBundleDir]){

bundlePath=[ipaBundleDirstringByAppendingPathComponent:bundleName];

mainBundle=[NSBundlebundlewww.wang027.comWithPath:bundlePath];

imgPath=[mainBundlepathForResource:imgNameofType:@"png"];

}



UIImageimage;

staticNSStringmodel;



if(!model){

model=[[UIDevicecurrentDevice]model];

}



if([modelisEqualToString:@"iPad"]){

NSDataimageData=[NSDatadataWithContentsOfFile:imgPath];

image=[UIImageimageWithData:imageDatascale:2.0];

}else{

image=[UIImageimageWithContentsOfFile:imgPath];

}

returnimage;

}



+(void)yf_fetchPatchInfo:(NSString)urlStrcompletionHandler:(void(^)(NSDictionarypatchInfo,NSErrorerror))completionHandler

{

NSURLSessionConfigurationdefaultConfigObject=[NSURLSessionConfigurationdefaultSessionConfiguration];

NSURLSessiondefaultSession=[NSURLSessionsessionWithConfiguration:defaultConfigObjectdelegate:nildelegateQueue:[NSOperationQueuemainQueue]];



NSURLurl=[NSURLURLWithString:urlStr];



NSURLSessionDataTaskdataTask=[defaultSessiondataTaskWithURL:url

completionHandler:^(NSDatadata,NSURLResponseresponse,NSErrorerror){

NSDictionarypatchInfo=[NSJSONSerializationJSONObjectWithData:dataoptions:0error:&error];;



completionHandler(patchInfo,error);

}];



[dataTaskresume];

}



+(void)yf_downloadFileFrom:(NSString)urlStrcompletionHandler:(void(^)(NSURLlocation,NSErrorerror))completionHandler

{

NSURLurl=[NSURLURLWithString:urlStr];



NSURLSessionConfigurationdefaultConfigObject=[NSURLSessionConfigurationdefaultSessionConfiguration];

NSURLSessiondefaultSession=[NSURLSessionsessionWithConfiguration:defaultConfigObjectdelegate:nildelegateQueue:[NSOperationQueuemainQueue]];



NSURLSessionDownloadTaskdownloadTask=[defaultSessiondownloadTaskWithURL:url

completionHandler:^(NSURLlocation,NSURLResponseresponse,NSErrorerror)

{



completionHandler(location,error);



}];

[downloadTaskresume];

}



+(NSString)yf_cachePathFor:(NSString)patchMd5

{

NSArrayLibraryPaths=NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);



NSStringcachePath=[[[LibraryPathsobjectAtIndex:0]stringByAppendingFormat:@"/Caches"]stringByAppendingPathComponent:[selfyf_relativeCachePathFor:patchMd5]];



returncachePath;

}



@end





现在,加载图片的代码更简单了:



1

2

UIImageimage=[UIImageyf_imageNamed:@"sub/sample"];

self.sampleImageView.image=image;

献花(0)
+1
(本文系thedust79首藏)