+2

Android Performance: Tránh sử dụng ENUM trên Android

1. What is the ENUM ?

Enum trong java là một kiểu dữ liệu chứa các tập hợp các hằng số cố định. Là 1 trong số các kiểu dữ liệu do người lập trình tự định nghĩa.

2. Why we use ENUM ?

Trong quá trình lập trình, những kiểu dữ liệu được định nghĩa sẵn trong ngôn ngữ lập trình có thể không mang lại ý nghĩa phù hợp. Ví dụ mình muốn sử dụng các giá trị từ 1 đến 7 để đại diện cho 7 ngày trong tuần (1 đại diện cho ngày chủ nhật, 7 đại diện cho thứ 7), như vậy mình cần ít nhất là 7 biến để lưu trữ các giá trị này:

const int SUNDAY = 1;
const int MONDAY = 2;
const int TUESDAY = 3;
const int WEDNESDAY = 4;
const int THURSDAY = 5;
const int FRIDAY = 6;
const int SATURDAY = 7;

Mình không sử dụng mảng một chiều trong trường hợp này vì:

int DAYS_OF_WEEK[7] = { 1, 2, 3, 4, 5, 6, 7 };

Những con số cụ thể ko mang lại ý nghĩa cho người đọc mã nguồn chương trình. Việc sử dụng tên của các biến hằng số sẽ giúp chương trình chúng ta rõ ràng hơn.

Nhưng việc khai báo các hằng số như trên vẫn có một số nhược điểm:

  • Có thể khai báo thiếu sót một vài giá trị khi danh sách các hằng số là quá nhiều.

  • Có thể khai báo ko theo một quy luật (hay thứ tự) nhất định khiến chúng ta khó tìm trong chương trình.

  • Ví dụ:

const int WEDNESDAY = 4;
const int SUNDAY = 1;
const int TUESDAY = 3;
const int FRIDAY = 6;
const int MONDAY = 2;
const int SATURDAY = 7;
const int THURSDAY = 5;
  • Có một số hằng số không liên quan đến nhau nhưng được khai báo gần nhau khiến chúng ta dễ rối.
  • Ví dụ:
const float PI = 3.14;
const float ACCELERATION_OF_GRAVITY = 9.8;
const int MAX_SIZE_OF_ARRAY = 255;
//..............

Như vậy, muốn khắc phục một số nhược điểm trên, chúng ta cần tìm cách để tập hợp các hằng số có ý nghĩa tương đương nhau thành những nhóm hằng số riêng biệt. ENUM sẽ giúp chúng ta thực hiện điều này.

Để định nghĩa một kiểu liệt kê mới, chúng ta sử dụng từ khóa enum theo cấu trúc sau:

enum <name_of_enumeration>
{
	//list all of values inside this block
	//each enumerator is separated by a comma, not a semicolon
};

Việc khai báo kiểu dữ liệu mới (như kiểu enum) không yêu cầu chương trình cấp phát bộ nhớ, lúc nào chúng ta sử dụng kiểu enum vừa đã được định nghĩa để tạo ra biến kiểu enum thì chương trình mới cấp phát bộ nhớ.

Mỗi giá trị trong block của kiểu enum cách nhau bởi một dấu phẩy (đối với giá trị cuối cùng thì không cần sử dụng dấu phẩy).

  • Ví dụ :
enum DaysOfWeek
{
	SUNDAY,
	MONDAY,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY
};
enum DaysOfWeek { SUNDAY,	MONDAY,	TUESDAY, WEDNESDAY,	THURSDAY, FRIDAY, SATURDAY };
enum Color { RED, GREEN, BLUE, WHITE };
enum Animal { CAT, DOG, HORSE, MONKEY, CHICKEN };

Khi nhìn vào bên trong khối lệnh định nghĩa của kiểu enum có tên Color, chúng ta chỉ thấy những những danh từ như RED, GREEN, BLUE... mà không hề thấy những con số. Thực chất, những danh từ này đã được gắn cho một giá trị cụ thể, và những cái danh từ mà chúng ta nhìn thấy sẽ đại diện cho những giá trị đó. Sử dụng những danh từ để thay thế cho những con số sẽ giúp người đọc chương trình dễ hiểu hơn (chứ không giúp chương trình chạy nhanh hơn).

3. Drawback of using ENUM.

Mặc dù bộ nhớ dành cho ENUM chỉ được cấp khi chúng ta sử dụng nhưng vẫn mất nhiều bộ nhớ hơn so với cái kiểu biến khác Ví dụ Thêm một ENUM đơn sẽ làm tăng kích thước (gấp 13 lần so với số nguyên Integer) của tệp DEX cuối cùng. Nó cũng tạo ra một vài vấn đề về Runtime Overhead and your app will required more space. Nếu không kiểm soat đc ENUM sẽ làm tăng DEX size và tăng runtime memory allocation size.

4. Solution

Android có cũng cấp một thự viện annotation TypeDef. Annotations này đảm bảo rằng một tham số cụ thể, giá trị trả về, hoặc trường tham chiếu đến tập hợp các hằng số cụ thể. Họ cũng cho phép hoàn thành mã để tự động cung cấp các hằng số cho phép. IntDef và StringDef là 2 Magic Constant Annotation mà bạn có thể sử dụng thay thế cho ENUM. Những chú thích này sẽ giúp chúng ta kiểm tra các phép gán biến như Enum trong thời gian biên dịch.

5. How to use?

Let’s take a simple example to understand the use. Bên dưới là một đoạn code của lớp ConstantSeason

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

Thật không may, không có đảm bảo rằng người dùng sẽ sử dụng hàm cóntructor với các giá trị thích hợp - không có kiểu an toàn ở đây. Việc thực hiện tương tự bằng ENUM là:

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

Bây giờ hãy xem cách sử dụng magic constants từ các annotations hỗ trợ : Đầu tiên add dependencies { compile ‘com.android.support:support-annotations:24.2.0’ } Khai báo các hằng số và @IntDef cho các hằng số này:

// 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 {}

Các chú thíc TypeDef ở đây sử dụng một interface để khai báo một loại chú thích kê khai mới . Chú thích @IntDef@StringDef, cùng với @Retention. Chú ý @Retention(RetentionPolicy.SOURCE) nói với trình biên dịch không lưu trữ dữ liệu chú thích đã liệt kê trong tệp .class. Vì vậy, ví dụ trên sẽ thực hiện là:

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

@StringDef ó thể được sử dụng theo cách tương tự.

// 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 {}

Đây là mô hình hiệu suất của Android về giá của Enum Click this link

6. Conclusion

Enum thêm ít nhất hai lần byte đến tổng kích cỡ APK và có thể sử dụng bộ nhớ RAM 5-10 lần so với các hằng số tương đương. Đây là một lời khuyên thực hành tốt nhất và các vấn đề hiệu suất của ứng dụng. Bạn có thể tìm hiểu thêm về thư viện chú thích hỗ trợ ở đây. Link 1 Link 2

Thanks for reading.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.