《ReactNative系列講義》高階篇---09.熱更新差量更新
阿新 • • 發佈:2019-02-16
| 版權宣告:本文為博主原創文章,未經博主允許不得轉載。
一、簡介
通過前面幾篇文章我們已經實現了全量熱更新,這僅僅是實現了熱更新的第一步,全量更新的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檔案)算是全部實現了。後續還有一些地方可以優化升級:版本升級功能可放入子執行緒;圖片更新做成差量。