React-Native 热更新以及增量更新

简书链接:http://www.jianshu.com/p/7503a7ad093f

不是增量更新,Rn的热更新,流程是下载服务器端上的一个解压包到本地 解压到应用的文件目录


BC%G_~)({H({UJ8Q2(7Z%RA.png

这是一个打包后的apk文件,在Rn中我们的js代码都是打包后存放在assets目录中,其中index.android.bundle,可以理解我们js写后打包的代码文件


7ZPWOH$N0YZ8A@5{9MEYYH4.png

其中Rn加载bundle 的文件的代码片段在ReactNativeHost,在MainApplication中就为我们初始化好了

protected ReactInstanceManager createReactInstanceManager() {
    ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModuleName(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setUIImplementationProvider(getUIImplementationProvider())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }

    //这是可以重写的方法,为我们提供重写获取bundleFile的方法
    String jsBundleFile = getJSBundleFile();
    if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
    } else {
      //加载assets目录下的文件
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
    }
    return builder.build();
  }
 public Builder setBundleAssetName(String bundleAssetName) {
      mJSBundleAssetUrl = (bundleAssetName == null ? null : "assets://" + bundleAssetName);
      mJSBundleLoader = null;
      return this;
 }

开工

首先为我们旧的应用打包
http://reactnative.cn/docs/0.42/signed-apk-android.html#content(react-native 中文网打包教程)

注意点

  • keystore放在android/app的目录下

安装apk

之后修改代码 生成我们新的jsbundle 和图片资源文件(更新必须是要附带图片的即使旧版本的资源已经有了,也要重新下载)

react-native bundle --platform android --dev false --reset-cache --entry-file index.android.js --bundle-output E:\test\index.android.bundle   --assets-dest E:\test

生成后的 文件 对其进行生成压缩包

P[PS1H$2PO~~8{P7~)9GRCL.png

注意点

  • 因为使用的zipinputStream 这个api 如果是生成rar解压包后改成zip 可能获取不到getNextEntry() 所以最好是直接生成zip格式的解压包

代码部分

    private String bundleParentPath = null;
    private String bundlePath = null;
    private String bundleName = "index.android.bundle";
    private File bundleFile = null;
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(),
                    //无视这个
                    new GankViewManager()
            );
        }

        @Override
        protected String getJSMainModuleName() {
            return super.getJSMainModuleName();
        }

        @Nullable
        @Override
        protected String getBundleAssetName() {
            String bundleName = "index.android.bundle";

            if (bundleFile != null && bundleFile.exists()) {
                Log.d(TAG, "assets bundle exit");
                return null;
            }
            //            Logger.d("assets bundle does not exit");
            return bundleName;
        }

        @Nullable
        @Override
        protected String getJSBundleFile() {
            if (bundleFile != null && bundleFile.exists()) {
                Log.d(TAG, "js bundle file " + bundleFile.getPath());
                return bundleFile.getPath();
            }
            return null;
        }
    };

 @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
         //adb push到sd卡中
         File file = new File(Environment.getExternalStorageDirectory().getAbsoluteFile() + "/test.zip");
        if (file.exists()) {
            try {
                ZipUtils.unzip(Environment.getExternalStorageDirectory().getAbsoluteFile() + "/test.zip", Environment.getExternalStorageDirectory().getAbsoluteFile()  + "/bundle");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        bundleParentPath = Environment.getExternalStorageDirectory().getAbsoluteFile() + "/bundle";
        bundlePath = bundleParentPath + File.separator + bundleName;
        bundleFile = new File(bundlePath);
    }
public class ZipUtils {
    private final static int BUFFER_SIZE = 1 << 12;
    public static void unzip(String zipFilePath, String destDirectory) throws Exception {
        File destDir = new File(destDirectory);
        if (destDir.exists()) {
            destDir.delete();
        }
        destDir.mkdir();
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath));
        ZipEntry zipEntry = zipInputStream.getNextEntry();
        while (zipEntry != null) {
            String filePath = destDirectory + File.separator + zipEntry.getName();
            if (!zipEntry.isDirectory()) {
                extractFiles(zipInputStream, filePath);
            } else {
                File dir = new File(filePath);
                dir.mkdir();
            }
            zipInputStream.closeEntry();
            zipEntry = zipInputStream.getNextEntry();
        }
        zipInputStream.close();
    }

    private static void extractFiles(ZipInputStream inputStream, String path) throws IOException {
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(path));
        byte[] bytes = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, read);
        }

        outputStream.close();
    }
}

上面的方式是在Application中替换掉加载的JSBundle ,图片资源和代码最好在一个目录下

~EXPZ02S(B0K47P9H2TO50X.png

如果文件被情况,默认加载assets下的原始的bundle

注意点

  • 原始的Android 代码打包成dex是没法做热更新的

增量更新(暂未实现)

  1. index.android.bundle文件增量更新:使用Google的google-diff-match-patch对比老版本的index.android.bundle文件和新版本的index.android.bundle文件生成一个补丁包,客户端下载后与assets中的文件合并,由于google-diff-match-patch 适合字符串文本的对比,在这里 使用的jbdiff这个来进行更新 https://github.com/jdesbonnet/jbdiff/tree/master/src/ie/wombat/jbdiff,需要注意的是 在Android中assets中的文件操作,如果单纯是文件的修改可以实现, 在assets中通过InputStream的方式还未实现
  2. 资源的增量更新,需要修改内部的image加载的方式

资源的增量更新 需要看到图片的加载方法

//这样加载一张图片 内部的代码
<Image source={require('./imgs/test.png')} />

在//image.android.js 中

render: function() {
    const source = resolveAssetSource(this.props.source);
    const loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource);
    ....
 }

//继续查看 resolveAssetSource   

function resolveAssetSource(source: any): ?ResolvedAssetSource {
  if (typeof source === 'object') {
    return source;
  }

  var asset = AssetRegistry.getAssetByID(source);
  if (!asset) {
    return null;
  }
  //主要是AssetSourceResolver 这个对象传递了,这里看出非网络图片的时候,加载图片的方式和bundle的路径有关
  const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
  //这里应该是图片变换才会走的
  if (_customSourceTransformer) {
    return _customSourceTransformer(resolver);
  }
  //最后来到defaultAsset这个方法
  return resolver.defaultAsset();
}

 //是否是网络图片
function getDevServerURL(): ?string {
  if (_serverURL === undefined) {
    var scriptURL = SourceCode.scriptURL;
    var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
    if (match) {
      // Bundle was loaded from network
      _serverURL = match[0];
    } else {
      // Bundle was loaded from file
      _serverURL = null;
    }
  }
  return _serverURL;
}

//加载bunle的路径
function getBundleSourcePath(): ?string {
  if (_bundleSourcePath === undefined) {
    const scriptURL = SourceCode.scriptURL;
    if (!scriptURL) {
      // scriptURL is falsy, we have nothing to go on here
      _bundleSourcePath = null;
      return _bundleSourcePath;
    }
    if (scriptURL.startsWith('assets://')) {
      // running from within assets, no offline path to use
      _bundleSourcePath = null;
      return _bundleSourcePath;
    }
    if (scriptURL.startsWith('file://')) {
      // cut off the protocol
      _bundleSourcePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
    } else {
      _bundleSourcePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
    }
  }

  return _bundleSourcePath;
}

  defaultAsset(): ResolvedAssetSource {
    //这里开始加载网络图片
    if (this.isLoadedFromServer()) {
      return this.assetServerURL();
    }

    if (Platform.OS === 'android') {
      //加载本地图片,如果是离线文件 加载drawableFolderInBundle这个方法,而这个方法是bundle和资源文件在一个目录下
      return this.isLoadedFromFileSystem() ?
        this.drawableFolderInBundle() :
        this.resourceIdentifierWithoutScale();
    } else {
      return this.scaledAssetPathInBundle();
    }
  }
 drawableFolderInBundle(): ResolvedAssetSource {
    const path = this.bundlePath || '';
    return this.fromSource(
      'file://' + path + getAssetPathInDrawableFolder(this.asset)
    );
  }

如果要实现资源的热更新,思路是修改代码加载图片的路径问题

参考文章:http://blog.csdn.net/shandian000/article/details/54582603
http://blog.csdn.net/u011050541/article/details/52703209
http://www.cnblogs.com/liubei/p/RNUpdate.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值