1. 程式人生 > >《ReactNative系列講義》高階篇---09.熱更新差量更新

《ReactNative系列講義》高階篇---09.熱更新差量更新

| 版權宣告:本文為博主原創文章,未經博主允許不得轉載。

一、簡介

通過前面幾篇文章我們已經實現了全量熱更新,這僅僅是實現了熱更新的第一步,全量更新的bundle包會稍顯大一些,差量更新就是給bundle包做瘦身。
大致思路如下:
釋出APP版本前,保留髮布版APP的bundle包;等再次更新的時候,手動打bundle包,將生成的bundle包和儲存的原始版本做差量動作,生成差量包;將生成的bundle差量包上傳至伺服器,修改bundle版本號;使用者端APP下載差量包,將差量包與APP本地正使用的bundle包進行合併,生成新的bundle;RN重新載入指定路徑的bundle包。
這裡討論的差量指的是程式碼檔案的差量,即index.android.bundle檔案的差量計算。圖片目前還是全量更新,如果做差量,需要修改RN原始碼,如果APP RN的版本需要升級,升級完成之後仍然需要再次修改RN原始碼,後續文章我們會講到。

二、程式碼實現

1. 生成差量包
  • 需將新舊bundle包轉換成String型別的資料
/**
 * 將檔案轉換成字串
 * FileReader
 * BufferedReader
 *
 * @param path 檔案路徑
 * @return
 */
