15、APK瘦身:资源混淆、代码混淆、动态库精简、无用资源删除

APK瘦身这件事,说白了就是跟用户的流量和耐心赛跑。我见过一个项目,APK从30MB一路膨胀到80MB,用户下载转化率直接掉了5个百分点。老板急,我也急。后来我们花了两周时间,把包体砍到了35MB——嗯,今天聊的这几个手段,就是那次瘦身的主力军。

核心思路:APK里无非三样东西——代码、资源、动态库。瘦身就是分别对这三样下手,能删的删,能压缩的压缩,能混淆的混淆。

一、资源混淆:AndResGuard

资源文件(图片、布局、字符串)在打包后,文件名和路径其实是可以被缩短的。AndResGuard做的就是这件事——把 res/drawable-hdpi/ic_launcher.png 变成 r/d/a.png

我个人习惯在发布Release包时必开这个工具。为什么?因为资源路径的字符串在APK里是明文存储的,缩短后不仅体积变小,还能增加逆向难度。

配置示例(build.gradle)

apply plugin: 'AndResGuard'

andResGuard {
    mappingFile = null
    use7zip = true
    useSign = true
    keepRoot = false
    // 保留哪些资源不混淆
    whiteList = [
        "R.string.umeng*",
        "R.drawable.umeng*"
    ]
    compressFilePattern = [
        "*.png",
        "*.jpg",
        "*.webp"
    ]
    sevenzip {
        artifact = 'com.tencent.mm:SevenZip:1.2.21'
    }
}

避坑指南:我曾经遇到过第三方SDK通过资源ID反射获取资源的情况。比如友盟、微信支付,它们内部写死了资源名。这时候一定要加白名单,否则运行时直接崩溃。嗯,别问我怎么知道的。

二、代码混淆:ProGuard / R8

代码混淆不只是为了安全,更是为了瘦身。ProGuard会把类名、方法名、字段名缩短成 abc,同时还会删除无用的代码分支。

R8是Google后来推出的替代品,它比ProGuard更激进,直接在编译期做全量优化。我建议新项目直接用R8,老项目迁移时注意测试。

混淆效果对比

项目 未混淆 ProGuard R8
DEX体积 8.2 MB 5.1 MB 4.6 MB
方法数 65,432 42,108 38,976
类数 4,210 2,893 2,647

你看,R8比ProGuard还能再省10%左右。但要注意,R8的优化规则更严格,有些反射调用的代码会被误删。我建议在 proguard-rules.pro 里保留所有通过反射调用的类。

-keep class com.example.reflection.** { *; }
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class * extends android.app.Activity

注意:混淆后的堆栈信息是乱码的。一定要保存好 mapping.txt 文件,否则线上崩溃根本看不懂。我习惯每次发版后把这个文件归档到Git仓库的 releases/ 目录下。

三、动态库精简

动态库(.so文件)是APK体积的大头。一个完整的FFmpeg库可能就有20MB。很多开发者图省事,直接把所有架构的so都打进去——arm64-v8a、armeabi-v7a、x86、x86_64,全都要。

你想想看,现在市面上99%的Android设备都是ARM架构,x86基本绝迹了。保留x86的so纯粹是浪费体积。

我的建议

  • 只保留 arm64-v8a 和 armeabi-v7a:覆盖99%的设备
  • 使用APK拆分:通过 splits 配置,为不同架构生成独立APK
  • 按需加载:有些so库只在特定功能中使用,可以放到服务器上按需下载
android {
    splits {
        abi {
            enable true
            reset()
            include 'arm64-v8a', 'armeabi-v7a'
            universalApk false
        }
    }
}

这样做之后,每个APK的体积能减少40%-60%。我记得有个项目,原本的so文件夹占了18MB,精简后只剩6MB——效果立竿见影。

四、无用资源删除

项目迭代久了,难免会留下一些废弃的资源。比如某个图片换掉了,但旧图还在drawable里躺着。或者某个布局文件被重构了,但旧的layout文件没人删。

Android Studio自带的 Refactor > Remove Unused Resources 可以扫描出这些垃圾。但我建议不要全选删除——有些资源是通过 getIdentifier() 动态引用的,IDE扫描不出来。

更稳妥的做法

  1. 先用Lint扫描,生成报告
  2. 人工确认每个未使用资源是否真的没用
  3. 使用 shrinkResources true 在构建时自动移除
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

小技巧:开启 shrinkResources 后,如果某个资源被误删,可以在 res/raw/keep.xml 中手动保留:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/keep_me,@drawable/keep_me"/>

知识体系总览

下面这张图概括了APK瘦身的四个核心方向,以及它们各自的作用范围:

APK瘦身四大核心手段 资源混淆 AndResGuard 代码混淆 ProGuard / R8 动态库精简 .so 文件 无用资源删除 shrinkResources 作用对象 图片、布局、字符串 资源文件路径名 作用对象 类名、方法名、字段名 无用代码分支 作用对象 arm64-v8a armeabi-v7a 作用对象 废弃图片、布局 未引用资源 综合效果:APK体积可减少 30% ~ 60% 建议在Release构建中全部开启,并配合CI自动化验证

总结一下

APK瘦身不是一次性工作,而是需要持续维护的。我建议每个Release版本都跑一遍这四个步骤:

  • 资源混淆:AndResGuard,注意第三方SDK的白名单
  • 代码混淆:R8优先,保留反射类和mapping文件
  • 动态库精简:只保留ARM架构,按需拆分
  • 无用资源删除:Lint扫描 + shrinkResources + 人工确认

嗯,这些手段组合起来,效果远大于单独使用。我记得有一次帮一个合作团队做瘦身,他们之前只做了代码混淆,包体还有45MB。我们把剩下三个手段加上后,直接降到了22MB——对方技术负责人当场就愣住了。

所以别嫌麻烦,该上的工具都上。用户的流量和耐心,都挺贵的。


公众号:蓝海资料掘金营,微信deep3321