Android 性能監控框架 Matrix(7)資源優化

上一篇文章中說到,除了插樁以外,Matrix 還會根據用戶配置選擇是否執行資源優化的功能,以刪除沒必要要的資源文件。android

arsc 文件格式

Matrix 資源優化的其中一個功能是裁剪 resources.arsc,分析該功能以前,先簡單瞭解一下 arsc 的文件格式。markdown

首先介紹一下 arsc 文件中的幾個概念:優化

  1. Chunk,指一個數據塊,下面介紹的 Table、Package、String Block、Type 的都是 Chunk,都有文件頭、類型、size、對齊填充等信息
  2. Resource Table,一個 arsc 文件對應一個 Resource Table
  3. Package,用於描述一個包,一個 Table 對應多個 Package,而 packageID 便是資源 resID 的最高八位,通常來講系統 android 的是 1(0x01),普通的例如 com.tencent.mm 會是 127(0x7f),剩下的是從 2 開始起步,也能夠在 aapt 中指定。
  4. String Block,一個 Table 有一個全局的字符串資源池,一個 Package 有一個存儲資源類型的字符串資源池,一個儲存資源名的字符串資源池
  5. Resource Type,資源類型,好比 attr、drawable、layout、id、color、anim 等,一個 Package 對應多個 Type
  6. Config,用於描述資源的維度,例如橫豎屏,屏幕密度,語言等,一個 Type 對應一個 Config
  7. Entry,一個 Type 對應多個 Entry,例如 drawable-mdpi 中有 icon1.png、icon2.png 兩個 drawable,那在 mdpi 這個 Type 中就存在兩個 entry

文件結構以下圖所示:ui

arsc 文件結構

刪除未使用的資源

下面開始分析 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 文件結構:

arsc 文件結構

RemoveUnusedResourcesTask 執行步驟以下:

  1. 獲取 apk 文件、R 文件、簽名配置文件等文件信息
  2. 根據用戶提供的 unusedResource 文件及 R 文件肯定須要刪除的資源信息,包括資源名及其 ID
  3. 刪除指定的資源,刪除方法是在寫入新的 apk 文件時,忽略該資源
  4. 若是啓用了 shrinkArsc 功能,那麼,修改 arsc 文件,移除掉已刪除的資源信息,移除方法是將其 Entry 置爲 null
  5. 其它數據原封不動地寫入到新的 apk 文件中
相關文章
相關標籤/搜索