上一篇文章中說到,除了插樁以外,Matrix 還會根據用戶配置選擇是否執行資源優化的功能,以刪除沒必要要的資源文件。android
Matrix 資源優化的其中一個功能是裁剪 resources.arsc,分析該功能以前,先簡單瞭解一下 arsc 的文件格式。markdown
首先介紹一下 arsc 文件中的幾個概念:優化
文件結構以下圖所示:ui
下面開始分析 Matrix 是怎麼執行資源優化的。spa
Matrix 首先會獲取 apk 文件、R 文件、簽名配置文件等文件信息:code
String unsignedApkPath = output.outputFile.getAbsolutePath();
removeUnusedResources(unsignedApkPath,
project.getBuildDir().getAbsolutePath() + "/intermediates/symbols/${variant.name}/R.txt",
variant.variantData.variantConfiguration.signingConfig);
複製代碼
接着,肯定須要刪除的資源信息,包括資源名及其 ID:orm
// 根據配置獲取須要刪除的資源
Set<String> ignoreRes = project.extensions.matrix.removeUnusedResources.ignoreResources;
Set<String> unusedResources = project.extensions.matrix.removeUnusedResources.unusedResources;
Iterator<String> iterator = unusedResources.iterator();
String res = null;
while (iterator.hasNext()) {
res = iterator.next();
if (ignoreResource(res)) { // 指定忽略的資源不須要刪除
iterator.remove();
}
}
// 讀取 R 文件,保存資源名及對應的 ID 到 resourceMap 中
Map<String, Integer> resourceMap = new HashMap();
Map<String, Pair<String, Integer>[]> styleableMap = new HashMap();
File resTxtFile = new File(rTxtFile);
readResourceTxtFile(resTxtFile, resourceMap, styleableMap); // 讀取 R 文件
// 將 unusedResources 中的資源放到 removeResources 中
Map<String, Integer> removeResources = new HashMap<>();
for (String resName : unusedResources) {
if (!ignoreResource(resName)) {
// 若是資源會被刪除,那麼將它從 resourceMap 中移除
removeResources.put(resName, resourceMap.remove(resName));
}
}
複製代碼
以後就能夠刪除指定的資源了,刪除方法是建立一個新的 apk 文件,而且忽略不須要的資源:ip
for (ZipEntry zipEntry : zipInputFile.entries()) {
if (zipEntry.name.startsWith("res/")) {
String resourceName = entryToResouceName(zipEntry.name);
if (removeResources.containsKey(resourceName)) { // 須要刪除的資源不會寫入到新文件中
continue;
} else { // 將正常的資源信息寫入到新的 apk 文件中
addZipEntry(zipOutputStream, zipEntry, zipInputFile);
}
}
}
複製代碼
若是啓用了 shrinkArsc 功能,那麼,還須要修改 arsc 文件,移除掉已刪除的資源信息:資源
if (shrinkArsc && zipEntry.name.equalsIgnoreCase("resources.arsc") && unusedResources.size() > 0) {
File srcArscFile = new File(inputFile.getParentFile().getAbsolutePath() + "/resources.arsc");
File destArscFile = new File(inputFile.getParentFile().getAbsolutePath() + "/resources_shrinked.arsc");
// 從 arsc 文件中讀取資源信息
ArscReader reader = new ArscReader(srcArscFile.getAbsolutePath());
ResTable resTable = reader.readResourceTable();
// 遍歷須要刪除的資源列表,將對應的資源信息從 arsc 文件中移除
for (String resName : removeResources.keySet()) {
ArscUtil.removeResource(resTable, removeResources.get(resName), resName);
}
// 將裁剪後的 ResTable 寫入到新的 arsc 文件中
ArscWriter writer = new ArscWriter(destArscFile.getAbsolutePath());
writer.writeResTable(resTable);
// 將裁剪後的 arsc 文件寫入到新的 apk 中
addZipEntry(zipOutputStream, zipEntry, destArscFile);
}
複製代碼
移除的方法是將其 Entry 置爲 null:rem
public static void removeResource(ResTable resTable, int resourceId, String resourceName) throws IOException {
ResPackage resPackage = findResPackage(resTable, getPackageId(resourceId)); // 找到該 resId 對應的 package
if (resPackage != null) {
List<ResType> resTypeList = findResType(resPackage, resourceId);
for (ResType resType : resTypeList) { // 遍歷 package 中的 ResType,找到對應類型
int entryId = getResourceEntryId(resourceId); // 再找到對應的 entry
resType.getEntryTable().set(entryId, null); // 設置爲 null
resType.getEntryOffsets().set(entryId, ArscConstants.NO_ENTRY_INDEX);
resType.refresh();
}
resPackage.refresh();
resTable.refresh();
}
}
複製代碼
以上,移除沒必要要的資源後的新的 apk 文件就寫入完畢了。
arsc 文件結構:
RemoveUnusedResourcesTask 執行步驟以下: