Quản lý bộ nhớ của ứng dụng

Trang này giải thích cách chủ động giảm mức sử dụng bộ nhớ trong ứng dụng của bạn. Để biết thông tin về cách hệ điều hành Android quản lý bộ nhớ, hãy xem bài viết Tổng quan về việc quản lý bộ nhớ.

Bộ nhớ truy xuất ngẫu nhiên (RAM) là một tài nguyên quan trọng trong mọi môi trường phát triển phần mềm, thậm chí còn quan trọng hơn nữa trên hệ điều hành dành cho thiết bị di động do nơi này thường có bộ nhớ thực bị hạn chế. Mặc dù cả môi trường máy ảo Android Runtime (ART) và Dalvik đều thực hiện quy trình thu gom rác thông thường, nhưng điều đó không có nghĩa là bạn có thể bỏ qua thời điểm và vị trí mà ứng dụng phân bổ cũng như giải phóng bộ nhớ. Bạn vẫn cần tránh gây ra tình trạng rò rỉ bộ nhớ (thường là do việc giữ lại thông tin tham chiếu đến đối tượng trong các biến thành phần tĩnh) và giải phóng mọi đối tượng Reference vào thời điểm thích hợp như được xác định bằng các phương thức gọi lại trong vòng đời.

Giảm mức sử dụng mã và tài nguyên của ứng dụng

Một số tài nguyên và thư viện trong mã nguồn của bạn có thể chiếm dụng bộ nhớ mà bạn không biết. Kích thước tổng thể của ứng dụng (bao gồm cả thư viện của bên thứ ba hoặc tài nguyên được nhúng) có thể ảnh hưởng đến mức tiêu thụ bộ nhớ của ứng dụng. Bạn có thể cải thiện mức tiêu thụ bộ nhớ của ứng dụng bằng cách xoá mọi thành phần, tài nguyên và thư viện thừa, không cần thiết hoặc chiếm dụng bộ nhớ khỏi mã nguồn của mình.

Giảm kích thước tổng thể của ứng dụng bằng cách bật R8

Mã xử lý ứng dụng đã biên dịch là một phần đang hoạt động trong mức sử dụng bộ nhớ thời gian chạy của bạn. Mọi lớp, phương thức, phần phụ thuộc thư viện và hằng số chuỗi đều phải được tải vào RAM khi chạy. Cơ sở mã đã biên dịch càng lớn thì ứng dụng của bạn càng cần nhiều RAM thực để tồn tại.

Bạn có thể dùng R8 để giảm mức sử dụng bộ nhớ của ứng dụng. Mặc dù R8 thường được biết đến với khả năng giảm kích thước APK, nhưng nó có tác động trực tiếp và tích cực đến bộ nhớ thời gian chạy (RAM). R8 phân tích mã byte của ứng dụng để loại bỏ mã không dùng đến, hợp nhất các lớp dư thừa, phương thức nội tuyến và rút gọn mã nhận dạng. Bằng cách tải ít mã byte đã biên dịch hơn từ APK vào RAM, điều này sẽ làm giảm mức sử dụng bộ nhớ cơ bản tổng thể của ứng dụng. Ngoài ra, việc rút gọn tên lớp, phương thức và trường thành các giá trị nhận dạng ngắn hơn sẽ trực tiếp làm giảm mức hao tổn RAM. Các hoạt động tối ưu hoá như hợp nhất lớp và chèn cùng dòng phương thức mở rộng cũng thay thế các mẫu phân bổ và tra cứu thời gian chạy tốn kém, dẫn đến việc tối ưu hoá bộ nhớ ngăn xếp và vùng nhớ heap.

Tìm hiểu về quy tắc giữ lại

Quy tắc lưu giữ là các chỉ dẫn cấu hình cho R8 biết những phần nào trong mã của bạn cần giữ lại trong quá trình tối ưu hoá, ngăn R8 xoá hoặc giảm kích thước mã mà ứng dụng của bạn dựa vào. Để biết thêm thông tin, hãy xem bài viết Tổng quan về các quy tắc của Keep.

