zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    Asp.Net Web API 2第八课——Web API 2中的属性路由
    Asp.Net Web API 2第七课——Web API异常处理
    Asp.Net Web API 2第六课——Web API路由和动作选择
    Asp.Net Web API 2第五课——Web API路由
    开始学习python
    BMI 小程序 购物车
    深浅copy 文件操作
    字典 dict 集合set
    基本数据类型 (str,int,bool,tuple,)
    python 运算符
  • 原文地址:https://www.cnblogs.com/rayshen/p/5737293.html
Copyright © 2011-2022 走看看