APK瘦身相关知识点汇总

APK 安装和更新时都需要经过网络下载到设备。so! APK越小,用户体验越好。
本文主要收集apk瘦身相关知识点汇总


APK Analyser 使用

使用APK Analyzer分析您的APK教程

查看文件和大小信息

查看AndroidManifest.xml

查看DEX文件

过滤DEX文件树视图

加载Proguard映射

加载Proguard映射文件,请执行以下操作:

* 单击Load Proguard Mappings。
* 导航到包含映射文件的项目文件夹,并加载所有文件,文件的任意组合或包含文件的文件夹。

以下列表描述了映射文件:

* seeds.txt:Proguard配置防止在收缩期间被移除的节点以粗体显示。
* mapping.txt:启用 反混淆名称, 以便您可以还原由Proguard混淆的节点的原始名称。例如,你可以恢复像模糊的节点名a,b,c来MyClass,MainActivity和myMethod()。
* usage.txt:启用显示已删除的节点, 以便显示Proguard在收缩期间删除的类,方法和字段。已恢复的节点以删除线显示。

显示字节码,查找用法并生成Keep规则



查看代码和资源实体


比较APK文件

* 将要发布的APK版本加载到APK分析器中。
* 在APK分析器的右上角,单击“ 比较”。
* 在选择对话框中,找到上次发布给用户的APK,然后单击“ 确定”。

APK分析器可以比较两个不同APK文件中的实体大小。当您需要了解应用程序与先前版本相比增加的原因时,这非常有用。在发布更新的APK之前,请执行以下操作:

优化

Dex 文件

Dalvik 可执行文件格式DEX介绍

  • 可针对不需要的第三方库作移除操作
    比如:android.support软件包中引用了超过 13000 种的方法,对于一个不需要使用这个库的项目则完全没有必要。【其他库分析同理】

删除未使用到代码

同样使用Android Studio的Lint,步骤:点击菜单栏 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘app’ -> OK

可多重复几次

使用微信Android资源混淆工具

微信AndResGuard是一个帮助你缩小APK大小的工具,详情:Android资源混淆工具使用说明。

使用方法:

  • Project/build.gradle
    1
    2
    3
    4
    5
    6
    7
    8
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.0'
    }
    }
  • app/build.gradle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    apply plugin: 'AndResGuard'
    def supportVersion = "25.0.0"
    android {
    ...
    signingConfigs {
    release {
    storeFile file('keystore/android.keystore')
    storePassword '123456'
    keyAlias 'android.keystore'
    keyPassword '123456'
    }
    debug {
    storeFile file('keystore/android.keystore')
    storePassword '123456'
    keyAlias 'android.keystore'
    keyPassword '123456'
    }
    }
    buildTypes {
    release {
    ...
    signingConfig signingConfigs.release
    }
    }
    }
    andResGuard {
    // mappingFile = file("./resource_mapping.txt")
    //mappingFile用于增量更新,保持本次混淆与上次混淆结果一致;
    mappingFile = null
    //uss7zip为true时,useSign必须为true;
    use7zip = true
    //useSign为true时,需要配置signConfig;
    useSign = true
    //打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字;
    keepRoot = false
    //whiteList添加在代码内部需要动态获取的资源id,不混淆这部分;
    whiteList = [
    // for your icon
    "R.drawable.icon",
    // for fabric
    "R.string.com.crashlytics.*",
    // for umeng update
    "R.string.umeng*",
    "R.string.UM*",
    "R.string.tb_*",
    "R.layout.umeng*",
    "R.layout.tb_*",
    "R.drawable.umeng*",
    "R.drawable.tb_*",
    "R.anim.umeng*",
    "R.color.umeng*",
    "R.color.tb_*",
    "R.style.*UM*",
    "R.style.umeng*",
    "R.id.umeng*",
    // umeng share for sina
    "R.drawable.sina*",
    // for google-services.json
    "R.string.google_app_id",
    "R.string.gcm_defaultSenderId",
    "R.string.default_web_client_id",
    "R.string.ga_trackingId",
    "R.string.firebase_database_url",
    "R.string.google_api_key",
    "R.string.google_crash_reporting_api_key",
    // umeng share for facebook
    "R.layout.*facebook*",
    "R.id.*facebook*",
    // umeng share for messager
    "R.layout.*messager*",
    "R.id.*messager*",
    // umeng share commond
    "R.id.progress_bar_parent",
    "R.id.webView"
    ]
    //用来指定文件重打包时是否压缩指定文件;
    compressFilePattern = [
    "*.png",
    "*.jpg",
    "*.jpeg",
    "*.gif",
    "resources.arsc"
    ]
    //sevenzip可使用artifacr或path,path指本地安装的7za(7zip命令行工具)。
    sevenzip {
    artifact = 'com.tencent.mm:SevenZip:1.2.0'
    //path = "/usr/local/bin/7za"
    }
    }
    AndResGuard打包命令行:gradlew resguardRelease,最终的混淆APK会生成在{App}/build/output/apk/AndResGuard目录下。