Các quy tắc giữ được viết không đúng cách sẽ ngăn R8 tối ưu hoá các phần lớn trong cơ sở mã của bạn. Tránh các quy tắc giữ lại quá rộng và làm theo các phương pháp hay nhất sau đây:

  • Các quy tắc chung cần tránh:
    • -dontoptimize: Tắt hoàn toàn tính năng tối ưu hoá cho toàn bộ ứng dụng, dẫn đến các tệp thực thi lớn hơn và chậm hơn.
    • -dontshrink: Ngăn chặn việc xoá mã và tài nguyên không dùng đến.
    • -dontobfuscate: Ngăn việc giảm thiểu tên, bỏ lỡ cơ hội tiết kiệm bộ nhớ có giá trị (đặc biệt là trong các ứng dụng lớn).
  • Tránh dùng ký tự đại diện trên toàn gói: Các quy tắc chung như -keep class com.example.package.** { *; } buộc R8 phải giữ lại mọi lớp, trường và phương thức trong gói đó. Điều này hoàn toàn ngăn chặn khả năng xoá, tối ưu hoá hoặc rút gọn mã của R8 trong gói đó.

  • Sử dụng tệp cấu hình R8 mặc định: Luôn sử dụng proguard-android-optimize.txt.

Để biết thêm thông tin về cách viết quy tắc giữ lại, hãy xem bài viết Tổng quan về quy tắc giữ lại. Để biết các mẫu cụ thể cần sử dụng và tránh sử dụng, hãy xem bài viết Các phương pháp hay nhất về quy tắc Keep.

Trình phân tích cấu hình R8 cung cấp thông tin chi tiết về cấu hình R8 và mức độ ảnh hưởng của từng quy tắc giữ lại đến ứng dụng của bạn. Để biết thêm thông tin về cách xác định các quy tắc chặn hoạt động tối ưu hoá, hãy xem phần Trình phân tích cấu hình R8.

Cẩn thận khi dùng thư viện bên ngoài

Mã nguồn thư viện bên ngoài thường không được viết cho môi trường thiết bị di động và có thể không hiệu quả khi được sử dụng cho hoạt động trên ứng dụng khách dành cho thiết bị di động. Khi dùng thư viện bên ngoài, bạn có thể phải tối ưu hoá thư viện đó cho thiết bị di động. Hãy lên kế hoạch trước cho việc này và phân tích thư viện về kích thước mã cũng như dung lượng RAM trước khi sử dụng.

Ngay cả một số thư viện được tối ưu hoá cho thiết bị di động cũng có thể gây ra sự cố do các cách triển khai khác nhau. Ví dụ: một thư viện có thể sử dụng các giao thức protobuf lite trong khi một thư viện khác sử dụng các giao thức protobuf micro, dẫn đến hai cách triển khai protobuf khác nhau trong ứng dụng của bạn. Điều này có thể xảy ra khi bạn triển khai nhiều phương thức ghi nhật ký, phân tích, tải hình ảnh, lưu vào bộ nhớ đệm và nhiều tính năng khác mà bạn không mong đợi.

Mặc dù việc tối ưu hoá ứng dụng bằng R8 có thể xoá mã không dùng đến khỏi các phần phụ thuộc, nhưng hiệu quả của việc này thường bị giới hạn bởi cấu hình nội bộ của thư viện. Ví dụ: các quy tắc giữ lại diện rộng hoặc việc sử dụng tính năng phản chiếu trong một thư viện có thể ngăn R8 thu gọn mã của thư viện đó, dẫn đến mức sử dụng bộ nhớ lớn hơn. Để biết các chiến lược chọn thư viện hiệu quả, hãy xem phần Chọn thư viện một cách khôn ngoan.

Bạn nên tránh sử dụng thư viện dùng chung chỉ cho một hoặc hai tính năng trong số hàng chục tính năng. Đừng lấy một lượng lớn mã và chịu chí phí hao tổn mà bạn không sử dụng. Khi bạn cân nhắc có nên sử dụng một thư viện hay không, hãy tìm một cách triển khai phù hợp nhất với nhu cầu của bạn. Nếu không, bạn có thể quyết định tạo cách triển khai của riêng mình.

Dùng Hilt hoặc Dagger 2 để chèn phần phụ thuộc

Khung chèn phần phụ thuộc có thể đơn giản hoá mã bạn viết và cung cấp một môi trường thích ứng hữu ích cho việc kiểm thử cũng như các thay đổi khác về cấu hình.

