美文网首页RN程序员Android进阶之路
React-Native 热更新以及增量更新

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

作者: 越长越圆 | 来源:发表于2017-03-24 15:50 被阅读2513次

    Rn热更新以及增量更新操作

    原文链接:http://blog.csdn.net/qq_22329521/article/details/65631947
    不是增量更新,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
    

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

    ![PPS1H$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

    相关文章

      网友评论

        本文标题:React-Native 热更新以及增量更新

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