资源

目录“res”中包含了大量的布局(Layout)文件、Drawable 和动画,它们并非在 Android Studio UI 中立刻可见。同样,它们也是由支持库推入其中的。亦可删除多余的资源

在resources.arsc文件中,还包含了对每个资源的引用。

用7zip代替压缩资源。

优化res,assets文件大小

png图片格式转成jpg、或使用webp格式

* 我们可以选中 drawable 和 mipmap 文件夹,右键后选择 convert to webp,将图片转为 webp 格式。

使用https://tinypng.com/进行图片无损压缩

可使用复用的xml布局,减少重复代码

使用一套资源【可使用xxxh 或 xxh一个目录图片,作支持】

删除未使用到xml和图片

使用shape作为背景

配置resConfigs

1
2
3
4
5
6
7
8
如果APP支持中文,可以配置resConfigs,只支持中文
android {
defaultConfig {
...
//语言资源,只支持中文
resConfigs "zh"
}
}

覆盖第三库里的大图

1
2
有些第三库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用1x1的透明图片覆盖。
你可能会有点不舒服,因为你的drawable下竟然包含了一些莫名其妙的名称的1x1图片…

删除armable-v7包下的so

1
2
基本上armable的so也是兼容armable-v7的,armable-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。
这里不排除有极少数设备会Crash,可能和不同的so有一定的关系,请大家务必测试周全后再发布。

删除x86包下的so

1
2
与上条不同的是,x86包下的so在x86型号的手机是需要的,如果产品没用这方面的要求也可以精简。
建议实际工作的配置是只保留armable、armable-x86下的so文件,算是一个折中的方案。

使用着色方案