Nếu bạn định dùng một khung chèn phần phụ thuộc trong ứng dụng của mình, hãy cân nhắc dùng Hilt hoặc Dagger. Hilt là một thư viện chèn phần phụ thuộc cho Android chạy trên Dagger. Dagger không dùng chức năng phản chiếu để quét mã của ứng dụng. Bạn có thể dùng phương thức triển khai thời gian biên dịch tĩnh của Dagger trong các ứng dụng Android mà không cần tốn thời gian chạy hay chiếm mức sử dụng bộ nhớ không cần thiết.

Các khung chèn phần phụ thuộc khác sử dụng tính năng phản chiếu sẽ khởi chạy các quy trình bằng cách quét mã để tìm chú thích. Quy trình này có thể yêu cầu nhiều chu kỳ và RAM hơn trong CPU, đồng thời có thể gây ra độ trễ đáng kể khi ứng dụng khởi chạy.

Khi sử dụng tính năng chèn phần phụ thuộc, hãy cẩn thận để tránh rò rỉ bộ nhớ bằng cách đảm bảo rằng các đối tượng được đặt phạm vi một cách thích hợp. Việc giữ lại các đối tượng lâu hơn mức cần thiết bằng cách liên kết chúng với vòng đời không chính xác có thể dẫn đến rò rỉ bộ nhớ. Để biết thêm thông tin, hãy xem hướng dẫn về cách tránh rò rỉ bộ nhớ bằng các đối tượng có phạm vi.

Chủ động tải hình ảnh

Bitmap đồ hoạ thường là các đối tượng chung lớn nhất nằm trong bộ nhớ của ứng dụng. Ngay cả khi bạn đang làm việc với các tệp nén như JPEG, tệp đó phải được mở rộng thành một bitmap chưa nén để hiển thị trên màn hình. Một tệp hình ảnh nén nhỏ có thể mở rộng thành một bitmap rất lớn.

Ví dụ: hầu hết các bitmap đều sử dụng cấu hình ARGB_8888, tức là mỗi pixel cần 4 byte bộ nhớ – mỗi byte cho màu đỏ, xanh lục, xanh dương và alpha (độ trong suốt). Nếu bạn có một tệp JPEG 100 KB và hiển thị tệp đó trong khung hiển thị 1000×1000 pixel, thì bitmap sẽ cần 4 byte cho mỗi 1.000.000 pixel đó, cộng thêm tối đa 4 MB bộ nhớ.

Bạn có thể làm một số việc để tối ưu hoá việc sử dụng hình ảnh. Ví dụ: việc sử dụng các thư viện tải hình ảnh có thể giúp bạn giải phóng bộ nhớ khi không cần thiết. Để biết thông tin về cách xử lý hình ảnh một cách hiệu quả, hãy xem phần Tối ưu hoá hình ảnh bitmap.

Giám sát mức sử dụng bộ nhớ và bộ nhớ hiện có

Bạn cần tìm thấy các vấn đề về mức sử dụng bộ nhớ của ứng dụng rồi mới có thể khắc phục. Trình phân tích bộ nhớ của Android Studio giúp bạn tìm và chẩn đoán các vấn đề về bộ nhớ theo những cách sau:

Trình phân tích bộ nhớ cũng tích hợp với thư viện phát hiện rò rỉ LeakCanary. Bằng cách sử dụng LeakCanary, bạn có thể chuyển quy trình phân tích rò rỉ bộ nhớ từ thiết bị kiểm thử sang máy phát triển, điều này có thể giúp tăng tốc đáng kể quy trình làm việc của bạn. Để biết thêm thông tin, hãy xem ghi chú phát hành của Android Studio.

Bạn có thể dùng các công cụ khác để chẩn đoán các vấn đề về bộ nhớ dựa trên dữ liệu của những người dùng đang chạy ứng dụng phát hành công khai của bạn:

Giải phóng bộ nhớ để phản hồi sự kiện

Android có thể thu hồi bộ nhớ của ứng dụng hoặc dừng ứng dụng hoàn toàn nếu cần để giải phóng bộ nhớ cho các tác vụ quan trọng, như giải thích trong phần Tổng quan về việc quản lý bộ nhớ. Để giúp cân bằng hơn mức bộ nhớ hệ thống và tránh việc hệ thống phải dừng các tiến trình ứng dụng của bạn, bạn có thể triển khai giao diện ComponentCallbacks2 trong các lớp Activity. Phương thức gọi lại onTrimMemory() được cung cấp sẽ thông báo cho ứng dụng của bạn về các sự kiện liên quan đến vòng đời hoặc bộ nhớ. Đây là cơ hội tốt để ứng dụng của bạn tự nguyện giảm mức sử dụng bộ nhớ. Việc giải phóng bộ nhớ có thể giảm tần suất ứng dụng của bạn bị mô đun tắt ứng dụng khi bộ nhớ thấp tắt.

