Android內存優化方式之一:使用註解(Annotation)代替枚舉類Enum,併爲程序打上標記!

枚舉類型是Java 5中新增特性的一部分,它是一種特殊的數據類型,之所以特殊是因爲它既是一種類(class)類型卻又比類類型多了些特殊的約束,但是這些約束的存在也造就了枚舉類型的簡潔性、安全性以及便捷性。

舉一個常見的星期一、二、...星期日枚舉定義的例子:

package main;

public class HelloEnum {
	//通過關鍵字enum定義枚舉類型
	enum Day {
	       MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY, SUNDAY
	    }
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
        System.out.print(Day.MONDAY);
	}
}

瞭解了枚舉類型的定義與簡單使用後,現在有必要來了解一下枚舉類型的基本實現原理:實際上在使用關鍵字enum創建枚舉類型並編譯後,編譯器會爲我們生成一個相關的類,這個類繼承了Java API中的java.lang.Enum類,也就是說通過關鍵字enum創建枚舉類型在編譯後事實上也是一個類類型而且該類繼承自java.lang.Enum類。它已經不再是單純的常量定義了。

雖然它的使用也很簡單,但是在Android開發中官網不推薦使用枚舉enums。爲什麼呢?

佔用內存多(Enums often require more than twice as much memory as static constants.)。 Android中當你的App啓動後系統會給App單獨分配一塊內存,App的DEX code、Heap以及運行時的內存分配都會在這塊內存中。

總結一下就是:

(1)每一個枚舉值都是一個單例對象,在使用它時會增加額外的內存消耗,所以枚舉相比與Integer和String會佔用更多的內存;
(2)較多的使用Enum會增加DEX文件的大小,會造成運行時更多的IO開銷,使我們的應用需要更多的空間。特別是分dex多的大型APP,枚舉的初始化很容易導致ANR。

使用註解方案

註解相當於一種標記,在程序中加上了註解就等於爲程序加上了某種標記,以後JAVAC編譯器,開發工具和其他程序可以用反射來了解你的類以及各種元素上有無任何標記,看你有什麼標記,就去幹相應的事。

先來總結一下常見的3個註解:@Override,@Deprecated,@SuppressWarnings

① 註解@Override用在方法上,當我們想重寫一個方法時,在方法上加@Override,當我們方法的名字出錯時,編譯器就會報錯。

② 註解@Deprecated,用來表示某個類的屬性或方法已經過時,不想別人再用時,在屬性和方法上用@Deprecated修飾註解。

③ 註解@SuppressWarnings用來壓制程序中出來的警告。

④ 註解@Retention可以用來修飾註解,是註解的註解,稱爲元註解。

@Retention

下面重點說這個@Retention註解的源碼定義和相關內容的解釋:

/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 */

package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * <p>A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

這個Retention指的是註解的註解,稱爲元註解。它有一個屬性value(),是RetentionPolicy類型的,Enum RetentionPolicy是一個枚舉類型,這個枚舉決定了Retention註解應該如何去保持,也可理解爲Rentention 搭配 RententionPolicy使用。RetentionPolicy有3個值,源碼如下:

/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 */

package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

分別來解釋下這些組合後用法的意思:
用@Retention(RetentionPolicy.CLASS)修飾的註解,表示當程序編譯時註解的信息被保留在class文件(字節碼文件)中,但不會被虛擬機讀取在運行的時候;
用@Retention(RetentionPolicy.SOURCE )修飾的註解,表示註解的信息會被編譯器拋棄,不會留在class文件中,註解的信息只會留在源文件中;
用@Retention(RetentionPolicy.RUNTIME )修飾的註解,表示當程序編譯時註解的信息被保留在class文件(字節碼文件)中,會被虛擬機保留在運行時,所以他們可以用反射的方式讀取。RetentionPolicy.RUNTIME 可以讓你從JVM中讀取Annotation註解的信息,以便在分析程序的時候使用.

@interface

