Redex安卓Apk优化技术研究

ReDex 是 Facebook 开源的工具,通过对字节码进行优化,以减小 Android Apk 大小,同时提高 App 启动速度。
GitHub:ReDex github,官网主页:fbredex.com

本次研究完成了Redex在Ubuntu linux上的安装和配置,进行了Redex优化测试, 实验了Redex优化的主要流程, 包括Inderdex。

Redex优化的基础知识

可以先看看这几篇文章:

Ubuntu上安装Redex

Redex目前支持Ubuntu Linux和Mac系统, 安装时需要编译源码,Ubuntu下面需要有sudo权限才能安装。
安装过程参考官方文档。

Ubuntu 14.04 LTS (64-bit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo apt-get install \
g++ \
automake \
autoconf \
autoconf-archive \
libtool \
libboost-all-dev \
liblz4-dev \
liblzma-dev \
make \
zlib1g-dev \
binutils-dev \
libjemalloc-dev \
libiberty-dev \
libjsoncpp-dev

Download, Build and Install

Get ReDex from GitHub:

1
2
git clone https://github.com/facebook/redex.git
cd redex

Now, build ReDex using autoconf and make.

1
2
autoreconf -ivf && ./configure && make
sudo make install

然后就可以在命令行下运行Redex了

Redex Indexdex介绍

Interdex优化比较复杂,默认配置是不开启的,具体看Interdex文档
Interdex Pass 可以优化dex中class的顺序,以及class在不同的dex中的分布(如果是app使用了multidex)
按照class在实际运行中调用的顺序在dex中进行重新排序,可以带来几个好处:

  • 更少的IO
  • 更少的内存占用
  • 更少page cache污染

Redex默认的配置文件是不包含Inderdex这一步的。增加Inderdex后的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"redex" : {
"passes" : [
"ReBindRefsPass",
"BridgePass",
"SynthPass",
"FinalInlinePass",
"DelSuperPass",
"SingleImplPass",
"SimpleInlinePass",
"StaticReloPass",
"RemoveEmptyClassesPass",
"ShortenSrcStringsPass",
"InterDexPass"
],
"coldstart_classes":"app_list_of_classes.txt" //class调用顺序列表
}
}

生成输入数据

如何得到实际运行中class的调用顺序?

首先需要收集app的运行数据

按照典型使用场景操作app,获取heap dump文件, 使用redex提供的脚本redex/tools/hprof/dump_classes_from_hprof.py分析dump文件,得到class列表。
这里有个坑,首先是dump_classes_from_hprof.py在python2运行都有错误, Python2需要安装enum34后才能正常运行, 不兼容python3
在ubuntu上安装enum34后,用python2.7运行,可以得到class列表

具体操作过程如下

// get the process if of your app

