React-Native 不使用第三方服务实现iOS&A

作者: sands_yu | 来源:发表于2017-09-07 20:04 被阅读235次

流程图:


简单实现:

iOS Demo地址:https://github.com/yushengchu/Incremental-hot-update

// 热更细管理类
//  HotUpdataManage.h
//  hotUpdataDemo
//
//  Created by joker on 2017/9/7.
//  Copyright © 2017年 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@interface HotUpdataManage : NSObject

//单例方法
+ (HotUpdataManage*)getInstance;

//获取加载URL
- (NSURL*)getBridge;

//检查更新
- (void)checkUpdate:(NSString*)checkUrl;

//bridge对象 用于重新载入jsbundle
@property (nonatomic,strong) RCTBridge *bridge;

@end

//
//  HotUpdataManage.m
//  hotUpdataDemo
//
//  Created by joker on 2017/9/7.
//  Copyright © 2017年 Facebook. All rights reserved.
//

#import "HotUpdataManage.h"
#import "MXHZIPArchive.h"
#import "DiffPatch.h"


#define HOT_MAIN_DOC_PATH [NSString stringWithFormat:@"%@/HOTSDK/main",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]]
#define HOT_JS_PATH [NSString stringWithFormat:@"%@/HOTSDK/main/%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0],@"main.jsbundle"]

@implementation HotUpdataManage

+ (HotUpdataManage*)getInstance{
  static HotUpdataManage *manager;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    manager = [[HotUpdataManage alloc] init];
  });
  return manager;
}

#pragma mark 获取bridge
- (NSURL*)getBridge{
  NSFileManager *fileManager =[NSFileManager defaultManager];
  if (![fileManager fileExistsAtPath:HOT_JS_PATH]) {
    NSString* zipPatch = [[NSBundle mainBundle] pathForResource:@"bundle" ofType:@"zip"];
    NSLog(@"zipPatch ---> %@",zipPatch);
    if([fileManager fileExistsAtPath:zipPatch]){
      BOOL isReload = [self unzipBundleAndReload:zipPatch];
      //复制jsbundle文件和assest文件到到对应目录
      if (isReload) {
        return [NSURL URLWithString:HOT_JS_PATH];
      }
    }
  }
  return [NSURL URLWithString:HOT_JS_PATH];
}

#pragma mark 检查更新
- (void)checkUpdate:(NSString*)urlStr{
    NSURL *url = [NSURL URLWithString: urlStr];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10];
    [request setHTTPMethod: @"GET"];
    NSData   *data = [NSURLConnection sendSynchronousRequest:request
                                           returningResponse:nil
                                                       error:nil];
    if (data){
      NSDictionary *resultInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
      if ([resultInfo[@"isUpdate"] boolValue]) {
        [self downLoadFile:[resultInfo objectForKey:@"updataUrl"]];
      }
    }
}

#pragma mark 下载
- (void)downLoadFile:(NSString *)urlString{
  NSURL *url = [NSURL URLWithString:urlString];
  NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10];
  [request setHTTPMethod: @"GET"];
  NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
  if (data){
    NSLog(@"diff下载成功");
    NSString *pacthPath = [self getFilePath:@"diff.patch"];
    if ([data writeToFile:pacthPath atomically:YES]) {
      [self patchBundle:pacthPath];
    }else{
      NSLog(@"diff保存失败.");
    }
  }
  else {
    NSLog(@"diff下载失败");
  }
}

- (void)patchBundle:(NSString*)pacthPath{
  NSString* zipPatch = [[NSBundle mainBundle] pathForResource:@"bundle" ofType:@"zip"];
  NSString* tmpZipPath = [NSString stringWithFormat:@"%@/tmp.zip",HOT_MAIN_DOC_PATH];
  //  创建最新jsbundel文件
  BOOL writeBundel =   [DiffPatch beginPatch:pacthPath origin:zipPatch toDestination:tmpZipPath];
  if (!writeBundel) {
    NSLog(@"bundel写入失败");
    return;
  }
  if ([self unzipBundleAndReload:tmpZipPath]) {
    [self reloadNow];
  }
  NSLog(@"更新成功");
}

