代碼實(shí)現(xiàn)react-native熱更新(iOS)

react-native作為跨平臺的語言好處主要體現(xiàn)在跨平臺和能進(jìn)行熱更新脯倚。要實(shí)現(xiàn)熱更新通常想到使用某個第三方組件實(shí)現(xiàn),常用的就是微軟的codepush和RN中文網(wǎng)的react-native-pushly.如果不想使用第三方我們也可以自己實(shí)現(xiàn)获洲。所謂的熱更新就是下載新的bundle壓縮包然后解壓,讓app加載這個補(bǔ)丁包那槽。根據(jù)我的實(shí)踐,一個壓縮的bundle包也只有兩三M周偎,所有只要在原生端下載這個壓縮包然后解壓就行了翁逞。
大概思路:
1.沙盒里建三個文件夾和一個plist文件总珠,一個放補(bǔ)丁包(IOSBundle)屏鳍,一個放下載的壓縮包(BundleZip解壓后會刪除該壓縮包),一個放記錄版本號與補(bǔ)丁號的plist文件(PatchPlist)局服, 判斷存放補(bǔ)丁包的文件夾下是否有下載的補(bǔ)丁钓瞭,同時判斷記錄的版本號與當(dāng)前版本號是否相同,不相同有補(bǔ)丁也不加載淫奔。

2.網(wǎng)絡(luò)請求獲取是否有補(bǔ)丁需要下載山涡,如果有則拿到下載的url進(jìn)行下載,如果沒有則不下載唆迁。網(wǎng)絡(luò)請求使用AFN,解壓用了ZipArchive

3.如果需要下載補(bǔ)丁則先下載壓縮包鸭丛,然后解壓,解壓完成時刪除壓縮包唐责,將獲得的版本號和補(bǔ)丁號寫入plist鳞溉。下次app重啟是就會加載補(bǔ)丁包,熱跟新完成鼠哥。

ps:如果想在上架app Store審核之后用熱更新更新新需求而不重新提交審核熟菲,那圖片就不能放在xcode里了,所有引用圖片的方式需要使用require的方式朴恳,因?yàn)榉旁趚code里的圖片是沒辦法熱更新的〕保現(xiàn)在說的這種更適合修復(fù)線上js寫的邏輯bug,無法修改原生代碼.

在AppDelegate中于颖,啟動的時候判斷沙盒文件夾是否有已經(jīng)下載的補(bǔ)丁包呆贿,如果有就加載。但是如果安裝了新版本且是覆蓋安裝森渐,這樣啟動時加載的會仍然是那個補(bǔ)丁包做入,所以為了防止這種情況冒晰,我們需要比較上次下載補(bǔ)丁時記錄的版本號和當(dāng)前版本號,如果不同母蛛,即使沙盒里有補(bǔ)丁包也不加載

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   NSURL *jsCodeLocation;
   NSString* iOSBundlePath = [[[UpdateDataLoader sharedInstance] iOSFileBundlePath] stringByAppendingString:@"/bundle/index.ios.jsbundle"];
  if ([[NSFileManager defaultManager] fileExistsAtPath:iOSBundlePath] && [self compareAppVersionIsSame]) {
#ifdef DEBUG
    //開發(fā)包
    jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
#else
    //加載熱修復(fù)下載的bundle
    jsCodeLocation = [NSURL URLWithString:iOSBundlePath];
#endif
  }else{
#ifdef DEBUG
     //開發(fā)包
     jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
#else
     //離線包
     jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];
#endif
  }
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"ReactFlyApp"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
//因?yàn)槲覍嚎s包托管在leanCould上翩剪,此處是設(shè)置該SDK,如果有自己的后臺則直接請求是否需要更新補(bǔ)丁
[self AVOSCloudSetting];
。彩郊。。
}

//判斷熱更新記錄的版本與當(dāng)前版本是否相同
-(BOOL)compareAppVersionIsSame{
  NSString *currentAppVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
  NSDictionary *dic = [[UpdateDataLoader sharedInstance]getDataFromIosBundlePlist];
  NSString *recordAppVersion = @"";
  if(dic !=nil && dic[@"appVerion"]){
    recordAppVersion = dic[@"appVerion"] ;
  }
  return [currentAppVersion isEqualToString:recordAppVersion];
}

-(void)AVOSCloudSetting{
  [[UpdateDataLoader sharedInstance] createHotFixBundlePath]; //在沙盒新建三個文件夾和一個plist文件蚪缀,如果有則不新建
  [AVOSCloud setApplicationId:AVOSCloudID clientKey:AVOSCloudKey];
#ifdef DEBUG
  [AVOSCloud setAllLogsEnabled:YES];
#else
  [AVOSCloud setAllLogsEnabled:FALSE];
#endif
  [[UpdateDataLoader sharedInstance] downloadNewBundle];//進(jìn)行網(wǎng)絡(luò)請求判斷是否需要下載補(bǔ)丁
}

