Redex介绍
ReDex 是 Facebook 开源的工具,通过对字节码进行优化,以减小 Android Apk 大小,同时提高 App 启动速度。
GitHub:ReDex github,官网主页:fbredex.com
本次研究完成了Redex在Ubuntu linux上的安装和配置,进行了Redex优化测试, 实验了Redex优化的主要流程, 包括Inderdex。
Redex优化的基础知识
可以先看看这几篇文章:
- 基于 Facebook Redex 实现 Android APK 的压缩和优化
- Facebook App 优化工具 ReDex 优化的 6 点及未优化的一大方面
- Optimizing Android bytecode with ReDex
Ubuntu上安装Redex
Redex目前支持Ubuntu Linux和Mac系统, 安装时需要编译源码,Ubuntu下面需要有sudo权限才能安装。
安装过程参考官方文档。
Ubuntu 14.04 LTS (64-bit)
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:
git clone https://github.com/facebook/redex.git
cd redex
Now, build ReDex using autoconf and make.
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后的配置文件如下:
{
"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
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
adb root
adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof
// copy the heap to your host computer
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
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
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多一点,因此优化效果不够明显。
优化前数据
['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 点及未优化的一大方面