首先感謝React Native 實現熱部署、差異化增量熱更新提供的方案,我只是在這個方案的基礎上封裝了一層實用一點的diff一鍵生成,便於提供diff包。
OK,一言不合先來段代碼。下面的代碼片段是生成diff-bundle的總體流程。
public void windowsDiffCreator(String desDir,String sourceProjectDir) {
//根據根路徑 創建對應的文件夾
prepareDir(desDir);
//清空newDir下的文件
deleteNewDirChildFiles();
//執行bundle打包命令
doReactNativeBundle(desDir, sourceProjectDir);
//與old進行diff算法
//將diff目錄下的文件都刪除掉
deleteDiffDirChildFiles();
//生成diff所有文件
createDiff();
//刪除old文件夾下所有資源
deleteOldDirChildFiles();
//將new文件夾下的所有資源copy到old中
copyNew2Old();
//壓縮diff文件夾到rootDir中
ZipUtils.zipMultiFile(getDiffFileDir(),getRootDir()+"/diff.zip",false);
}
根據外部傳入的desDir(生成diff相關的root目錄)和sourceProjectDir(project所在的目錄,目的是調用bundle打包的命令)執行以下幾步操作:
1、準備工作:在desDir目錄下,創建如下幾個文件夾
(1)、new:bundle打包命令執行後的目標路徑,裏面會有bundle文件、全量的資源文件
(2)、old:上一次執行完bundle打包命令後的bundle文件和全量的資源文件,目的是用作diff-bundle時的參考對比目錄
(3)、diff:new和old兩個文件夾對比後,生成的差量文件夾,裏面存放差量的diff-bundle文件和資源的所有差量文件
2、刪除new目錄下的所有文件,打包前先清空new路徑下的所有文件
3、執行bundle的打包命令:執行cmd命令,進入到當前project所在路徑,然後執行react-native bundle命令,執行bundle打包命令
4、將生成出來的new文件夾和old文件夾進行對比,生成diff文件夾
(1)、使用google提供的diff_match_patch,對bundle進行diff處理
(2)、對全量的資源進行diff算法,目前有侷限性:新的資源文件名稱必須區別於舊的資源文件名稱,即不能出現覆蓋文件的現象
5、生成diff文件完畢後,將old文件夾清空,並將new的文件夾下所有文件copy到old文件夾下
6、對diff文件夾進行壓縮處理,該壓縮文件就是被放到server端,供用戶下載的差量bundle文件
7、對生成的diff.zip文件進行MD5計算,生成MD5.txt,放到server端,供用戶下載判斷是否需要下載增量diff.zip包
下面着重介紹一下3、4兩步。
先看下第三步的核心代碼:
private static String BUNDLE_CMD = "cmd /c react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output %s/bundle/new/index.android.bundle --assets-dest %s/bundle/new/res/";//打bundle文件的StringFormat,目前是使用默認的index.android.js默認名稱 後續可以由用戶自主選擇名稱進行打包
private static String CD_SOURCE_PROJECT_DIR = "cmd /k cd %s";//進入項目的根路徑 用於執行打包命令的StringFormat
private static String CD_DISK = "cmd /k %s";//切換磁盤符的StringFormat
private void doReactNativeBundle(String desDir, String sourceProjectDir) {
//由於在windows平臺下進行的,首先獲取磁盤符 如D:
String diskDir = desDir.substring(0,2);
System.out.println(diskDir);
//Format切換磁盤符的標準字符串
String cmdDisk = String.format(CD_DISK,diskDir);
//開啓cmd process,準備執行部分命令
Runtime runtime = Runtime.getRuntime();
//Format進入到項目根路徑命令
String cdCMD = String.format(CD_SOURCE_PROJECT_DIR,sourceProjectDir);
//Format執行打包命令
String bundleCMD = String.format(BUNDLE_CMD,desDir,desDir);
System.out.println(cdCMD);
System.out.print(bundleCMD);
try {
runtime.exec(cmdDisk);//執行切換磁盤符命令
runtime.exec(cdCMD);//執行進入到項目根路徑命令
Process p = runtime.exec(bundleCMD);//執行打bundle文件命令
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), "GBK"));
String line = null;
while ((line = input.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
接下來再看一下第四步(將新打出來的bundle文件和資源文件與上一版本的bundle文件和資源文件進行diff運算)的核心代碼:
private void createDiff() {
//(1)、先生成diff-bundle文件到diff下
File newBundleFile = new File(getNewFileDir(),INDEX_ANDROID_BUNDLE);
if (!newBundleFile.exists()) {
System.out.println("新的bundle文件沒有生成");
return;
}
File oldBundleFile = new File(getOldFileDir(),INDEX_ANDROID_BUNDLE);
File diffBundleFile = new File(getDiffFileDir(),INDEX_ANDROID_BUNDLE);
if (!oldBundleFile.exists()) {
//沒有舊的bundle文件 創建一個空的bundle文件 進行diff處理
// FileUtils.copyFile(newBundleFile.getAbsolutePath(),diffBundleFile.getAbsolutePath());
try {
oldBundleFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//存在舊的bundle文件 進行diff算法進行處理
new BundleDiffCreator().createDiffBundle(oldBundleFile.getAbsolutePath(),newBundleFile.getAbsolutePath(),diffBundleFile.getAbsolutePath());
//(2)、生成資源的diff文件
File newResFile = new File(getNewFileDir(),RES_FILE_DIR_NAME);
File oldResFile = new File(getOldFileDir(),RES_FILE_DIR_NAME);
File diffResFile = new File(getDiffFileDir(),RES_FILE_DIR_NAME);
if (!oldResFile.exists() || oldResFile.listFiles().length == 0) {
//如果沒有舊的資源文件 直接把新的資源文件夾都copy到diff下
FileUtils.copyDir(newResFile.getAbsolutePath(),diffResFile.getAbsolutePath());
} else {
//如果存在舊的資源文件 進行diff運算 將不同的copy過去
for (File newRatioFile:newResFile.listFiles()) {
if (newRatioFile.isDirectory()) {
//100%是個directory 如mhdpi,xmhdpi......
File oldRatioFile = new File(oldResFile,newRatioFile.getName());
if (!oldRatioFile.exists()) {
oldRatioFile.mkdirs();
}
File[] oldRatioChildFiles = oldRatioFile.listFiles();
List<String> childFileNameList = new ArrayList<>();
for (File oldRatioChildFile:oldRatioChildFiles) {
childFileNameList.add(oldRatioChildFile.getName());
}
for (File newRatioChildFile : newRatioFile.listFiles()) {
if (childFileNameList.contains(newRatioChildFile.getName())) {
//如果包含 代表有這個文件 因爲目前不允許名稱相同的覆蓋文件
continue;
} else {
File diffRatioFile = new File(diffResFile,newRatioFile.getName());
if (!diffRatioFile.exists()) {
diffRatioFile.mkdirs();
}
File diffChildFile = new File(diffRatioFile,newRatioChildFile.getName());
FileUtils.copyFile(newRatioChildFile.getAbsolutePath(),diffChildFile.getAbsolutePath());
}
}
}
}
}
}