UpdateDataLoader文件主要是創(chuàng)建文件夾秫逝,plist文件和判斷是否需要下載

#import <Foundation/Foundation.h>

@interface UpdateDataLoader : NSObject

@property (nonatomic, strong) NSDictionary* versionInfo;
+ (UpdateDataLoader *) sharedInstance;

//創(chuàng)建bundle路徑
-(void)createHotFixBundlePath;

//檢查下載熱更新包
-(void)downloadNewBundle;

-(void)writeAppVersionInfoWithDictiony:(NSDictionary*)info;

-(NSString*)iOSFileBundlePath;

-(NSDictionary*)getDataFromIosBundlePlist;
@end

----------------------------------------------------------------------------------------= = ------------------------------

#import "UpdateDataLoader.h"
#import "DownLoadTool.h"
#import "Network.h"
#import "AFNetworking.h"
#import <AVOSCloud/AVOSCloud.h>
@implementation UpdateDataLoader

+ (UpdateDataLoader *) sharedInstance
{
  static UpdateDataLoader *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[UpdateDataLoader alloc] init];
  });
  return sharedInstance;
}

//創(chuàng)建bundle路徑
-(void)createHotFixBundlePath{
  if([[NSFileManager defaultManager]fileExistsAtPath:[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 *bundleZipPath = [path stringByAppendingPathComponent:@"BundleZip"];
  [fileManager createDirectoryAtPath:bundleZipPath withIntermediateDirectories:YES attributes:nil error:nil];
  NSString *PatchPlistPath = [path stringByAppendingPathComponent:@"PatchPlist"];
  [fileManager createDirectoryAtPath:PatchPlistPath withIntermediateDirectories:YES attributes:nil error:nil];
  NSString *filePath = [PatchPlistPath stringByAppendingPathComponent:@"Version.plist"];
  [fileManager createFileAtPath:filePath contents:nil attributes:nil];
}

//獲取版本信息,有下載就下載新的熱更新包
-(void)downloadNewBundle{
  NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
  NSDictionary *dic = [self getDataFromIosBundlePlist];
  int patchCode = 0;
  if(dic !=nil && dic[@"patchCode"]){
    patchCode = [dic[@"patchCode"] intValue];
  }
//此處應(yīng)該將version和patchCode穿給后臺,且要求后臺返回數(shù)據(jù)要按修改時間升序(時間越新越靠前)询枚,拿version到數(shù)據(jù)庫中匹配违帆,與該version相同且大于該patchCode,有滿足條件的數(shù)據(jù)則表明有補(bǔ)丁包需要下載金蜀,這時返回一個數(shù)組刷后,我們?nèi)?shù)組的第一個對象,該對象包含需要下載的url,然后下載渊抄。應(yīng)該我使用了leanCloud尝胆,所以代碼是這樣
 AVQuery *fileQuery = [AVQuery queryWithClassName:@"hot_update_file"];
  [fileQuery whereKey:@"patchCode" greaterThan:@(patchCode)];
  [fileQuery whereKey:@"version" equalTo:version];
  [fileQuery orderByDescending:@"createdAt"];
  [fileQuery findObjectsInBackgroundWithBlock:^(NSArray *lists, NSError *error) {
    if(error==nil){
      if(lists!=nil && lists.count>0){
        AVObject *avObject = lists.firstObject;
        if(avObject !=nil){
          NSDictionary *dic = (NSDictionary *)[avObject objectForKey:@"localData"];
          NSString *patchCode = (NSString *)dic[@"patchCode"];
          if(dic[@"pathfile"]!=nil){
            AVFile *file = dic[@"pathfile"];
            NSString *downLoadURL = (NSString *)file.url;
            [[DownLoadTool defaultDownLoadTool] downLoadWithUrl:downLoadURL patchCode:patchCode serverAppVersion:version];
          }
        }
      }
    }
  }];
}

//獲取Bundle 路徑
-(NSString*)iOSFileBundlePath{
  NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString* path = [paths objectAtIndex:0];
  NSString* filePath = [path stringByAppendingPathComponent:@"/IOSBundle"];
  return  filePath;
}

//獲取版本信息儲存的文件路徑
-(NSString*)getVersionPlistPath{
  NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString* path = [paths objectAtIndex:0];
  NSString* filePath = [path stringByAppendingPathComponent:@"/PatchPlist/Version.plist"];
  return filePath;
}

//讀取plist文件數(shù)據(jù)
-(NSDictionary*)getDataFromIosBundlePlist{
  return [NSDictionary dictionaryWithContentsOfFile:[self getVersionPlistPath]];
}

//創(chuàng)建或修改版本信息
-(void)writeAppVersionInfoWithDictiony:(NSDictionary*)dictionary{
  NSString* filePath  = [self getVersionPlistPath];
  [dictionary writeToFile:filePath atomically:YES];
}
@end

下面就是拿到url后下載壓縮包,需要主要的是下載默認(rèn)是掛起的护桦,需要我們手動開啟[downloadTask resume];

#import <Foundation/Foundation.h>

@interface DownLoadTool : NSObject
@property (nonatomic, strong) NSString *zipPath;

+ (DownLoadTool *) defaultDownLoadTool;

//根據(jù)url下載相關(guān)文件
-(void)downLoadWithUrl:(NSString*)url patchCode:(NSString *)patchCode serverAppVersion:(NSString *)serverVersion;

@end

----------------------------------------------------------------------------------------= = ------------------------------


#import "DownLoadTool.h"
#import "ZipArchive.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;
}

-(void)downLoadWithUrl:(NSString*)url patchCode:(NSString *)patchCode serverAppVersion:(NSString *)serverVersion{
  //根據(jù)url下載相關(guān)文件
  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) {
    //獲取下載進(jìn)度
    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:@"BundleZip"];
    return [targetPathUrl URLByAppendingPathComponent:[response suggestedFilename]];
    
  } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    if(error){
      //下載出現(xiàn)錯誤
      NSLog(@"%@",error);
    }else{
      //下載成功
      if([filePath absoluteString].length>7){
        self.zipPath = [[filePath absoluteString] substringFromIndex:7];
      }
      //下載成功后更新本地存儲信息
      NSDictionary*infoDic=@{@"patchCode":serverVersion,@"appVerion":serverVersion,};
      [[UpdateDataLoader sharedInstance] writeAppVersionInfoWithDictiony:infoDic];
      //解壓并刪除壓縮包
        [self unZip];
    }
  }];
  [downloadTask resume];
}