@interface是用來自定義註釋(註解)類型(JAVA Annotation)的語法。註釋(註解)類型的定義跟定義一個接口相似,我們需要在 interface這個關鍵字前面加上一個@符號,即@interface。註釋中的每一個方法定義了這個註釋類型的一個元素,註釋中方法的聲明中一定不能包含參數,也不能拋出異 常;方法的返回值被限制爲簡單類型、String、Class、emnus、註釋,和這些類型的數組。方法可以有一個缺省值。

@StringDef

/*
 * Copyright (C) 2014 The Android Open Source Project
 */
package android.support.annotation;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Denotes that the annotated String element, represents a logical
 * type and that its value should be one of the explicitly named constants.
 * <p>
 * Example:
 * <pre><code>
 *     Retention(SOURCE)
 *     StringDef({
 *     POWER_SERVICE,
 *     WINDOW_SERVICE,
 *     LAYOUT_INFLATER_SERVICE
 *  })
 *  public @interface ServiceName {}
 *  public static final String POWER_SERVICE = "power";
 *  public static final String WINDOW_SERVICE = "window";
 *  public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
 *  ...
 *  public abstract Object getSystemService(@ServiceName String name);
 * </code></pre>
 */
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
    /** Defines the allowed constants for this element */
    String[] value() default {};
}

@StringDef來代替Enum枚舉。它會幫我檢測像Enum枚舉一樣,在編譯時期檢查變量的賦值情況,但又不像枚舉那麼耗費內存資源!

 下面是源碼中給的使用舉例:

  @Retention(SOURCE)
  @StringDef({
     POWER_SERVICE,
     WINDOW_SERVICE,
     LAYOUT_INFLATER_SERVICE
  })
   public @interface ServiceName {}
   public static final String POWER_SERVICE = "power";
   public static final String WINDOW_SERVICE = "window";
   public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
   ...
   public abstract Object getSystemService(@ServiceName String name);

@IntDef

/*
 * Copyright (C) 2014 The Android Open Source Project
 */
package android.support.annotation;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * 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.
 * <p>
 * Example:
 * <pre><code>
 *  &#64;Retention(SOURCE)
 *  &#64;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);
 *  &#64;NavigationMode
 *  public abstract int getNavigationMode();
 * </code></pre>
 * For a flag, set the flag attribute:
 * <pre><code>
 *  &#64;IntDef(
 *      flag = true,
 *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
 * </code></pre>
 *
 * @see LongDef
 */
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;
}

上面源碼中給的例子:

  @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})

在Activity中使用實例:

public class MainActivity extends Activity {
 
    //先定義 常量
    public static final int RED = 0;
    public static final int GREEN = 1;
    public static final int BLUE = 2;
 
    //用 @IntDef "包住" 常量,這裏使用@IntDef來代替Enum枚舉,也可以使用@StringDef。它會像Enum枚舉一樣在編譯時期檢查變量的賦值情況!
    //這裏是定義包含三種顏色的枚舉值的枚舉
    @IntDef({RED, GREEN, BLUE})
    // @Retention 定義註解保留的策略,是默認保留註解
    @Retention(RetentionPolicy.SOURCE)
    //接口定義 即通過@interface自定義的註解類型
    public @interface ColorTypes {}
 
    @ColorTypes int mColor = RED ;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setColorType(GREEN);
 
        //聲明變量,使用時需要設置一個標記
        @ColorTypes int colorType = getColorType();
 
        switch (colorType){
            case RED:
                break;
            case GREEN:
                break;
            case BLUE:
                break;
            default:
                break;
        }
 
    }
    //使用上面自定義的註解來註解方法參數
    public void setColorType(@ColorTypes int color) {
        this.mColor = color;
    }
    //使用上面自定義的註解來註解這個方法
    @ColorTypes
    public int getColorType() {
        return mColor;
    }
}

想要更透徹的理解Java中自定義註解類型請看另一篇文章!回頭再來看這篇文章會更好!另一篇文章我可能會設置粉絲可見,期待有緣人關注一下!

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章