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

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

原文鏈接:http://blog.csdn.net/qq_22329521/article/details/65631947
不是增量更新脊串,Rn的熱更新状知,流程是下載服務(wù)器端上的一個解壓包到本地 解壓到應(yīng)用的文件目錄


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;
 }

開工

首先為我們舊的應(yīng)用打包
http://reactnative.cn/docs/0.42/signed-apk-android.html#content(react-native 中文網(wǎng)打包教程)

注意點

  • keystore放在android/app的目錄下

安裝apk

之后修改代碼 生成我們新的jsbundle 和圖片資源文件(更新必須是要附帶圖片的即使舊版本的資源已經(jīng)有了沪哺,也要重新下載)

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是沒法做熱更新的

增量更新(暫未實現(xiàn))

  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中的文件操作页慷,如果單純是文件的修改可以實現(xiàn), 在assets中通過InputStream的方式還未實現(xiàn)
  2. 資源的增量更新胁附,需要修改內(nèi)部的image加載的方式

資源的增量更新 需要看到圖片的加載方法

//這樣加載一張圖片 內(nèi)部的代碼
<Image source={require('./imgs/test.png')} />

在//image.android.js 中

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

//繼續(xù)查看 resolveAssetSource   

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

  var asset = AssetRegistry.getAssetByID(source);
  if (!asset) {
    return null;
  }
  //主要是AssetSourceResolver 這個對象傳遞了酒繁,這里看出非網(wǎng)絡(luò)圖片的時候,加載圖片的方式和bundle的路徑有關(guān)
  const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
  //這里應(yīng)該是圖片變換才會走的
  if (_customSourceTransformer) {
    return _customSourceTransformer(resolver);
  }
  //最后來到defaultAsset這個方法
  return resolver.defaultAsset();
}

 //是否是網(wǎng)絡(luò)圖片
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 {
    //這里開始加載網(wǎng)絡(luò)圖片
    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)
    );
  }

如果要實現(xiàn)資源的熱更新,思路是修改代碼加載圖片的路徑問題

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弓候,一起剝皮案震驚了整個濱河市郎哭,隨后出現(xiàn)的幾起案子他匪,更是在濱河造成了極大的恐慌,老刑警劉巖夸研,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邦蜜,死亡現(xiàn)場離奇詭異,居然都是意外死亡亥至,警方通過查閱死者的電腦和手機悼沈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姐扮,“玉大人井辆,你說我怎么就攤上這事∪芪眨” “怎么了杯缺?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長睡榆。 經(jīng)常有香客問我萍肆,道長,這世上最難降的妖魔是什么胀屿? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任塘揣,我火速辦了婚禮,結(jié)果婚禮上宿崭,老公的妹妹穿的比我還像新娘亲铡。我一直安慰自己,他們只是感情好葡兑,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布奖蔓。 她就那樣靜靜地躺著,像睡著了一般讹堤。 火紅的嫁衣襯著肌膚如雪吆鹤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天洲守,我揣著相機與錄音疑务,去河邊找鬼。 笑死梗醇,一個胖子當(dāng)著我的面吹牛知允,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叙谨,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼温鸽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唉俗?” 一聲冷哼從身側(cè)響起嗤朴,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虫溜,沒想到半個月后雹姊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡衡楞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年吱雏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘾境。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡歧杏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迷守,到底是詐尸還是另有隱情犬绒,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布兑凿,位于F島的核電站凯力,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏礼华。R本人自食惡果不足惜咐鹤,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圣絮。 院中可真熱鬧祈惶,春花似錦、人聲如沸扮匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棒搜。三九已至血久,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帮非,已是汗流浹背氧吐。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留末盔,地道東北人筑舅。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像陨舱,于是被迫代替她去往敵國和親翠拣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容