【腾讯内部干货分享】分析Dalvik字节码进行减包优化
http://wetest.qq.com/lab/view/?id=96?from=ads_test2_qqtips&sessionUserType=BFT.PARAMS.192184.TASKID&ADUIN=192726456&ADSESSION=1465349342&ADTAG=CLIENT.QQ.5479_.0&ADPUBNO=26582
小编导读:无论是开发还是发行,不可避免的会遇到包体过大需要压缩的情况。
对于发行商来说,尽管现在wifi遍地,但就算移动运营有一天开放4G免费,包体越小的你依然具备优势。
对于玩家来说则更为简单明了,用户喜欢连续点击几个应用同时下载,包体小的很快就跑完读条更容易被玩家接受。
但真正压缩的时候遇到的麻烦事可真的不少:对游戏整体的压缩却不影响场景,对图片的压缩却不影响品质。最惨的对代码进行压缩,简直是让程序们熬白了头发只为包体再小几K。
今天,直面减包优化这件事,分享一下腾讯人是怎么进行减包的!
作者:移动客户端开发工程师 彭旭康
Android结合版最近几个版本在包大小配额上超标了,先后采用了包括图片压缩,功能H5,无用代码移除等手段减包,还是有着很大的减包压力。组内希望我能从代码的角度减少一些包大小,感觉有点压力山大。经过一段时间对手q安装包反编译后的Dalvik字节码的分析,发现通过调整Java代码可以减少编译后的Dalvik字节码,从而减少包大小。在这方面我做了许多的尝试,有成功有失败,拿出来给大家分享分享,多拍砖多交流。
优化思路
通过dexdump反编译apk中的dex,得到对应Dalvik字节码,找到寻找冗余的字节码,尝试去除或替换冗余的字节码
目前主要是替换或去除原有的java代码,减少对应的Dalvik指令,从而减少安装包大小。
现在主要是从Dalvik字节码分析来调整Java代码,之后希望能够通过ASM等框架直接调整字节码减少现在的包大小。
优化效果
去除初始化赋值方案 ————减少整个手q的发布包大小80k左右。
插桩函数优化———减少整个手q的发布包大小2k左右。
其它尝试方案,包括字符串拼接、移除interface很多空方法等,因为效果比较小、难以统一修改等问题,只是列举下分析结果,大家如果项目中出现的量比较多也是可以尝试去优化的。
优化方案如下:
1、去除初始化赋值冗余
1.1、问题分析:
静态变量为类的所有对象共享,在类加载的准备阶段就会初始设置为系统零值(如下图),比如String被设置初始值为null,而在类中存在
这样的赋值行为会在之后的<cinit>()类构造器方法中执行,重复设置String A为null,增加了对应的<cinit>()方法的Dalvik指令,没有必要,可以干掉。
成员变量在对象创建内存分配完成后,对应的内存空间会被初始设置为系统零值(和静态变量一样),比如int类型被设置为0,而在类中存在
public int B=0;
这样的赋值行为会在之后的<init>()对象构造方法中执行,重复设置int B为0,增加了对应的<init>方法中的Dalvik指令,没有必要,可以干掉。
对于初始化赋值为系统分配默认零值的静态变量和成员变量,去掉初始化赋值,直接使用系统赋的系统零值,可以减少<cinit>和<init>中的Dalvik指令,从而减少包大小,而且可以提高类加载和对象创建的效率。
1.2、优化要点
注意对于static final的变量必须赋初值;
interface的变量都是static final类型的;
注意只有赋值为系统赋予的零值的静态变量和成员变量才能按照这种方式优化,其它比如局部变量的改动会导致编译不通过等问题。
1.3、冗余示例:
优化前:
对应字节码:
优化后:
对应字节码:
减少了两行Dalvik指令的执行,最后分析结果平均优化一处可以减少安装包8个字节左右。
1.4、优化结果:
目前在手Q6.3.0分支上利用自行写的过滤脚本(可以私下找我要对应的优化脚本用于对应的工程)可以看到优化的效果,如果对整个手q执行这个方案,预计能够优化80k左右,修改了4677个文件,修改了17164处冗余。
2、调整插桩对应的代码
Qzone补丁包引入了插桩这一步,需要在所有qzone类的构造函数中加入对mqq.app.MobileQQ类的引用。
优化的方案是将插桩插入到对象构造函数中的语句由
改为
以Qzone某个类的<init>为例,由原本的字节码
变成了
这里替换一处代码,将System.out.print改成getName,可以减少对象构造函数的一行Dalvik指令,替换了1314处初始化函数中插入的代码,最终将对应的qzone_plugin.apk减少了2459字节,整个手q减少2457字节左右。<font color=#FF0000>一行代码,2k收益</font>,其实还是很划算的。
3、字符串拼接
下面是我针对String拼接的特殊情况“变量+”””和“””+变量”的不同形式举例分析Dalvik字节码。
字节码
从示例中可以看出各类字符串拼接方式的优劣,如果用String.valueOf()绝对是最优方案。只是通过对“变量+”””和“””+变量”的形式在手q整个项目调整以后大概能够优化6k左右,如果只是优化qzone部分,效果比较微小,脚本方面不太好过滤对应情况,暂时没有加入,只是做了下试验。
PS:其实“String +”一般来说比StringBuffer的拼接更费字节码,这个部分可以自行验证,前提是a+b+…的形式中首位a这个为变量,而不是常量,如果a是常量,则实际上和StringBuffer等同,这也是个优化点,具体可以参考文章 从字节码视角看java字符串的拼接 。
4、调整interface到class,减少实现接口造成的空方法
很多代码中实现接口时有很多的空方法,并没有作用但还是会占用字节码,希望能够通过调整对应的interface为class,去除冗余的空方法,减少字节码,从而减少包大小。
示例如下:
改成
该方案的缺点在于修改必须手动,难度大,qzone中场景不足以引起量变,而且因为Qzone中<init>中还加入了插桩函数的负担,所以整体优化效果不佳,优化完qzone才2k不到的大小缩减,优化难度高收益小,弃坑。
这些减包思路希望能够给一起在减包路上踩坑的朋友们一些帮助吧。
【腾讯内部干货分享】分析Dalvik字节码进行减包优化的更多相关文章
- 【腾讯Bugly干货分享】深入源码探索 ReactNative 通信机制
Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 本文从源码角度剖析 RNA 中 J ...
- 【腾讯bugly干货分享】Android自绘动画实现与优化实战——以Tencent OS录音机波形动
前言 本文为腾讯bugly的原创内容,非经过本文作者同意禁止转载,原文地址为:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1180 ...
- Dalvik字节码的类型,方法与字段表示方法
Dalvik字节码有着自己的类型,方法与字段表示方法,这些方法与Dalvik虚拟机指令集一起组成了一条条的Dalvik汇编代码. 1.类型 Dalvik字节码只有两种类型,基本类型与引用类型.Dalv ...
- APK反编译之一:基础知识—APK、Dalvik字节码和smali文件
refs: APK反编译之一:基础知识http://blog.csdn.net/lpohvbe/article/details/7981386 APK反编译之二:工具介绍http://blog.csd ...
- 4. 如何解释dalvik字节码
如何解释dalvik字节码 文档: 在Android系统源码目录dalvik\docs有相关指令文档 dalvik-bytecode.html 实战: 来直接实战模拟来理解枯燥的理论 用IDA打开一个 ...
- Android逆向基础----Dalvik字节码
参考此微博,更多详细内容可以到这里查看 http://blog.csdn.net/dd864140130/article/details/52076515 Dalvik字节码 1.寄存器位32位,64 ...
- 安卓逆向学习---初始APK、Dalvik字节码以及Smali
参考链接:https://www.52pojie.cn/thread-395689-1-1.html res目录下资源文件在编译时会自动生成索引文件(R.java ), asset目录下的资源文件无需 ...
- 【腾讯Bugly干货分享】微信热补丁Tinker的实践演进之路
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ad7a70eaed47bb2699e68e Dev Club 是一个交流移动 ...
- 【腾讯Bugly干货分享】TRIM:提升磁盘性能,缓解Android卡顿
Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处.在业内,Android 手机一直有着 ...
随机推荐
- Apache PHP 安装问题 (SUSE Linux)
1. SUSE Linux配置命令如下: './configure' '--with-apxs2=/usr/local/apache2/bin/apxs' '--with-mysql' 2. 接下来 ...
- JS URL编码
JS URL编码escape() 方法: 采用ISO Latin字符集对指定的字符串进行编码.所有的空格符.标点符号.特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在 ...
- Xcode5 支持 SVN 1.7
Xcode升级了之后出现了各种问题,SVN升级到subversion 1.7后,Xcode自带有svn,版本是1.6,所以svn的1.6和1.7不兼容. 解决的办法,要么是降低系统的svn 版本,要么 ...
- C# GDI+学习笔记1
前言 本文是学习C# GDI+系列的第一篇文章,简单的介绍了GDI+的一些基本绘图内容,比较粗糙.但本文主要是让大家简单的回顾一下GDI+的基本概念.本篇文章的参考代码请在此下载 . GDIPTes ...
- EhCache 分布式缓存/缓存集群
开发环境: System:Windows JavaEE Server:tomcat5.0.2.8.tomcat6 JavaSDK: jdk6+ IDE:eclipse.MyEclipse 6.6 开发 ...
- 14、NFC技术:使用Android Beam技术传输文本
Android Beam的基本理念 Android Beam的基本理念就是两部(只能是两部)NFC设备靠近时(一般是背靠背),通过触摸一部NFC设备的屏幕,将数据推向另外一部NFC设备.在传递数据的过 ...
- cocos2d-x CocoStudio中场景触发器(Trigger)的代码部分和触发器之间的互调
这节继上一篇触发器扩展,讲一下代码部分的实现. 事件:EventDef.h 只有一个枚举,是对触发器事件的编号 #ifndef__EVENTDEF__ #define__EVENTDEF__ enum ...
- STL六大组件之——分配器(内存分配,好深奥的东西)
SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器:当配置区小于1 ...
- 关于python中字典的一些总结
1. 获取字典中的值,但是无异常 当在字典中取值的时候,可以使用如下两种方式: >>> d = {'name':'kel'} >>> d {'name': 'kel ...
- C语言实现strlen
strlen: #ifndef STRLEN_H #define STRLEN_H #include <stdio.h> // 参考微软的写法 int cat_strlen(const c ...