1
adb shell ps | grep YOUR_APP_NAME | awk '{print $2}' > YOUR_PID ( if you don't have awk, the second value is the pid of your app)

// dump the heap of your app. You WILL NEED ROOT for this step

1
2
adb root
adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof

// copy the heap to your host computer

1
adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE/.

// pass the heap dump to the python script for parsing and printing out the class list
// Note that the script needs python 2

1
YOUR_PYTHON_2_PATH redex/tools/hprof/dump_classes_from_hprof.py --hprof YOUR_DIR_HERE/SOMEDUMP.hprof > list_of_classes.txt

测量优化效果

主要是看app内存占用和 .dex mmap

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
adb shell ps | grep com.test.app | awk '{ print $2 }'
9003

[R:\AndroidM\packages\apps]$ adb shell dumpsys meminfo 9003
Applications Memory Usage (kB):
Uptime: 2329984 Realtime: 2329984

** MEMINFO in pid 9003 [com.test.app] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 10711 10044 0 0 44416 40455 3960
Dalvik Heap 2201 2172 0 0 35719 33937 1782
Dalvik Other 5424 4984 0 0
Stack 516 516 0 0
Ashmem 4 0 0 0
Other dev 5 0 4 0
.so mmap 967 152 148 360
.apk mmap 271 0 56 0
.ttf mmap 8 0 0 0
.dex mmap 4531 8 4464 0
.oat mmap 2274 0 776 0
.art mmap 2761 1352 1020 0
Other mmap 94 8 8 0
GL mtrack 4196 4196 0 0
Unknown 190 188 0 0
TOTAL 34153 23620 6476 360 80135 74392 5742

App Summary
Pss(KB)
------
Java Heap: 4544
Native Heap: 10044
Code: 5604
Stack: 516
Graphics: 4196
Private Other: 5192
System: 4057

TOTAL: 34153 TOTAL SWAP (KB): 360

Objects
Views: 48 ViewRootImpl: 0
AppContexts: 2 Activities: 1
Assets: 3 AssetManagers: 2
Local Binders: 12 Proxy Binders: 27
Parcel memory: 13 Parcel count: 52
Death Recipients: 0 OpenSSL Sockets: 0

SQL
MEMORY_USED: 663
PAGECACHE_OVERFLOW: 88 MALLOC_SIZE: 62

DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 68 512 225/36/22 /data/user/0/com.test.app/databases/MyTicket

App冷启动时间测试

我们常说的App冷启动,是指启动时你的应用程序的进程是没有创建的. 这也是大部分应用的使用场景.用户在桌面上点击你应用的 icon 之后,首先要创建进程,然后才启动 MainActivity.
这时候adb shell am start -W packagename/MainActivity 返回的结果,就是标准的应用程序的启动时间(注意 Android 5.0 之前的手机是没有 WaitTime 这个值的)
具体可以参考怎么计算apk的启动时间?
如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime。

我编写了一个python脚本,可以自动进行多次冷启动,并画出启动时间统计图,计算平均启动时间。用这个脚本可以很方便的测量任意app的启动时间。
打开app,马上运行adb shell dumpsys activity top,可以看到app的包名和启动Activity, 测试脚本需要输入包名和启动activity的完整类名。

Redex优化效果分析

使用以前开发的App做测试,体积20M,使用了multidex。由于app有启动页,本身启动速度已经很快,1s多一点,因此优化效果不够明显。

1
2
3
4
5
6
7
8
9
10
11
优化前数据
['465', '1122', '1163']
[['444', '1123', '1149'], ['440', '1150', '1191'], ['410', '1450', '1520'], ['439', '1081', '1112'], ['419', '1072', '1117'], ['409', '1055', '1084'], ['423', '1101', '1135'], ['427', '1079', '1120'], ['465', '1122', '1163']]
apk launcher avarage times:[ThisTime, TotalTime, WaitTime]
[ 430.66665649 1137. 1176.77783203]

redex优化后的数据,优化后TotalTime减少70ms
['453', '1129', '1159']
[['403', '1072', '1099'], ['411', '1077', '1123'], ['414', '1056', '1085'], ['383', '1031', '1077'], ['386', '1056', '1102'], ['381', '1030', '1071'], ['449', '1111', '1148'], ['390', '1095', '1127'], ['453', '1129', '1159']]
apk launcher avarage times:[ThisTime, TotalTime, WaitTime]
[ 407.777771 1073. 1110.11108398]

结论

Redex可以在Proguard优化后再在dex层面进行优化,Redex需要配置Proguard配置文件来保护一些不应该被优化的类(如JNI调用、反射调用的类等)。
根据实际测试结果看Redex优化后可以提升冷启动速度10%左右,apk体积减少100k左右,低于Facebook给出的数据(25%)。原因可能是我测试的Apk比较简单,本身启动速度已经比较快,后面应该找启动速度慢的App进行优化测试。
普通App建议在做了Proguard优化后,再根据冷启动测试数据决定是否做Redex优化。

参考文章

ReDex github
Optimizing Android bytecode with ReDex
基于 Facebook Redex 实现 Android APK 的压缩和优化
Facebook App 优化工具 ReDex 优化的 6 点及未优化的一大方面

浅谈Android启动时间
怎么计算apk的启动时间?

本文独立博客地址

Contents
  1. 1. Redex优化的基础知识
  2. 2. Ubuntu上安装Redex
    1. 2.1. Ubuntu 14.04 LTS (64-bit)
    2. 2.2. Download, Build and Install
  3. 3. Redex Indexdex介绍
  4. 4. 生成输入数据
    1. 4.1. 首先需要收集app的运行数据
    2. 4.2. 具体操作过程如下
    3. 4.3. 测量优化效果
  5. 5. App冷启动时间测试
    1. 5.1. Redex优化效果分析
  6. 6. 结论
  7. 7. 参考文章
,