深入探索 Android 包体积优化(匠心制作二)

本文来自jsonchao的投稿,个人微信:bcce5360     

本文承接上文深入探索 Android 包体积优化(匠心制作一)

三、资源瘦身方案探索

总所周知,Android 构建工具链中使用了 AAPT 工具来对资源进行处理,Manifest、Resources、Assets 的资源经过相应的 ManifesMerger、ResourcesMerger、AssetsMerger 资源合并器将多个不同 moudule 的资源合并为了 MergedManifest、MergedResources、MergedAssets。然后,它们被 AAPT 处理后生成了 R.java、Proguard Configuration、Compiled Resources。如下图左上方所示:

其中 Proguard Configuration、Compiled Resources作用 如下所示:

  • Proguard Configuration:这是AAPT工具为Manifest中声明的四大组件与布局文件中使用的各种Views所生成的混淆配置,该文件通常存放在

    ${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt。
    
  • Compiled Resources:它是一个Zip格式的文件,这个文件的路径通常为 在经过 zip 解压之后,可以发现它 包含了res、AndroidManifest.xml和resources.arsc 这三部分。并且,从上面的 APK 构建流程中可以得知,Compiled Resources 会被 apkbuilder 打包到 APK 包中,它其实就是 APK资源包。因此,我们可以 通过 Compiled Resources 文件来修改不同后缀文件资源的压缩方式来达到瘦身效果的。但是需要注意的是,resources.arsc 文件最好不要压缩存储,如果压缩会影响一定的性能,尤其是在冷启动时间方面造成的影响。并且,如果在 Android 6.0 上开启了 android:extractNativeLibs=”false” 的话,So 文件也不能被压缩

  • ${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_。
    

1、冗余资源优化

1、使用 Lint 的 Remove Unused Resource

APK 的资源主要包括图片、XML,与冗余代码一样,它也可能遗留了很多旧版本当中使用而新版本中不使用的资源,这点在快速开发的 App 中更可能出现。我们可以通过点击右键,选中 Refactor,然后点击 Remove Unused Resource => preview 可以预览找到的无用资源,点击 Do Refactor 可以去除冗余资源。如下图所示:

需要注意的,Android Lint 不会分析 assets 文件夹下的资源,因为 assets 文件可以通过文件名直接访问,不需要通过具体的引用,Lint 无法判断资源是否被用到

2、优化 shrinkResources 流程真正去除无用资源

resources.arsc 中可能会存在很多 无用的资源映射,我们可以使用 android-arscblamer,它是一个命令行工具,能够 解析 resources.arsc  文件并检查出可以优化的部分,比如一些空的引用。

此外,当我们通过 shrinkResources true开启资源压缩,资源压缩工具只会把无用的资源替换成预定义的版本而不是移除。那么,如何高效地对无用资源自动进行去除呢?

我们可以 在 Android 构建工具执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现自动去除无用资源,具体的实现原理如下:

1)、首先,收集 Compiled Resources 中被替换的预定义版本的资源名称

通过查看 Zip 格式资源包中每个 ZipEntry 的 CRC-32 checksum 来寻找被替换的预定义资源,预定义资源的 CRC-32 定义在 ResourceUsageAnalyze 中,如下所示:

// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY
public static final long TINY_PNG_CRC = 0x88b2a3b0L;


// A 3x3 pixel PNG of type BufferedImage.TYPE_INT_ARGB with 9-patch markers
public static final long TINY_9PNG_CRC = 0x1148f987L;


// The XML document <x/> as binary-packed with AAPT
public static final long TINY_XML_CRC = 0xd7e65643L;
2)、然后,使用 android-chunk-utils  把 resources.arsc 中对应的定义移除。
3)、最后,删除资源包中对应的资源文件即可。

2、重复资源优化

在大型 App 项目的开发中,一个 App 一般会有多个业务团队进行开发,其中每个业务团队在资源提交时的资源名称可能会有重复的,这将会 引发资源覆盖的问题,因此,每个业务团队都会为自己的 资源文件名添加前缀。这样就导致了这些资源文件虽然 内容相同,但因为 名称的不同而不能被覆盖,最终都会被集成到 APK 包中。这里,我们还是可以 在 Android 构建工具执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现重复资源的去除,具体放入实现原理可细分为如下三个步骤:

  • 1)、首先,通过资源包中的每个ZipEntry的CRC-32 checksum来筛选出重复的资源

  • 2)、然后,通过android-chunk-utils修改resources.arsc,把这些重复的资源都重定向到同一个文件上

  • 3)、最后,把其它重复的资源文件从资源包中删除,仅保留第一份资源

具体的实现代码如下所示:

variantData.outputs.each {
    def apFile = it.packageAndroidArtifactTask.getResourceFile();


    it.packageAndroidArtifactTask.doFirst {
        def arscFile = new File(apFile.parentFile, "resources.arsc");
        JarUtil.extractZipEntry(apFile, "resources.arsc", arscFile);


        def HashMap<String, ArrayList<DuplicatedEntry>> duplicatedResources = findDuplicatedResources(apFile);


        removeZipEntry(apFile, "resources.arsc");


        if (arscFile.exists()) {
            FileInputStream arscStream = null;
            ResourceFile resourceFile = null;
            try {
                arscStream = new FileInputStream(arscFile);


                resourceFile = ResourceFile.fromInputStream(arscStream);
                List<Chunk> chunks = resourceFile.getChunks();


                HashMap<String, String> toBeReplacedResourceMap = new HashMap<String, String>(1024);


                // 处理arsc并删除重复资源
                Iterator<Map.Entry<String, ArrayList<DuplicatedEntry>>> iterator = duplicatedResources.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, ArrayList<DuplicatedEntry>> duplicatedEntry = iterator.next();


                    // 保留第一个资源,其他资源删除掉
                    for (def index = 1; index < duplicatedEntry.value.size(); ++index) {
                        removeZipEntry(apFile, duplicatedEntry.value.get(index).name);


                        toBeReplacedResourceMap.put(duplicatedEntry.value.get(index).name, duplicatedEntry.value.get(0).name);
                    }
                }


                for (def index = 0; index < chunks.size(); ++index) {
                    Chunk chunk = chunks.get(index);
                    if (chunk instanceof ResourceTableChunk) {
                        ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk;
                        StringPoolChunk stringPoolChunk = resourceTableChunk.getStringPool();
                        for (def i = 0; i < stringPoolChunk.stringCount; ++i) {
                            def key = stringPoolChunk.getString(i);
                            if (toBeReplacedResourceMap.containsKey(key)) {
                                stringPoolChunk.setString(i, toBeReplacedResourceMa
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值