Việc triển khai onTrimMemory() chỉ nên tập trung vào các sự kiện TRIM_MEMORY_UI_HIDDENTRIM_MEMORY_BACKGROUND. (Kể từ Android 14, hệ thống sẽ không gửi thông báo cho các hằng số cũ khác nữa. Những hằng số đó chính thức không được dùng nữa trong Android 15.)

  • TRIM_MEMORY_UI_HIDDEN: Tín hiệu này cho biết giao diện người dùng của ứng dụng đã chuyển ra khỏi chế độ xem của người dùng. Quá trình chuyển đổi này tạo cơ hội giải phóng các hoạt động phân bổ bộ nhớ đáng kể gắn liền với giao diện người dùng, chẳng hạn như bitmap, bộ đệm phát video hoặc tài nguyên ảnh động phức tạp.

  • TRIM_MEMORY_BACKGROUND: Tín hiệu này cho biết quy trình của bạn đang nằm ở chế độ nền và hiện là đối tượng có thể bị chấm dứt để đáp ứng nhu cầu bộ nhớ chung của hệ thống. Để kéo dài thời gian quy trình của bạn vẫn ở trạng thái được lưu vào bộ nhớ đệm và giảm số lần khởi động nguội ứng dụng, bạn nên giải phóng mọi tài nguyên có thể dễ dàng tạo lại khi người dùng tiếp tục phiên hoạt động của họ.

Mã mẫu này cho biết cách triển khai lệnh gọi lại onTrimMemory() để phản hồi nhiều sự kiện liên quan đến bộ nhớ:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Kiểm tra dung lượng bộ nhớ bạn cần

Để cho phép nhiều quy trình chạy, Android đặt giới hạn cố định cho dung lượng vùng nhớ khối xếp được phân bổ cho mỗi ứng dụng. Giới hạn dung lượng vùng nhớ khối xếp chính xác sẽ khác nhau giữa các thiết bị tuỳ theo tổng dung lượng RAM có sẵn. Nếu ứng dụng của bạn đạt đến hạn mức vùng nhớ khối xếp và đang cố gắng phân bổ thêm bộ nhớ, hệ thống sẽ gửi một OutOfMemoryError.

Để tránh tình trạng hết bộ nhớ, bạn có thể truy vấn hệ thống để xác định dung lượng vùng nhớ khối xếp hiện có trên thiết bị hiện tại. Bạn có thể truy vấn hệ thống cho số liệu này bằng cách gọi getMemoryInfo(). Thao tác này sẽ trả về một đối tượng ActivityManager.MemoryInfo cung cấp thông tin về trạng thái bộ nhớ hiện tại của thiết bị, bao gồm bộ nhớ còn trống, tổng bộ nhớ và ngưỡng bộ nhớ – mức dung lượng mà tại đó hệ thống bắt đầu dừng các quy trình. Đối tượng ActivityManager.MemoryInfo cũng cho thấy lowMemory (một giá trị boolean đơn giản cho biết liệu thiết bị sắp hết bộ nhớ hay chưa).

Đoạn mã mẫu sau đây cho biết cách dùng phương thức getMemoryInfo() trong ứng dụng của bạn.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

Theo dõi các trường hợp tắt ứng dụng do bộ nhớ thấp

Lỗi tắt ứng dụng khi bộ nhớ thấp (LMK) mà người dùng nhận thấy xảy ra khi bộ nhớ hệ thống xuống mức cực thấp. Khi bộ nhớ thấp, lmkd (trình nền tắt ứng dụng khi bộ nhớ thấp) sẽ chấm dứt các quy trình dựa trên oom_adj_score của chúng. Các ứng dụng được lưu vào bộ nhớ đệm hoặc đang chạy một dịch vụ không có giao diện người dùng liên kết (chẳng hạn như một lệnh) sẽ có điểm số cao nhất và bị chấm dứt trước tiên. Nếu bộ nhớ vẫn ở mức cực thấp, thì trình nền sẽ buộc phải thu hồi bộ nhớ từ các quy trình có oom_adj_score bằng 0. Vì điểm số đó dành riêng cho các ứng dụng hiển thị, nên việc chấm dứt các ứng dụng này sẽ dẫn đến một quy trình thoát ngay lập tức, không có thời gian ân hạn. Đối với người dùng cuối, có vẻ như ứng dụng gặp sự cố, thường bỏ qua các cơ chế lưu trạng thái vòng đời tiêu chuẩn và dẫn đến việc mất tiến trình của người dùng.

