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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容