Enum VS Constants

在項目開發中,在需要定義多個常量的開發場景(如上傳文件邏輯的狀態回調),會猶豫是應該使用Enum枚舉還是直接定義Constants常量來解決問題。

查看 Google Developer Guide, 有提到

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到1.4KB的大小。建議使用 @IntDef annotation。

那麼到正式項目中,

  • 在使用混淆的情況下增加一個枚舉具體會對APK增加多大的大小?
  • 如果只創建一個枚舉類,增加枚舉中成員的個數,對classes.dex的影響有多大?
  • 添加一個枚舉類,到底增加了哪些代碼?
  • 在實際開發中,到底應不應該使用枚舉類代替常量的定義?
  • 對需要定義多個常量的開發場景,應該如果編寫代碼?

帶着上面的問題,通過測試工程,一一解答。

一、在使用混淆的情況下增加一個枚舉具體增加多大的大小?

1、 通過Android Studio創建一個默認項目,解壓獲取classes.dex文件,大小爲1342700bytes,如下圖:

2、 添加一個常量類,在代碼中進行引用,增加代碼如下:

// 常量類
public class Apple {
    public static final int FIRST = 1;
    public static final int SECOND = 2;
    public static final int THREE = 3;
    public static final int FOUR = 4;
    public static final int FIVE = 5;
}
//  MainActivity.class
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)
  setSupportActionBar(toolbar)
  Log.d("s", Apple.FIRST.toString())
}

重新獲取classes.dex文件,大小爲1342720bytes,如下圖:

相對原始項目: 增加20bytes大小,影響非常小。

3、添加一個只有一個元素的枚舉類,在代碼中引用,另外刪除掉上面常量類代碼,增加代碼如下:

enum Apple {
    FIRST
}
// 引用如上面的代碼,此處省略

重新獲取classes.dex文件,大小爲1343128bytes,如下圖:


相對原始項目: 增加428bytes大小,是增加一個常量類增加classes.dex文件大小的21.4

結論一: 在有混淆的情況下,增加一個枚舉類,classes.dex文件大約增加0.5KB的大小。

二、如果只創建一個枚舉類,增加枚舉中成員的個數,對classes.dex的影響有多大?

1、 在1.3的基礎上,對Apple枚舉類中多添加幾個元素,代碼如下:

enum Apple {
    FIRST,
    SECOND,
    THREE,
    FOUR,
    FIVE,
}
// 引用如上面的代碼,此處省略

重新獲取classes.dex文件,大小爲134332bytes,如下圖:

相對於只有一個元素的枚舉類:增加204bytes,平均每增加一個增加50bytes

結論二: 如果已經存在一個枚舉類,向其中,每增加一個元素,大約增加50bytes,影響並不很大。

三、添加一個枚舉類,到底增加了哪些代碼?

那麼,增加一個枚舉類,到底增加了哪些元素?通過反編譯APK文件,我們能夠獲取到其中的答案。
通過Android Studio自帶的apk compare工具,我們可以知道,每增加一個枚舉,在classes.dex中多增加1個class,增加4個method,那麼增加了什麼類,增加了哪些方法?
通過反編譯,貼出關鍵代碼如下:

.class final enum Lcom/learn/enumtest/a;
.super Ljava/lang/Enum;

# annotations
.annotation system Ldalvik/annotation/Signature;
    value = {
        "Ljava/lang/Enum<",
        "Lcom/learn/enumtest/a;",
        ">;"
    }
.end annotation

# static fields
.field public static final enum a:Lcom/learn/enumtest/a;
.field public static final enum b:Lcom/learn/enumtest/a;
.field public static final enum c:Lcom/learn/enumtest/a;
.field public static final enum d:Lcom/learn/enumtest/a;
.field public static final enum e:Lcom/learn/enumtest/a;
.field private static final synthetic f:[Lcom/learn/enumtest/a;

# direct methods
.method static constructor <clinit>()V
    .locals 7
    new-instance v0, Lcom/learn/enumtest/a;
    const-string v1, "FIRST"
    const/4 v2, 0x0
    const-string v1, "SECOND"
    const/4 v3, 0x1
    const-string v1, "THREE"
    const/4 v4, 0x2
    const-string v1, "FOUR"
    const/4 v5, 0x3
    const-string v1, "FIVE"
    return-void
   // 省略掉其中部分代碼
.end method

.method private constructor <init>(Ljava/lang/String;I)V
    .locals 0
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "()V"
        }
    .end annotation
    invoke-direct {p0, p1, p2}, Ljava/lang/Enum;-><init>(Ljava/lang/String;I)V
    return-void
.end method

.method public static valueOf(Ljava/lang/String;)Lcom/learn/enumtest/a;
    .locals 1
    const-class v0, Lcom/learn/enumtest/a;
    invoke-static {v0, p0}, Ljava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    move-result-object p0
    check-cast p0, Lcom/learn/enumtest/a;
    return-object p0
.end method

.method public static values()[Lcom/learn/enumtest/a;
    .locals 1
    sget-object v0, Lcom/learn/enumtest/a;->f:[Lcom/learn/enumtest/a;
    invoke-virtual {v0}, [Lcom/learn/enumtest/a;->clone()Ljava/lang/Object;
    move-result-object v0
    check-cast v0, [Lcom/learn/enumtest/a;
    return-object v0
.end method

從反編譯代碼中,每增加一個枚舉類,無論是單獨的枚舉,還是內部嵌套枚舉,都會向classes.dex文件中新增加一個java類,類中增加兩個構造函數,增加一個valueof函數,增加一個values函數

結論三: 添加一個枚舉,在程序編譯期,會自動增加有兩個構造函數的Java類,同時生成values、valueof兩個函數,方便程序的調用。

四、 在實際開發中,到底應不應該使用枚舉類代替常量的定義?

通過上述比較,我們可以得出結論,使用枚舉對classes.dex文件大小的影響,是直接定義常量的21.4倍,所以爲了APK包的大小,盡力少使用枚舉,除非在需要常量與資源對應的情況下。

五、 對需要定義多個常量的開發場景,應該如果編寫代碼?

推薦使用@intDef,代碼如下:

public class Apple {
    @IntDef({FIRST, SECOND, THREE, FOUR, FIVE})
    public @interface State {
        FIRST = 0;
        SECOND = 1;
        THREE = 2;
        FOUR = 3;
        FIVE = 4;
    }
   
    private int mState;
    public void setState(@State int state) {
        mState = state;
    }
    @State
    public int getState() {
        return mSate;
    }
}

@interface註解修飾State,不需要在寫public static final int進行修飾,@IntDef註解定義State都包含哪些值,如果不在取值範圍內,在編譯期會報紅提醒,防止setState被隨便設置參數的問題,代替枚舉優勢,同時對APK包大小的增加非常小。

枚舉和常量的比較,就寫到這裏,如果有任何問題,歡迎留言。

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