Redex安卓Apk优化技术研究
jinbaobei
8年前
<h2>Redex介绍</h2> <p>ReDex 是 非死book 开源的工具,通过对字节码进行优化,以减小 Android Apk 大小,同时提高 App 启动速度。</p> <p>本次研究完成了Redex在Ubuntu linux上的安装和配置,进行了Redex优化测试, 实验了Redex优化的主要流程, 包括Inderdex。</p> <h2>Redex优化的基础知识</h2> <p>可以先看看这几篇文章:</p> <ul> <li><a href="http://mp.weixin.qq.com/s?__biz=MzAwMTYwNzE2Mg==&mid=2651036594&idx=1&sn=b276c0f76cea713e5d568ab51e3f7f13&scene=0#wechat_redirect" rel="nofollow,noindex">基于 非死book Redex 实现 Android APK 的压缩和优化</a></li> <li><a href="/misc/goto?guid=4959741696287164365" rel="nofollow,noindex">非死book App 优化工具 ReDex 优化的 6 点及未优化的一大方面</a></li> <li><a href="/misc/goto?guid=4959741696374573219" rel="nofollow,noindex">Optimizing Android bytecode with ReDex</a></li> </ul> <h2>Ubuntu上安装Redex</h2> <p>Redex目前支持Ubuntu Linux和Mac系统, 安装时需要编译源码,Ubuntu下面需要有sudo权限才能安装。</p> <p>安装过程参考官方文档。</p> <h3>Ubuntu 14.04 LTS (64-bit)</h3> <pre> <code class="language-java">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</code></pre> <h3>Download, Build and Install</h3> <p>Get ReDex from GitHub:</p> <pre> <code class="language-java">git clone https://github.com/非死book/redex.git cd redex</code></pre> <p>Now, build ReDex using autoconf and make.</p> <pre> <code class="language-java">autoreconf -ivf && ./configure && make sudo make install</code></pre> <p>然后就可以在命令行下运行Redex了</p> <h2>Redex Indexdex介绍</h2> <p>Interdex优化比较复杂,默认配置是不开启的,具体看 <a href="/misc/goto?guid=4959741696458422296" rel="nofollow,noindex">Interdex文档</a> 。</p> <p>Interdex Pass 可以优化dex中class的顺序,以及class在不同的dex中的分布(如果是app使用了multidex)</p> <p>按照class在实际运行中调用的顺序在dex中进行重新排序,可以带来几个好处:</p> <ul> <li>更少的IO</li> <li>更少的内存占用</li> <li>更少page cache污染</li> </ul> <p>Redex默认的配置文件是不包含Inderdex这一步的。增加Inderdex后的配置文件如下:</p> <pre> <code class="language-java">{ "redex" : { "passes" : [ "ReBindRefsPass", "BridgePass", "SynthPass", "FinalInlinePass", "DelSuperPass", "SingleImplPass", "SimpleInlinePass", "StaticReloPass", "RemoveEmptyClassesPass", "ShortenSrcStringsPass", "InterDexPass" ], "coldstart_classes":"app_list_of_classes.txt" //class调用顺序列表 } }</code></pre> <h2>生成输入数据</h2> <p>如何得到实际运行中class的调用顺序?</p> <h3>首先需要收集app的运行数据</h3> <p>按照典型使用场景操作app,获取heap dump文件, 使用redex提供的脚本 redex/tools/hprof/dump_classes_from_hprof.py 分析dump文件,得到class列表。</p> <p>这里有个坑,首先是dump_classes_from_hprof.py在python2运行都有错误, Python2需要安装 <a href="/misc/goto?guid=4959741696541671064" rel="nofollow,noindex">enum34</a> 后才能正常运行, 不兼容python3</p> <p>在ubuntu上安装enum34后,用python2.7运行,可以得到class列表</p> <h3>具体操作过程如下</h3> <p>// get the process if of your app</p> <pre> <code class="language-java">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)</code></pre> <p>// dump the heap of your app. You WILL NEED ROOT for this step</p> <pre> <code class="language-java">adb root adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof</code></pre> <p>// copy the heap to your host computer</p> <pre> <code class="language-java">adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE/.</code></pre> <p>// pass the heap dump to the python script for parsing and printing out the class list</p> <p>// Note that the script needs python 2</p> <pre> <code class="language-java">YOUR_PYTHON_2_PATH redex/tools/hprof/dump_classes_from_hprof.py --hprof YOUR_DIR_HERE/SOMEDUMP.hprof > list_of_classes.txt</code></pre> <h3>测量优化效果</h3> <p>主要是看app内存占用和 .dex mmap</p> <pre> <code class="language-java">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</code></pre> <h2>App冷启动时间测试</h2> <p>我们常说的App冷启动,是指启动时你的应用程序的进程是没有创建的. 这也是大部分应用的使用场景.用户在桌面上点击你应用的 icon 之后,首先要创建进程,然后才启动 MainActivity.</p> <p>这时候 adb shell am start -W packagename/MainActivity 返回的结果,就是标准的应用程序的启动时间(注意 Android 5.0 之前的手机是没有 WaitTime 这个值的)</p> <p>如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime。</p> <p>我编写了一个python脚本,可以自动进行多次冷启动,并画出启动时间统计图,计算平均启动时间。用这个脚本可以很方便的测量任意app的启动时间。</p> <p>打开app,马上运行 adb shell dumpsys activity top ,可以看到app的包名和启动Activity, 测试脚本需要输入包名和启动activity的完整类名。</p> <h3>Redex优化效果分析</h3> <p>使用以前开发的App做测试,体积20M,使用了multidex。由于app有启动页,本身启动速度已经很快,1s多一点,因此优化效果不够明显。</p> <pre> <code class="language-java">优化前数据 ['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]</code></pre> <h2>结论</h2> <p>Redex可以在Proguard优化后再在dex层面进行优化,Redex需要配置Proguard配置文件来保护一些不应该被优化的类(如JNI调用、反射调用的类等)。</p> <p>根据实际测试结果看Redex优化后可以提升冷启动速度10%左右,apk体积减少100k左右,低于非死book给出的数据(25%)。原因可能是我测试的Apk比较简单,本身启动速度已经比较快,后面应该找启动速度慢的App进行优化测试。</p> <p>普通App建议在做了Proguard优化后,再根据冷启动测试数据决定是否做Redex优化。</p> <p> </p> <p>来自:http://www.jianshu.com/p/57d0d527345e</p> <p> </p>