Việc loại bỏ các quy trình ở nền trước là trọng tâm chính trong Android Vitals vì chúng đóng vai trò là một proxy có độ trung thực cao cho việc quản lý bộ nhớ sai cách. Mặc dù bất kỳ tỷ lệ LMK nào cao hơn 1% đều cho thấy bạn cần hành động ngay lập tức, nhưng tỷ lệ thấp không nhất thiết là chỉ báo về tình trạng tốt. Tỷ lệ LMK mà người dùng nhận thấy thấp có thể có nghĩa là trình nền LMK thường xuyên tắt các quy trình khi chúng ở chế độ nền, điều này làm giảm hiệu suất "khởi động ấm" và khả năng đa nhiệm mượt mà. Do đó, bạn nên tuân thủ các phương pháp hay nhất về bộ nhớ bất kể điểm LMK hiện tại của bạn là bao nhiêu để đảm bảo độ ổn định lâu dài và tình trạng thiết bị.

Sử dụng ProfilingManager để theo dõi các vấn đề về bộ nhớ

Nền tảng Android cung cấp ProfilingManager, một API quan sát nâng cao cho phép bạn thu thập dữ liệu người dùng trong quá trình phát hành công khai dựa trên các điều kiện kích hoạt mà bạn đặt. Việc này có thể giúp bạn xác định các vấn đề về bộ nhớ khó tái tạo.

Hai trình kích hoạt mới được giới thiệu cùng với Android 17 đặc biệt hữu ích trong việc phát hiện các vấn đề về bộ nhớ:

  • TRIGGER_TYPE_OOM cho biết ứng dụng đã gửi một OutOfMemoryError. Thao tác này sẽ kích hoạt lần tiếp theo ứng dụng bắt đầu sau sự cố, khi ứng dụng đăng ký các trình kích hoạt phân tích tài nguyên.
  • TRIGGER_TYPE_ANOMALY sẽ kích hoạt khi hệ thống phát hiện thấy hành vi bất thường của ứng dụng. Ngoài những thứ khác, điều này có thể được kích hoạt do mức sử dụng bộ nhớ quá mức. Thông báo này sẽ kích hoạt sau khi ứng dụng sử dụng quá nhiều mức sử dụng bộ nhớ và trước khi hệ thống thực hiện bất kỳ hành động nào để dừng tiến trình gây ra lỗi. Ví dụ: nếu ứng dụng vượt quá giới hạn bộ nhớ được giới thiệu trong Android 17, TRIGGER_TYPE_ANOMALY sẽ kích hoạt trước khi hệ thống tắt ứng dụng.

Để biết thêm thông tin về cách sử dụng ProfilingManager để đăng ký và truy xuất các điều kiện kích hoạt theo phương thức lập trình, hãy xem tài liệu về hồ sơ dựa trên điều kiện kích hoạt.

Bạn cũng có thể sử dụng tính năng lập hồ sơ dựa trên ứng dụng để xác định điểm bắt đầu và điểm kết thúc của dấu vết theo cách thủ công. Bạn nên thực hiện việc này để tự động chụp kết xuất heap hoặc hồ sơ heap ở những khu vực mà bạn nghi ngờ có thể bị rò rỉ bộ nhớ hoặc mức sử dụng bộ nhớ quá mức.

Xây dựng một cấu trúc mã tiết kiệm bộ nhớ hơn

Một số tính năng của Android, lớp Java và cấu trúc mã sẽ dùng nhiều bộ nhớ hơn những tính năng khác. Bạn có thể giảm thiểu mức bộ nhớ mà ứng dụng dùng bằng cách chọn các phương án thay thế hiệu quả hơn trong mã nguồn của mình.

Sử dụng các dịch vụ một cách thận trọng

