如需转载请评论或简信,并注明出处,未经允许不得转载
目录
前言
Android官方training文档中有一句话
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
http://www.androiddocs.com/training/articles/memory.html
枚举类的好处
在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<com.geekholt.kotlinandjavademo.Color> {
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 "<init>":(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 "<init>":(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 "<init>":(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 "<init>":(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<Color> {
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
类保证类型安全