1
2
相信你的工程里也有很多selector文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。
借助于android support库可实现一个全版本兼容的着色方案,参考代码:[DrawableLess.java](https://github.com/openproject/LessCode/blob/master/lesscode-core/src/main/java/com/jayfeng/lesscode/core/DrawableLess.java)

在线化素材库

1
2
如果你的APP支持素材库(比如聊天表情库)的话,考虑在线加载模式,因为往往素材库都有不小的体积。
这一步需要开发者实现在线加载,一方面增加代码的复杂度,一方面提高了APP的流量消耗,建议酌情选择。

避免重复库

1
2
避免重复库看上去是理所当然的,但是秘密总是藏的很深,一定要当心你引用的第三方库又引用了哪个第三方库,这就很容易出现功能重复的库了,比如使用了两个图片加载库:Glide和Picasso。
通过查看exploded-aar目录和External Libraries或者反编译生成的APK,尽量避免重复库的大小,减小APP大小。

使用更小的库

1
2
同样功能的库在大小上是不同的,甚至会悬殊很大。
如果并无对某个库特别需求而又对APP大小有严格要求的话,比较这些相同功能第三方库的大小,选择更小的库会减小APP大小。

支持插件化

1
2
插件化技术支持动态的加载代码和动态的加载资源,把APP的一部分分离出来了,对于业务庞大的项目来说非常有用,极大的分解了APP大小。
因为插件化技术需要一定的技术保障和服务端系统支持,有一定的风险,如无必要(比如一些小型项目,也没什么扩展业务)就不需要了,建议酌情选择。

Facebook的redex优化字节码

redex是facebook发布的一款android字节码的优化工具,需要按照说明文档自行配置一下。

1
redex input.apk -o output.apk --sign -s <KEYSTORE> -a <KEYALIAS> -p <KEYPASS>

针对测试apk进行分析得出

  • 下面我们来看看它的效果,仅redex的话,减小了157k:
  • 如果先进行微信混淆,再redex,减小了565k,redex只贡献了10k:
  • 如果先进行redex,在进行微信混淆,减小了711k,redex贡献了157k:

最后一种的效果是最好的,这是很容易解释的,如果最后是redex的重新打包则浪费了前面的7zip压缩,所以为了最优效果要注意顺序。
另外,据反应redex后会有崩溃的现象,这个要留意一下,我这里压缩之后都是可以正常运行的。

如何知道哪些xml和图片未被使用到?使用Android Studio的Lint,

步骤:

  • Android Studio -> Menu -> Refactor -> Remove Unused Resources
  • 选择 Refactor 一键删除
  • 选择 Perview 预览未使用到的资源
    或者
  • 点击菜单栏 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘app’ -> OK,这样会搜出来哪些未被使用到未使用到xml和图片

签名

目录“META-INF”中包含有CERT.SF、MANIFEST.MF和CERT.RSA文件,这些文件都需要 v1 APK 签名

如果有攻击者修改了我们 APK 中的代码,签名就会不匹配。这一机制保障了用户能避免执行第三方恶意软件的风险。

在MANIFEST.MF文件中列出了 APK 中的所有文件。其中,CERT.SF文件中包含了文件清单的摘要,以及每个文件的独立摘要。CERT.RSA文件中包含了一个公钥,用于验证CERT.SF文件的完整性。

在签名文件中,没有目标明显可优化。

启用最小化功能(Minification)

App 的build.gradle文件中设置允许最小化(Minification)和资源收缩(Resource Shrinking)。我们现在做此设置:

1
2
3
4
5
6
7
8
9
10
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android.txt'), 'proguard-rules.pro'
}
}
}

proguard相关介绍链接

将minifyEnabled属性设置为“true”值,这将启用 Proguard

该功能将从 App 中剥离出那些未使用的代码,并对符号的名称做模糊化处理,使得 App 难以被反向工程。

设置shrinkResources属性,将会在 APK 中移除任何并非直接引用的资源。这时如果我们使用反射机制间接地访问资源,就会导致问题,但是本文给出的 App 并不存在这样的问题。

Manifest 文件(优化为 5252 字节)

1
2
android:allowBackup="true"
android:supportsRtl="true"

zipalign 更改未压缩资源的字节对齐方式

现在我们要手工编辑我们的 APK 了。我们将使用如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1\. 创建一个未签名的 APK。
./gradlew assembleRelease

# 2\. 解压缩归档文件。
unzip app-release-unsigned.apk -d app

# 对文件进行编辑。

# 3\. 压缩归档文件
zip -r app app.zip

# 4\. 运行 zipalign。
zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk

# 5\. 使用 v2 签名运行 apksigner。
apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk

# 6\. 验证签名。
apksigner verify signed-release.apk

详细概述了 APK 签名过程。总而言之,gradle 生成了一个未签名的归档文件,zipalign 更改了未压缩资源的字节对齐方式,用于改进加载 APK 时的 RAM 使用,最后 APK 将被加密签名。

削减方法引用

减少创建不必要多余的方法等方式

其他相关参考