• 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

     

  • 相关阅读:
    数组操作
    HTML CSS 笔记
    jacascript 滚动scroll
    SEO优化技巧
    STP选举规则和例题
    3.1GSM-R的网络组成
    光缆的型号
    光缆的种类
    fdisk命令分区过程
    文件系统管理--挂载光盘与U盘
  • 原文地址:https://www.cnblogs.com/rayshen/p/5737293.html
Copyright © 2020-2023  润新知