避免在安卓项目中使用枚举

Enum在Java语言中代表一个数据类型,它包含了一组固定不变的恒量。当我们的需求中需要预先定义一组常量来代表一个数据的时候,那么我们就使用Enum。当一个变量可以被赋予一组相关信息的数据的时候,我们就可以使用枚举了,例如:

1
2
3
4
public enum Season
{
WINTER, SPRING, SUMMER, FALL
}

使用枚举来代替Integer或者String类型的数据可以提升编译阶段的检查时间以及避免一些数据类型不合法造成的编译错误。

枚举带来的坏处

Enum中的每个值都是作为一个对象存在的,并且每次使用的时候都会占用一些内存来引用这些对象。故相比较于Integer或者String类型的常量,它更占用内存。即使在低版本的设备上(<=2.2)JIT编译器针对枚举做了优化工作,也是比不上使用Integer和String的常量的占用的内存少。

在项目中使用一个单独的枚举将要增加最终编译后的dex文件的大小,不仅如此它还会产生运行时的系统开销而且应用需要更大的存储空间。

所以过度使用Enum将会导致Apk包的扩增以及运行时内存消耗

如果你的应用正在过度使用枚举的话,赶紧将他们替换为Integer或者String的常量吧!但是这样还是会有问题….

解决方案

Android中提供了注解库,该库中有一个TypeDef的注解。该注解可以应用在参数、返回类型以及成员变量上来指定一组特别的常量。他们还支持编译自动分配常量。

IntDefStringDef是两个魔法常量注解,也可以用来替代Enum的使用。该注解可以在编译阶段帮助我们检查变量的复制分配。

如何使用

下面看一个简单的例子来理解如何使用:ConstantSeason类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConstantSeason {
public static final int WINTER = 0;
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL = 3;
public ConstantSeason(int season) {
System.out.println("Season :" + season);
}
public static void main(String[] args) {
// Here chance to paas invalid value
ConstantSeason constantSeason = new ConstantSeason(5);
}
}

很不幸,使用该方法时用户不能保证在构造器中传入合适的参数,所以该方法类型不安全的做法。

同样使用枚举来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EnumSeason {
public EnumSeason(Season season) {
System.out.println("Season :" + season);
}
public enum Season {
WINTER, SPRING, SUMMER, FALL
}
public static void main(String[] args) {
EnumSeason enumSeason = new EnumSeason(Season.SPRING);
}
}

现在,我们来看看如何使用注解的魔法常量。
首先在Gradle文件中添加support-annotations库的依赖

dependencies { compile ‘com.android.support:support-annotations:24.2.0’ }

声明@IntDef的常量

1
2
3
4
5
6
7
8
9
10
// Constants
public static final int WINTER = 0;
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL = 3;
// Declare the @IntDef for these constants
@IntDef({WINTER, SPRING, SUMMER, FALL})
@Retention(RetentionPolicy.SOURCE)
public @interface Season {}

这里的Typedef注解使用了@interface来声明了新的枚举注解类型。使用@Retention来声明枚举类的声明周期,使用@IntDef来声明枚举元素。RetentionPolicy.SOURCE则告诉编译器不要把枚举编译到字节码文件中。

所以,具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AnnotationSeason {
public static final int WINTER = 0;
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL = 3;
public AnnotationSeason(@Season int season) {
System.out.println("Season :" + season);
}
@IntDef({WINTER, SPRING, SUMMER, FALL})
@Retention(RetentionPolicy.SOURCE)
public @interface Season {
}
public static void main(String[] args) {
AnnotationSeason annotationSeason = new AnnotationSeason(SPRING);
}
}

现在,如果我们向构造器中传递一个非season值的话,编译器将直接报错。

同理,@StringDef也可以按照上述方法声明。

1
2
3
4
5
6
7
8
9
10
// Constants
public static final String WINTER = "Winter";
public static final String SPRING = "Spring";
public static final String SUMMER = "Summer";
public static final String FALL = "Fall";
// Declare the @ StringDef for these constants:
@ StringDef ({WINTER, SPRING, SUMMER, FALL})
@Retention(RetentionPolicy.SOURCE)
public @interface Season {}

总结

通过以上分析我们知道:相比较使用普通常量和RAM内存可以使用5到10倍等效常数,而使用枚举将至少添加两倍的字节总数从而增大APK尺寸。

使用普通Integer和String常量也是最佳实践建议和性能优化方法。

引用

谷歌官方注解库文档
使用注解库