返回

自动打包时批量获取全部资源,优雅地优化APK包大小

Android

自动化获取 Android 资源大小优化包体积

随着移动应用开发项目不断迭代,对自动化工具的需求也随之增加。本文将重点探讨 Android 项目中如何自动化获取资源大小,从而优化包体积并提高打包效率。

开发背景

一个典型的 Android 应用项目包含多种类型的资源,包括图片、字体、矢量图、动画、布局文件和 AndroidManifest.xml。这些资源在编译后会被打包成一组 dex 文件和 res 文件。其中,res 文件包含了所有上述资源以及自动生成的派生资源(例如 9.png 格式的图片)。因此,获取 res 文件的大小对于优化资源文件至关重要。

方案调研

目前,获取 res 资源大小的方法有多种。网上也有不少相关的博客文章,但大部分都未能考虑到项目中可能存在多 flavor 的情况,导致过滤规则不准确。

方案一:使用 Gradle 脚本动态获取 flavor 对应的 res 资源大小

def countResSize(File resDir) {
    def size = 0
    resDir.eachFile { file ->
        size += file.length()
    }
    return size
}

android {
    applicationVariants.all { variant ->
        variant.resProviderConfigs.each { config ->
            def configName = config.name
            println configName + ": " + countResSize(variant.mergeResourcesOutputDirectory.resDir) + " bytes"
        }
    }
}

该方案基于每个 flavor 生成一个 res 文件,然后统计每个 res 的大小。但这种方法忽略了 flavor 之间的公共资源,导致获取到的 res 资源大小不准确。

方案二:通过 variantFilter 动态修改过滤规则

android {
    ...

    applicationVariants.all { variant ->
        if (variant.flavorName == 'pro') {
            variant.resValue 'string', 'app_name', '皮皮虾'
        }
    }
}

该方案可以通过 variantFilter 实现对不同 flavor 配置不同的 res 资源,但无法获取 res 资源的大小,因此无法优化包体积。

解决方案

为了解决上述方案的不足,我们可以结合方案一和方案二的优点。通过合并之后的 res 资源来统计 res 资源的大小,避免统计 flavor 间公共资源重复的问题。同时,通过 variantFilter 动态修改过滤规则,实现对不同 flavor 配置不同的 res 资源。

def countResSize(File resDir) {
    def size = 0
    resDir.eachFile { file ->
        size += file.length()
    }
    return size
}

android {
    ...

    applicationVariants.all { variant ->
        variant.resourceCollectors = [new VariantResCollector()]
        def resSize = 0
        variant.resProviderConfigs.each { config ->
            def configName = config.name
            resSize += countResSize(variant.mergeResourcesOutputDirectory.resDir)
            println configName + ": " + resSize + " bytes"
        }
    }
}

class VariantResCollector extends VariantOutputFilter {
    @Override
    void filter(Context context, Collection inputs, VariantOutputContext outputContext) {
        def outputFile = outputContext.output.getOutputFile()
        if (outputFile.parentFile.name == 'res') {
            println "res size:" + outputFile.length() + " bytes"
        }
    }
}

总结

通过上述解决方案,可以在打包时自动获取所有资源的大小,从而方便优化资源文件、减少包体积、提高打包效率。

常见问题解答

1. 为什么要统计 res 资源大小?

统计 res 资源大小可以帮助我们优化资源文件,减少包体积,提高打包效率。

2. 如何过滤 flavor 间公共资源?

通过合并之后的 res 资源来统计 res 资源的大小,避免统计 flavor 间公共资源重复的问题。

3. 如何对不同 flavor 配置不同的 res 资源?

通过 variantFilter 动态修改过滤规则,实现对不同 flavor 配置不同的 res 资源。

4. 该解决方案是否适用于所有 Android 项目?

该解决方案适用于所有 Android 项目,包括存在多 flavor 的项目。

5. 如何使用该解决方案?

在项目的 build.gradle 文件中添加代码即可使用该解决方案。具体步骤请参考代码示例。