Bạn không nên để dịch vụ tiếp tục hoạt động khi không cần thiết. Việc để các dịch vụ không cần thiết chạy là một trong những lỗi quản lý bộ nhớ nghiêm trọng nhất mà ứng dụng Android có thể mắc phải. Nếu ứng dụng của bạn cần một dịch vụ để hoạt động ở chế độ nền, đừng tiếp tục chạy ứng dụng đó trừ trường hợp ứng dụng đó cần thực hiện tác vụ. Hãy dừng dịch vụ khi hoàn thành tác vụ. Nếu không, bạn có thể gây ra sự cố rò rỉ bộ nhớ.

Khi bạn bắt đầu một dịch vụ, hệ thống ưu tiên giữ cho dịch vụ đó chạy. Hành vi này khiến các tiến trình dịch vụ trở nên rất tốn kém vì RAM mà dịch vụ sử dụng không dùng được cho các tiến trình khác. Điều này làm giảm số lượng các quy trình lưu vào bộ nhớ đệm mà hệ thống có thể giữ trong bộ nhớ đệm LRU, khiến việc chuyển đổi ứng dụng trở nên kém hiệu quả hơn. Điều này thậm chí có thể dẫn đến tình trạng đơ máy trong hệ thống khi bộ nhớ bị quá tải và hệ thống không thể duy trì đủ tiến trình để lưu trữ tất cả dịch vụ hiện đang chạy.

Nhìn chung, hãy tránh dùng các dịch vụ có tính liên tục do những dịch vụ này yêu cầu sử dụng bộ nhớ hiện có. Thay vào đó, bạn nên sử dụng một phương thức triển khai khác, chẳng hạn như WorkManager. Để biết thêm thông tin về cách sử dụng WorkManager nhằm lên lịch cho các quy trình trong nền, hãy xem bài viết Công việc liên tục.

Sử dụng vùng chứa dữ liệu được tối ưu hóa

Một số lớp do ngôn ngữ lập trình cung cấp không được tối ưu hoá để sử dụng trên thiết bị di động. Ví dụ: cách triển khai HashMap chung có thể không hiệu quả vì bộ nhớ cần có một đối tượng mục riêng cho từng mục ánh xạ.

Khung Android bao gồm một số vùng chứa dữ liệu được tối ưu hoá, bao gồm SparseArray, SparseBooleanArrayLongSparseArray. Ví dụ: các lớp SparseArray hiệu quả hơn vì chúng tránh được việc hệ thống phải tự động đóng hộp khoá và đôi khi là cả giá trị (tạo ra một hoặc hai đối tượng khác cho mỗi mục nhập).

Nếu cần, bạn luôn có thể chuyển sang các mảng thô để có cấu trúc dữ liệu tinh gọn.

Hãy cẩn thận với việc trừu tượng hóa mã

Các nhà phát triển thường sử dụng các thành phần trừu tượng dưới dạng phương thức lập trình hiệu quả vì chúng có thể cải thiện tính linh hoạt và khả năng bảo trì mã. Tuy nhiên, các thành phần trừu tượng thường yêu cầu nhiều mã hơn để thực thi. Như đã trình bày chi tiết trong phần Giảm mức sử dụng bộ nhớ cho mã và tài nguyên của ứng dụng, cơ sở mã được biên dịch càng lớn thì RAM thực mà ứng dụng của bạn yêu cầu càng tăng. Nếu các thành phần trừu tượng không mang lại lợi ích đáng kể, hãy tránh sử dụng chúng.

Sử dụng giao thức lite protobuf cho dữ liệu được tuần tự hóa

Vùng đệm giao thức (protobuf) là một cơ chế không phân biệt ngôn ngữ/nền tảng và có thể mở rộng do Google thiết kế để chuyển đổi tuần tự dữ liệu có cấu trúc. Cơ chế này tương tự với XML, nhưng nhỏ hơn, nhanh hơn và đơn giản hơn. Nếu bạn dùng protobuf cho dữ liệu của mình, hãy luôn sử dụng protobuf lite trong mã phía máy khách. Các protobuf thông thường tạo ra mã cực kỳ chi tiết, làm tăng mức sử dụng RAM của mã ứng dụng (xem phần Quản lý và tối ưu hoá mức sử dụng RAM của mã ứng dụng) và góp phần làm tăng kích thước APK.

Để biết thêm thông tin, hãy xem phần protobuf readme.