//解壓壓縮包
-(void)unZip{
  if (self.zipPath == nil) {
    return;
  }
  //檢查Document里有沒有bundle文件夾
  NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  NSString* bundlePath = [path stringByAppendingPathComponent:@"/IOSBundle"];
   dispatch_queue_t _opQueue = dispatch_queue_create("cn.reactnative.hotupdate", DISPATCH_QUEUE_SERIAL);
  dispatch_async(_opQueue, ^{
    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"];
    [SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath progressHandler:^(NSString * _Nonnull entry, unz_file_info zipInfo, long entryNumber, long total) {
    } completionHandler:^(NSString * _Nonnull path, BOOL succeeded, NSError * _Nullable error) {
      //刪除壓縮包
      NSError* merror = nil;
      [[NSFileManager defaultManager] removeItemAtPath:self.zipPath error:&merror];
    }];
  });
}
@end

至此我們的熱更新就完成了贪染,我親測這種方法是可以完成熱更新的。demo
這篇文章參考了別人的文章催享,但是在實(shí)踐過程中發(fā)現(xiàn)有的寫得有問題杭隙,且有的地方考慮不是很全面,所以在此基礎(chǔ)上進(jìn)行了修改完善因妙,當(dāng)然我寫的這種也許也有問題痰憎,有發(fā)現(xiàn)的望提出,我也及時更正兰迫。
參考:
React-Native開發(fā)iOS篇-熱更新的實(shí)現(xiàn)
React-Native開發(fā)iOS篇-熱更新的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信殊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汁果,更是在濱河造成了極大的恐慌涡拘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件据德,死亡現(xiàn)場離奇詭異鳄乏,居然都是意外死亡跷车,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門橱野,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朽缴,“玉大人,你說我怎么就攤上這事水援∶芮浚” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵蜗元,是天一觀的道長或渤。 經(jīng)常有香客問我,道長奕扣,這世上最難降的妖魔是什么薪鹦? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮惯豆,結(jié)果婚禮上池磁,老公的妹妹穿的比我還像新娘。我一直安慰自己楷兽,他們只是感情好地熄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拄养,像睡著了一般离斩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘪匿,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天跛梗,我揣著相機(jī)與錄音,去河邊找鬼棋弥。 笑死核偿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顽染。 我是一名探鬼主播漾岳,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粉寞!你這毒婦竟也來了尼荆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤唧垦,失蹤者是張志新(化名)和其女友劉穎捅儒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巧还,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年鞭莽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麸祷。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡澎怒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阶牍,到底是詐尸還是另有隱情喷面,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布走孽,位于F島的核電站乖酬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏融求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一算撮、第九天 我趴在偏房一處隱蔽的房頂上張望生宛。 院中可真熱鬧,春花似錦肮柜、人聲如沸陷舅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莱睁。三九已至,卻和暖如春芒澜,著一層夾襖步出監(jiān)牢的瞬間仰剿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工痴晦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留南吮,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓誊酌,卻偏偏與公主長得像部凑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碧浊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容