react-native-code-push首次增量热更优化

4,815 阅读3分钟

前言

code-push在首次热更的时候会下载完整的Bundle压缩产物,非首次热更的时候会增量资源(diff图片).jsbundle文件还是完整的。 通过观察我们的热更包首次已经达到了20mb(已经是压缩后的)是一个很恐怖的事情。 用户在弱网的环境下失败几率就会增大,并且不能及时热更,还浪费用户的流量。完整示例

思考

起初想到了拆包的方案,在解决多包资源共享的问题上,发现热更前diff资源效果更好。

分包优化

  • 主要分为基础包和业务包,每次热更的时只需要热更业务包能大大减少基础、无关联业务包重复的体积。

  • 遇到的问题:分包图片等资源共享,业务包重启立即生效,热更下载文件夹隔离改造。

  • 使用Metro进行分包,对这个感兴趣的可以看下 招商证券 react-native 热更新优化实践

  • 在处理分包图片资源共享的时候发现影响包体积主要还是在图片等资源上,了解到RN图片路径寻址流程是根据jsbundle所在的一层决定加载图片路径,code-push首次会全量下载到沙盒,实际上在热更后APP内存在2份资源文件。

  • 既然这样我们是否可以修改RN的图片加载寻址方案,判断jsbundle所在一层路径查找,没有再去APP内获取。如是就有了热更diff资源的方案。

热更diff资源

  • 首先修改AssetSourceResolver.js的defaultAsset方法,判断当前jsbundle层取图片不存在时就去APP内获取。

  • 需要一个仓库保存发布版本时的Bundle文件,热更时进行diff使用。

  • 编写脚本 react-native bundle 完成后与发布版本diff生成新的差量文件,再进行 code-push release 上传。

开始

先来简单了解一下RN Image的加载流程

  • 图片路径会经过resolveAssetSource方法处理
// Image.ios.js
const source = resolveAssetSource(props.source) || {
    uri: undefined,
    width: undefined,
    height: undefined,
};
...
function resolveAssetSource(source: any): ?ResolvedAssetSource {
  if (typeof source === 'object') {
    // {uri: 'xxx.png'} 直接返回
    return source;
  }

  // require('xxx.png') number 进行处理
  const asset = AssetRegistry.getAssetByID(source);
  if (!asset) {
    return null;
  }

  const resolver = new AssetSourceResolver(
    getDevServerURL(),
    getScriptURL(),
    asset,
  );
  if (_customSourceTransformer) {
    return _customSourceTransformer(resolver);
  }
  return resolver.defaultAsset();
}
  /**
   * If the jsbundle is running from a sideload location, this resolves assets relative to its location
   * E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png'
   */
  drawableFolderInBundle(): ResolvedAssetSource {
    const path = this.jsbundleUrl || 'file://';
    return this.fromSource(path + getAssetPathInDrawableFolder(this.asset));
  }
  
   /**
   * The default location of assets bundled with the app, located by resource identifier
   * The Android resource system picks the correct scale.
   * E.g. 'assets_awesomemodule_icon'
   */
  resourceIdentifierWithoutScale(): ResolvedAssetSource {
    invariant(
      Platform.OS === 'android',
      'resource identifiers work on Android',
    );
    return this.fromSource(
      assetPathUtils.getAndroidResourceIdentifier(this.asset),
    );
  }
  ...
  defaultAsset(): ResolvedAssetSource {
    // 本地调试
    if (this.isLoadedFromServer()) {
      return this.assetServerURL();
    }

    if (Platform.OS === 'android') {
      return this.isLoadedFromFileSystem()
        ? this.drawableFolderInBundle() // 从本地drawable加载
        : this.resourceIdentifierWithoutScale(); // 从assets加载
    } else {
      // bundle包位置assets文件夹
      return this.scaledAssetURLNearBundle();
    }
  }

If the jsbundle is running from a sideload location, this resolves assets relative to its location 图片加载路径是跟随jsbundle所在一层目录查找的。

defaultAsset hook

  • 原生新增 iOS RNPAssetsLoad Android AssetsLoadModule 桥架类

  • 提供DefaultMainBundlePath 常量jsbundle默认路径

  • searchDrawableFile方法 获取bundle目录下所有图片资源路径

  • isFileExist方法 判断图片资源是否存在

  • js新增 AssetsLoader.js 自定义图片加载方式

  • 在入口js文件调用 AssetsLoader.initAssetsLoader();

修改打包脚本

  • 新增 saveHashJson.js node脚本,打包完成调用

  • 传入参数路径、分支名称、环境、平台(后面3个参数可以作为包唯一性记录,方便后面的热更diff clone源包数据)

  • 需要准备一个git仓库存放每个版本的Bundle数据会递归文件存储文件hash值

  • 完整脚本 每次发布打包前执行 sh ./shell/build.android.sh

修改热更脚本

  • 新增 diff.js node脚本

  • 每次热更的时候和远程存储Bundle仓库进行递归留下修改过和新增的文件

  • 完整脚本 执行 sh ./shell/codepush.sh

合集

Demo

react-native-code-push非首次热更bundle增量

React-Native 热更新code-push-server小白一步步手动搭建

参考

源码解读RCTImageView(iOS)

CodePush优化之减小更新包体积