#pragma mark 解压
-(BOOL)unzipBundleAndReload:(NSString*)zipPath{
  //获取zipPath
  NSError *error;
  NSString *reload_zipPath = zipPath;
  NSString *desPath = HOT_MAIN_DOC_PATH;
  BOOL pathExist = [[NSFileManager defaultManager] fileExistsAtPath:desPath];
  if(!pathExist){
    [[NSFileManager defaultManager] createDirectoryAtPath:desPath withIntermediateDirectories:YES attributes:nil error:nil];
  }
  //  NSLog(@"start unzip zip Path:%@",reload_zipPath);
  [MXHZIPArchive unzipFileAtPath:reload_zipPath toDestination:desPath overwrite:YES password:nil error:&error];
  if(!error){
    NSLog(@"解压成功,路径:%@",desPath);
    return true;
  }else{
    NSLog(@"解压失败,路径:%@,错误原因:%@",desPath,[error description]);
    return false;
  }
}

#pragma mark 重新加载
-(void)reloadNow{
  [self.bridge reload];
  NSLog(@"Bridge reload");
}

- (NSString*)getFilePath:(NSString*)fileName{
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentsDirectory =[paths objectAtIndex:0];
  NSString *filePath =[documentsDirectory stringByAppendingPathComponent:fileName];
  return filePath;
}

@end

//AppDelegate中使用

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "HotUpdataManage.h"

@interface AppDelegate()

@property (nonatomic,strong) RCTBridge *bridge;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  HotUpdataManage* manage = [HotUpdataManage getInstance];
  NSURL* localUrl = nil;
  #ifdef  DEBUG
    localUrl = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
  #else
    localUrl = [manage getBridge];
  #endif
  //使用bridge的方式创建rootView
  _bridge = [[RCTBridge alloc] initWithBundleURL:localUrl moduleProvider:nil launchOptions:nil];
  manage.bridge = _bridge;
  
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge moduleName:@"hotUpdataDemo" 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];
  
  //检查更新 这里使用的是阿里的rap mock接口
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [manage checkUpdate:@"http://rapapi.org/mockjsdata/13203/checkUpdate"];
  });
  
  return YES;
}


@end

后言

包括pushy在内的许多支持增量热更新的类库,实际上只是对jsbundle文件进行了diff算法.

图片是通过在服务器进行筛选获取新增或者更改的图片,然后与jsbundle的diff文件一同打包成一个压缩包给客户端进行热更新.

本Demo采用的是对整个bundle.zip文件进行diff,直接获得一个针对旧版bundle.zip文件的热更新文件.

iOS打包中,会默认将jsbundle和assest文件夹打入ipa包中,并不会将bundle.zip文件打入.

这一块需要通过打包脚本来实现默认将bundle.zip打入ipa包内.

同时通过将Xcode -- targets -- Build Phases -- Bundle React Native code and images中的shell脚本删除,可以在打包时候不将jsbundle和assest文件夹打入包内.

在第一次打开APP时使用默认打入的bundle.zip来解压获得jsbundle和assest运行React-Native项目.

相关文章

网友评论

  • paintingStyle:作者你好,我们公司需要做H5网页资源的增量更新,这种情景属于热更新吗,会影响上架Appstore吗?
    sands_yu:@paintingStyle H5没有了解过这方面 应该也是通过diff算法算差值包合并 具体你可以试着谷歌搜一下 应该能有结果
    paintingStyle:@sands_yu H5压缩文件的增量更新有什么好的实现方法?就使用React-Native ?
    sands_yu:@paintingStyle H5网页的资源加载都是来自服务器 这种场景跟文章描述的不一样 不影响上架app store
  • 不爱做饭的码奴就不是合格的猫:你好,请问,Android 热更新有没有?
    sands_yu:@樹影 安卓热更新同理 按照demo对应流程实现方法就可以了 安卓打包默认就有bundle.zip

本文标题:React-Native 不使用第三方服务实现iOS&A

本文链接:https://www.haomeiwen.com/subject/cxhwjxtx.html