Hãy thận trọng với tình trạng rò rỉ bộ nhớ

Việc quản lý tham chiếu không đúng cách có thể dẫn đến tình trạng rò rỉ bộ nhớ, trong đó các đối tượng tồn tại lâu hơn tuổi thọ hữu ích của chúng, khiến trình thu gom rác không thể thu hồi bộ nhớ của đối tượng bị rò rỉ. Để tránh rò rỉ bộ nhớ, hãy triển khai thiết kế nhận biết vòng đời.

Để biết thêm thông tin, hãy xem phần Rò rỉ bộ nhớ.

Tránh tình trạng nhồi nhét bộ nhớ

Các sự kiện thu gom rác không ảnh hưởng đến hiệu suất của ứng dụng. Tuy nhiên, nhiều sự kiện thu gom rác xảy ra trong một khoảng thời gian ngắn có thể tiêu hao pin nhanh chóng cũng như tăng nhẹ thời gian thiết lập khung do bộ thu gom rác và các luồng ứng dụng cần phải tương tác với nhau. Hệ thống càng dành nhiều thời gian cho việc thu gom rác thì pin càng tiêu hao nhanh hơn.

Thông thường, tình trạng nhồi nhét bộ nhớ có thể gây ra một lượng lớn các sự kiện thu gom rác. Trên thực tế, tình trạng nhồi nhét bộ nhớ mô tả số lượng các đối tượng tạm thời được phân bổ xảy ra trong một khoảng thời gian nhất định.

Ví dụ: bạn có thể phân bổ nhiều đối tượng tạm thời trong một vòng lặp for. Hoặc bạn có thể tạo đối tượng Paint hoặc Bitmap mới bên trong hàm onDraw() của một khung hiển thị. Trong cả hai trường hợp, ứng dụng sẽ tạo nhanh nhiều đối tượng với khối lượng lớn. Các đối tượng này có thể nhanh chóng sử dụng hết bộ nhớ hiện có trong young generation (nhóm đối tượng được phân bổ gần đây), buộc sự kiện thu gom rác xảy ra.

Hãy dùng Trình phân tích bộ nhớ để tìm những nơi trong mã nguồn của bạn có tình trạng nhồi nhét bộ nhớ cao trước khi bạn có thể khắc phục.

Sau khi xác định được những nơi có vấn đề trong mã nguồn của bạn, hãy cố giảm số lượng đối tượng phân bổ ở những khía cạnh quan trọng về hiệu suất. Bạn nên cân nhắc di chuyển mọi thứ ra khỏi vòng lặp nội bộ hoặc có thể đưa chúng vào cấu trúc phân bổ dựa trên factory.

Bạn cũng có thể đánh giá xem nhóm đối tượng có lợi cho trường hợp sử dụng này hay không. Với một nhóm đối tượng, thay vì bỏ qua một thực thể đối tượng, hãy giải phóng thực thể đối tượng đó vào một nhóm khi không cần dùng nữa. Lần tiếp theo cần dùng thực thể đối tượng thuộc loại đó, bạn có thể lấy thực thể đối tượng đó trong nhóm thay vì phân bổ thực thể đối tượng đó.

Hãy đánh giá kỹ lưỡng hiệu quả để xác định xem một nhóm đối tượng có phù hợp trong một tình huống cụ thể hay không. Có những trường hợp nhóm đối tượng có thể làm giảm hiệu suất. Mặc dù nhóm này giúp tránh tình trạng phân bổ lại, nhưng đồng thời cũng mang lại các hao tổn khác. Ví dụ: việc duy trì nhóm thường liên quan đến quá trình đồng bộ hoá, có chi phí phát sinh không nhỏ. Ngoài ra, việc xoá thực thể đối tượng gộp (để tránh rò rỉ bộ nhớ) trong quá trình giải phóng, sau đó khởi tạo thực thể trong quy trình thu nạp có thể gây ra chi phí.

Việc giữ lại nhiều thực thể đối tượng trong nhóm hơn mức cần thiết cũng sẽ tạo gánh nặng cho việc thu thập rác. Tuy làm giảm số lượng lời gọi thu thập rác, nhưng các nhóm đối tượng lại làm tăng khối lượng công việc cần thực hiện trên mỗi lần gọi vì khối lượng công việc tỷ lệ với số byte đang dùng (có thể tiếp cận).