ReactNative 告別CodePush,自建熱更新版本升級環境


微軟的CodePush熱更新非常難用大家都知道,速度跟被牆了沒什么區別。

另外一方面,我們不希望把代碼放到別人的服務器。自己寫接口更新總歸感覺安全一點。

so,就來自己搞個React-Native APP的熱更新管理工具吧。暫且命名為hotdog。

 

/**************************************************/

首先我們要弄清react-native啟動的原理,是直接調用jslocation的jsbundle文件和assets資源文件。

由此,我們可以自己通過的服務器接口去判斷版本,並下載最新的然后替換相應的文件,然后從這個文件調用啟動APP。這就像之前的一些H5APP一樣做版本的管理。

 

以iOS為例,我們需要分以下幾步去搭建這個自己的RN升級插件:

一、設置默認jsbundle地址(比如document文件夾):

1.首先打包的時候把jsbundle和assets放入copy bundle resource,每次啟動后,檢測document文件夾是否存在,不存在則拷貝到document文件夾,然后給RN框架讀取啟動。

我們建立如下的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
}

 

二、做升級檢測,有更新則下載,然后對本地文件進行替換:

假如我們不立即做更新,可以更新后替換,然后不會影響本次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

 

 

三、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版本。

 

demo: https://github.com/rayshen/MXHotdog

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM