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

来源:互联网 发布:小米网络机顶盒哪个好 编辑:程序博客网 时间:2024/06/06 19:44

简书链接: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