如需转载请评论或简信,并注明出处,未经允许不得转载
目录
前言
Android官方training文档中有一句话
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
枚举类的好处
在java中,使用枚举类可以保证类型安全和提高代码可读性。无论需求如何变化,比如枚举常量增加一个数据,或是增加一条状态,都可以很方便的实现,比直接在全局常量类里定义状态来的便捷
static与Enum比较
第一步:有一个Android Project,编译后生成的dex的体积为3,465,228byte
第二步:新增创建一个类,如下所示,编译后生成的dex的体积为3,465,532byte(比初始增加304byte)
public class Test {
public static final int RED = 1;
public static final int BLACK = 2;
public static final int GREEN = 3;
public static final int YELLOW = 4;
public int fun(int color) {
switch (color) {
case RED:
return -1;
case BLACK:
return -2;
case GREEN:
return -3;
case YELLOW:
return -4;
default:
return 0;
}
}
}
第三步,改写第二步中创建的类,如下所示,编译后生成的dex的体积为3,466,568 byte(比初始增加1340byte,比使用static增加1036byte)
初步结论:使用Enum会比static增加2-3倍的byte,从而增加apk的体积
Enum源码分析
我们创建了一个枚举类Color
public enum Color {
RED, GREEN, BLACK, YELLOW
}
编译项目,在app/build/intermediates/javac/debug/classes/项目名称目录下,可以找到Color.class文件,执行javap -c Color.class命令对class文件进行反编译,结果如下所示
Compiled from "Color.java"
public final class com.geekholt.kotlinandjavademo.Color extends java.lang.Enum {
public static final com.geekholt.kotlinandjavademo.Color RED;
public static final com.geekholt.kotlinandjavademo.Color GREEN;
public static final com.geekholt.kotlinandjavademo.Color BLACK;
public static final com.geekholt.kotlinandjavademo.Color YELLOW;
public static com.geekholt.kotlinandjavademo.Color[] values();
Code:
0: getstatic #1 // Field $VALUES:[Lcom/geekholt/kotlinandjavademo/Color;
3: invokevirtual #2 // Method "[Lcom/geekholt/kotlinandjavademo/Color;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/geekholt/kotlinandjavademo/Color;"
9: areturn
public static com.geekholt.kotlinandjavademo.Color valueOf(java.lang.String);
Code:
0: ldc #4 // class com/geekholt/kotlinandjavademo/Color
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/geekholt/kotlinandjavademo/Color
9: areturn
static {};
Code:
0: new #4 // class com/geekholt/kotlinandjavademo/Color
3: dup
4: ldc #7 // String RED
6: iconst_0
7: invokespecial #8 // Method "":(Ljava/lang/String;I)V
10: putstatic #9 // Field RED:Lcom/geekholt/kotlinandjavademo/Color;
13: new #4 // class com/geekholt/kotlinandjavademo/Color
16: dup
17: ldc #10 // String GREEN
19: iconst_1
20: invokespecial #8 // Method "":(Ljava/lang/String;I)V
23: putstatic #11 // Field GREEN:Lcom/geekholt/kotlinandjavademo/Color;
26: new #4 // class com/geekholt/kotlinandjavademo/Color
29: dup
30: ldc #12 // String BLACK
32: iconst_2
33: invokespecial #8 // Method "":(Ljava/lang/String;I)V
36: putstatic #13 // Field BLACK:Lcom/geekholt/kotlinandjavademo/Color;
39: new #4 // class com/geekholt/kotlinandjavademo/Color
42: dup
43: ldc #14 // String YELLOW
45: iconst_3
46: invokespecial #8 // Method "":(Ljava/lang/String;I)V
49: putstatic #15 // Field YELLOW:Lcom/geekholt/kotlinandjavademo/Color;
52: iconst_4
53: anewarray #4 // class com/geekholt/kotlinandjavademo/Color
56: dup
57: iconst_0
58: getstatic #9 // Field RED:Lcom/geekholt/kotlinandjavademo/Color;
61: aastore
62: dup
63: iconst_1
64: getstatic #11 // Field GREEN:Lcom/geekholt/kotlinandjavademo/Color;
67: aastore
68: dup
69: iconst_2
70: getstatic #13 // Field BLACK:Lcom/geekholt/kotlinandjavademo/Color;
73: aastore
74: dup
75: iconst_3
76: getstatic #15 // Field YELLOW:Lcom/geekholt/kotlinandjavademo/Color;
79: aastore
80: putstatic #1 // Field $VALUES:[Lcom/geekholt/kotlinandjavademo/Color;
83: return
}
把上面的字节码文件翻译成java大致如下所示
public final class Color extends java.lang.Enum {
public static final Color RED;
public static final Color GREEN;
public static final Color BLACK;
public static final Color YELLOW;
static {
RED = new Color("RED", 0);
GREEN = new Color("GREEN", 1);
BLACK = new Color("BLACK", 2);
YELLOW = new Color("YELLOW", 3);
VALUES = new Color[]{RED, GREEN, BLACK, YELLOW};
}
public static Color[] values() {
Color tmp = new Color[VALUES.length];
system.arraycopy(VALUES, 0, tmp, 0, VALUES.length);
return tmp;
}
public static Color valueOf(String name) {
return Enum.valueOf(name);
}
}
由此我们可以得出以下结论:
Enum中的每一个值都是一个Object,它的每个声明都会占用运行时的部分内存以便能够引用到这个Object。因此Enum的值会比对应的Integer和String所占用的内存多
很多时候我们声明static变量只需要在已有的类中进行声明,而如果使用枚举类,就会多出一个类,最终则会增大Dex的体积,显然Enum的空间占用是远大于Integer常量或String常量的
解决方案
为了弥补 Android 平台不建议使用枚举的缺陷,官方推出了两个注解,@IntDef和@StringDef,用来提供编译期的类型检查
添加依赖
在build.gradle文件中的依赖块中添加:
dependencies { compile 'com.android.support:support-annotations:24.2.0' }
声明常量和@IntDef
@IntDef({
Color.RED,
Color.GREEN,
Color.BLACK,
Color.YELLOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface Color {
int RED = 1;
int GREEN = 2;
int BLACK = 3;
int YELLOW = 4;
}
这里@TypeDef注解使用了@interface来声明新的枚举注解类型。其中@IntDef和@StringDef注解以及@Retention标注了新的注解,目的是定义这个枚举类型。而@Retentino(RententionPolicy.SOURCE)注解告诉编译器在生成.class文件时不保留枚举注解数据
使用方法如下, 这样外界就无法传递 Color 之外的成员作为参数
public static void doSth(@Color int color){
switch (color){
case Color.RED:
//do something
break;
case Color.GREEN:
break;
case Color.BLACK:
break;
case Color.YELLOW:
break;
}
}
如果开启了Proguard可以在很多情况下将枚举Enum优化到整数对象。
结论
在android中使用枚举类不仅会增加apk体积,同时也会增加运行时内存,所以在架构设计上还是要慎用枚举类。如果希望进行编译期类型检查可以使用@IntDef和@StringDef类保证类型安全