public String readFileToString(String path) {
   String results = "";
   try {
       FileReader reader = new FileReader(path);
       BufferedReader bufferedReader = new
BufferedReader(reader); int result = bufferedReader.read(); StringBuilder sb = new StringBuilder(); while(result != -1) { sb.append((char) result); result = bufferedReader.read(); } bufferedReader.close(); reader.close(); results = sb.toString(); } catch
(FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return results; }
  • 使用Google diff_match_patch演算法庫,從官網下載最新的程式碼檔案,從中選擇java版本
/**
 * 生成差異補丁包
 *
 * @param oldSource 
 * @param newSource 
 * @return
 */
public String createPatch (String oldSource, String newSource) {
   diff_match_patch dmp = new diff_match_patch();
   // 對比新舊資源,獲取差異
   LinkedList<diff_match_patch.Diff> diffs = dmp.diff_main(oldSource, newSource);
   // 生成差異補丁包
   LinkedList<diff_match_patch.Patch> patches = dmp.patch_make(diffs);
   // 解析補丁包
   String patchesSource = dmp.patch_toText(patches);

   return patchesSource;
}
  • 需將生成的差異資料寫到檔案中
/**
 * 將字串寫入檔案
 * FileWriter
 * BufferedWriter
 *
 * @param source
 * @param path
 */
public void writeStringToFile(String source, File path) {
   try {
       FileWriter fileWriter = new FileWriter(path);
       BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
       bufferedWriter.write(source);
       bufferedWriter.close();
       fileWriter.close();
   } catch (IOException e) {
       e.printStackTrace();
   }
}
  • 編寫呼叫方法
/**
 * 生成檔案差量包
 */
private void createPatchFile () {
   File oldBundleFile = new File("本地檔案路徑");
   String oldBundleString = mFileUtils.readFileToString(oldBundleFile.getPath());

   File newBundleFile = new File("本地檔案路徑");
   String newBundleString = mFileUtils.readFileToString(newBundleFile.getPath());

   String patch = mFileUtils.createPatch(oldBundleString, newBundleString);
   File patchFile = new File("本地檔案路徑");

   mFileUtils.writeStringToFile(patch, patchFile);
}
  • 以上功能的實現可能需要你建立Java工程
2. 在Android工程中建立Google diff_match_patch演算法工具類
  • 演算法庫需要先行倒入
import com.facebook.react.bridge.ReactApplicationContext;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedList;

/**
 * Created by fangshifeng on 2018/1/19.
 */

public class GoogleDiffMatchPatchUtils {
    private static GoogleDiffMatchPatchUtils mInstance;
    private diff_match_patch dmp = null;
    private ReactApplicationContext mReactContext;

    private GoogleDiffMatchPatchUtils(ReactApplicationContext reactContext) {
        mReactContext = reactContext;

        if(dmp == null) {
            dmp = new diff_match_patch();
        }
    }

    // 多執行緒安全單例模式(雙重同步鎖)
    public static GoogleDiffMatchPatchUtils getInstance(ReactApplicationContext reactContext) {
        if(mInstance == null) {
            synchronized (GoogleDiffMatchPatchUtils.class) {
                if(mInstance == null) {
                    mInstance = new GoogleDiffMatchPatchUtils(reactContext);
                }
            }
        }

        return mInstance;
    }

    /**
     * 檔案合併
     *
     * @param assetsBundleFile
     * @param patchesFile
     */
    public void mergePatSourceWithAsset(String assetsBundleFile, String patchesFile, File newBundleFileDir) {
        // 轉換pat
        LinkedList<diff_match_patch.Patch> patchesList = (LinkedList<diff_match_patch.Patch>) dmp.patch_fromText(patchesFile);
        // 合併,生成新的bundle
        Object[] bundleArray = dmp.patch_apply(patchesList, assetsBundleFile);

        try {
            Writer writer = new FileWriter(newBundleFileDir);
            String newBundleFile = (String) bundleArray[0];
            writer.write(newBundleFile);
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3. 修改熱更新主流程
  • 修改構造方法
private GoogleDiffMatchPatchUtils mPatchUtils;

public VersionUpdateCheckModule(ReactApplicationContext reactContext) {
   super(reactContext);
   // 例項化演算法工具類
   mPatchUtils = GoogleDiffMatchPatchUtils.getInstance(reactContext);
}
  • 修改check方法
    在第9步之後新增相關的檔案操作
try {
    // 10. 獲取本地index.android.bundle檔案
    String assetsBundleFile = mFileUtils.readInputStreamToString(mReactContext.getAssets().open(Constants.NAME_JS_BUNDLE_JS_FILE));
    // 11. 獲取解壓後的bundle差量包
    String patchesFile = mFileUtils.readFileToString(jsBundleFolder.getPath() + "/" + Constants.NAME_BUNDLE_PATCH_FILE);

    if(!TextUtils.isEmpty(assetsBundleFile) && !TextUtils.isEmpty(patchesFile)) {
        // 12. 將本地bundle檔案和差量包進行合併
        File newBundleFile = new File(jsBundleFolder, Constants.NAME_JS_BUNDLE_JS_FILE);

        if(!newBundleFile.exists()) {
            newBundleFile.createNewFile();
        }

        mPatchUtils.mergePatSourceWithAsset(assetsBundleFile, patchesFile, newBundleFile);

        // 13.刪除.pat檔案
        new File(jsBundleFolder + "/" + Constants.NAME_BUNDLE_PATCH_FILE).delete();
    }
} catch (IOException error) {
    error.printStackTrace();
}
4. 修改JSBundle載入路徑
  • 將MainApplication檔案中載入JSbundle檔案的路徑修改為合併之後新bundle包路徑
@Override
protected String getJSBundleFile() {
    File bundleFile = new File(getCacheDir() + "/" + Constants.NAME_REACT_NATIVE_FOLDER + "/" + Constants.NAME_JS_BUNDLE_FILE_FOLDER, Constants.NAME_JS_BUNDLE_JS_FILE);
    if(bundleFile.exists()){
        return bundleFile.getAbsolutePath();
    }
    return super.getJSBundleFile();
}
5. 手動打包命令
bundle檔案打包命令詳解
react-native bundle 
--entry-file index.android.js 
--bundle-output ./android/app/src/main/assets/index.android.bundle
--platform android 
--assets-dest ./bundle 
--dev false

引數含義
--entry-file(入口檔案):android平臺是index.android.js;iOS平臺是index.ios.js
--bundle-output(bundle檔案輸出路徑): 專案根目錄/android/app/src/main/assets/index.android.bundle
--platform(平臺):android
--assets-dest(圖片資源輸出目錄): 專案根目錄/bundle
--dev(是否是開發版本)

三、總結

如果沒有疏漏的話,Android平臺RN熱更新差量更新(程式碼部分,即index.android.bundle檔案)算是全部實現了。後續還有一些地方可以優化升級:版本升級功能可放入子執行緒;圖片更新做成差量。

四、文章參考