话不多说,最近在进行内存优化排查时,准备对枚举下手了,为什么呢?官方文档不建议我们用枚举:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
枚举的局限
个人认为,使用枚举有两个弊端:
1.增加APK体积,对你没听错,我给你证明下。
2.增加APP运行时内存,虽然影响不大,只是一个细节点。
为啥说增加体积
1.创建一个java类如下:
public class EnumUtil {
public void start(){
}
public enum Demo{
START,
END,
STOP
}
}
然后执行Javac命令,编译为class文件。再通过以下命令执行,生成dex文件
dx --dex --output=需要生成dex的目录和名称 class文件所在的目录
上面的命令有两个比较坑的地方,不注意可能要折腾很久:
1.--output=XXXX 等号后面不要有空格,会报错
2.大概率会出现以下报错:
PARSE ERROR:
class name (EnumUtil$Demo) does not match path (/Users/zhouhao/Android/23DesignMode/app/src/main/java/com/example/a23designmode/EnumUtil$Demo.class)
我之前其实一直没遇到过,换电脑了后好像必现,处理方式,这时候只要在--dex 后面加上--no-strict 就可以了
如下:dx --dex --no-strict --output=XXXX YYY
public class EnumUtil {
private final static int START = 1;
private final static int END = 1;
private final static int STOP = 1;
public void start() {
}
}
把上面的枚举代码替换为 静态常量如下,再执行上面打dex文件的命令,生成dex 文件。对比看下结果:
使用枚举时候,明显dex文件大了不少,几百个字节,仅仅一个简单的枚举,体积就大了不少,如果公司的项目 大量使用,APP体积应该是有不少优化空间。
为啥说增加了内存
增加内存的说法又是怎么来的?同样我们使用javap XXX.class 简单的查看下两种写法的字节码。反编译后看到枚举字节码如下:
public final class com.example.a23designmode.EnumUtil$Demo extends java.lang.Enum {
public static final com.example.a23designmode.EnumUtil$Demo START;
public static final com.example.a23designmode.EnumUtil$Demo END;
public static final com.example.a23designmode.EnumUtil$Demo STOP;
public static com.example.a23designmode.EnumUtil$Demo[] values();
public static com.example.a23designmode.EnumUtil$Demo valueOf(java.lang.String);
static {};
}
重点就是把每个枚举类型搞成一个个对象了,简单总结如下:
以64位操作系统,对象头中的Markword存 hashcode 、GC分代、锁状态标志就部分就8字节 ,指针压缩(指针作用是表明这个对象被那个类创建持有的)开启就是4不开启8字节 ,还有就是数组长度这部分只有数组对象才有。那至少就12个字节了,一个int类型的4字节!还不算上对其部分。
多提一句:为什么要对其:因为这个涉及到一个CPU的 缓存行污染
CPU的 缓存行污染
比如不对齐 一个缓存行大小假如8个字节,存放了A对象的6个字节,存放了B对象的2个字节,那么当A对象改变了,B对象在被CPU加载时候需要重新加载到缓存行中 因此影响执行效率
最优方案
上面说了一堆,还没说道重点。重点就是我们使用注解来替代枚举。并且还可以限制开发者传制定的值:
public class EnumUtil {
private static final int AAAA =1;
private static final int BBBB =2;
public void start(@EnumType int type){
//TODO:使用
// start(111); //这样用户直接设置111这样,就会编译报错
start(AAAA);
}
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
@IntDef({AAAA,BBBB})
@interface EnumType{
}
}
好处就是当开发者随意传递就编译报错,这个场景使用起来还是很不错的!