Android开发@IntDef完美替代Enum (枚举)
概要
Enum 是 java 中一种包含固定常数的类型,当我们需要预先定义一些值时,我们使用 Enum,这样做通常为了在编译时期避免接受额外常量引起的错误。
而且,Enum 增加了APK 的大小,比常量多5到10倍的内存占用,这是关于应用性能的最佳实践.
Android官网不建议使用enums,占用内存多(Enums often require more than twice as much memory as static constants.)。
Android中当你的App启动后系统会给App单独分配一块内存。App的DEX code、Heap以及运行时的内存分配都会在这块内存中。
请看官方文档:
Android Developer-Avoid enumerations
Avoid enumerations
A single enum can add about 1.0 to 1.4 KB of size to your app's classes.dex
file. These additions can quickly accumulate for complex systems or shared libraries. If possible, consider using the @IntDef
annotation and ProGuardto strip enumerations out and convert them to integers. This type conversion preserves all of the type safety benefits of enums.
每个枚举常量可以使你的应用程序的classes.dex
文件增加大约1.0到1.4 KB的大小。 这些额外增加的占用会迅速在你的系统或共享库中累加。如果可能的话Android中推荐使用@IntDef 注解
或ProGuard
代替。
使用 Enum 的缺点
每一个枚举值都是一个对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存。
较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的开销,使我们的应用需要更多的空间。
如果你的应用使用很多的 Enum ,最好使用Integer 或 String 替代他们,但是这样还会有问题。
既然都说到这个份上了,那么有什么比较好的解决方法呢?
官方文档说明,安卓开发应避免使用Enum(枚举类),因为相比于静态常量Enum会花费两倍以上的内存。参考这里
那么如果需要使用Enum应该怎么做呢?
- 解决方案
既然是因为参数的类型太泛了造成的类型不安全,那么我只要将参数限定在某一个类型集合里面,不就大功告成了?!
是滴,一下就是要将的@IntDef/@StringDef + @interface来进行限定参数。
使用注解库
这些注解不是默认加载的,它们被包装为一个单独的库。Support Library现在是由一些更小的库组成的,包括:v4-support、appcompat、gridlayout、mediarouter等等。
添加注解的最简单的方法就是打开Project Structure对话框。首先在左边选中module,然后右边选中Dependencies标签,点击“+”号按钮,选择Library Dependency。如果SDK中已经包括了Android Support库,那么注解支持库就会显示在快捷选择列表中了,只需要点击选择就可以。
-
步骤1:点击Project Structure按钮
image -
步骤2:选中Dependencies标签,点击“+”号按钮
image -
步骤3:在下拉列表中选中support-annotations库
image -
点击OK确定,这将会修改build.gradle文件。当然也可以手动在Gradle中添加如下依赖:
dependencies {
compile 'com.android.support:support-annotations:23.1.0'
}
应用
- 定义static final的常量
private static final int ADD = 0;
private static final int SUB = 1;
private static final int MUL = 2;
private static final int DIV = 3;
- 定义一个IntDef注解,包含上面的常量,两种形式
@IntDef({ADD,SUB,MUL,DIV})
或
@IntDef(flag = true, value = {ADD,SUB,MUL,DIV})
区别是第二种可以用条件进行位运算,更多详细信息,请参考官方文档
- 定义一个注解,表明当前@IntDef的保留策略,只保留源码中,编译时删除,
@Retention(RetentionPolicy.SOURCE)
当然你还可以指定其他策略:
Class:编译时被保留,在class文件中存在,但JVM将会忽略
Runtime:将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用
- 自定义一个注解 表明类型
public @interface Operation{}
- 使用,在方法中使用,类型安全,替代枚举
public void operation(@Operation int opeartion) {
switch (opeartion) {
case ADD:
break;
case SUB:
break;
case DIV:
break;
case MUL:
break;
}
}
Android 中有使用到的一个例子
Toast
public class Toast {
static final String TAG = "Toast";
static final boolean localLOGV = false;
/** @hide */
/*定义部分*/
@IntDef({LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
public static final int LENGTH_SHORT = 0;
public static final int LENGTH_LONG = 1;
...
/*作为类型使用时*/
/**
* Set how long to show the view for.
* @see #LENGTH_SHORT
* @see #LENGTH_LONG
*/
public void setDuration(@Duration int duration) {
mDuration = duration;
}
/*做为返回值时*/
/**
* Return the duration.
* @see #setDuration
*/
@Duration
public int getDuration() {
return mDuration;
}
}
- ps :这里是IntDef的API说明
/*IntDef
implements Annotation
android.support.annotation.IntDef
Class Overview
Denotes that the annotated element of integer type, represents a logical type and that its value should be one of the explicitly named constants. If the IntDef#flag() attribute is set to true, multiple constants can be combined.
*/
//Example:
@Retention(SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
...
public abstract void setNavigationMode(@NavigationMode int mode);
@NavigationMode
public abstract int getNavigationMode();
//For a flag, set the flag attribute:
@IntDef(
flag = true
value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
总结
可以看到,如果不适用枚举,将会带来类型不安全的问题。一般情况下,我们在很多地方都会使用到枚举,因为方便和简洁。但是使用枚举也会产生占用内存过高等情况。所以我们可以有了自定义的方案,来限定我们使用的类型范围。