侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

Android 性能监控框架 Matrix(7)资源优化

2022-07-30 星期六 / 0 评论 / 0 点赞 / 209 阅读 / 6939 字

上一篇文章中说到,除了插桩之外,Matrix 还会根据用户配置选择是否执行资源优化的功能,以删除不必要的资源文件。arsc 文件格式Matrix 资源优化的其中一个功能是裁剪 resources.ar

上一篇文章中说到,除了插桩之外,Matrix 还会根据用户配置选择是否执行资源优化的功能,以删除不必要的资源文件。

arsc 文件格式

Matrix 资源优化的其中一个功能是裁剪 resources.arsc,分析该功能之前,先简单了解一下 arsc 的文件格式。

首先介绍一下 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

文件结构如下图所示:

删除未使用的资源

下面开始分析 Matrix 是怎么执行资源优化的。

Matrix 首先会获取 apk 文件、R 文件、签名配置文件等文件信息:

String unsignedApkPath = output.outputFile.getAbsolutePath();removeUnusedResources(unsignedApkPath,        project.getBuildDir().getAbsolutePath() + "/intermediates/symbols/${variant.name}/R.txt",        variant.variantData.variantConfiguration.signingConfig);

接着,确定需要删除的资源信息,包括资源名及其 ID:

// 根据配置获取需要删除的资源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 文件,并且忽略不需要的资源:

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:

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 执行步骤如下:

  1. 获取 apk 文件、R 文件、签名配置文件等文件信息
  2. 根据用户提供的 unusedResource 文件及 R 文件确定需要删除的资源信息,包括资源名及其 ID
  3. 删除指定的资源,删除方法是在写入新的 apk 文件时,忽略该资源
  4. 如果启用了 shrinkArsc 功能,那么,修改 arsc 文件,移除掉已删除的资源信息,移除方法是将其 Entry 置为 null
  5. 其它数据原封不动地写入到新的 apk 文件中
.
.

广告 广告

评论区