Tối ưu hoá việc sử dụng bộ nhớ

Việc tối ưu hoá bộ nhớ là rất quan trọng để đảm bảo hiệu suất mượt mà, ngăn chặn sự cố ứng dụng và duy trì độ ổn định của hệ thống cũng như tình trạng của nền tảng. Mặc dù bạn nên theo dõi và tối ưu hoá mức sử dụng bộ nhớ trong mọi ứng dụng, nhưng ứng dụng nội dung cho TV có những thách thức cụ thể khác với ứng dụng Android thông thường dành cho thiết bị cầm tay.

Mức tiêu thụ bộ nhớ cao có thể dẫn đến các vấn đề về hành vi của ứng dụng và hệ thống, bao gồm:

  • Chính ứng dụng có thể bị chậm hoặc giật, hoặc trong trường hợp xấu nhất, bị tắt.
  • Các dịch vụ hệ thống hiển thị với người dùng (Bảng điều khiển Điều khiển âm lượng, Cài đặt hình ảnh, Trợ lý thoại, v.v.) bị trễ rất nhiều hoặc có thể không hoạt động.
  • Các thành phần hệ thống có thể bị tắt; sau đó, các thành phần này sẽ khởi động lại, kích hoạt các đỉnh của sự tranh chấp tài nguyên cực độ và ảnh hưởng trực tiếp đến ứng dụng trên nền trước.
  • Quá trình chuyển đổi sang Trình chạy có thể bị trì hoãn đáng kể và khiến ứng dụng trên nền trước có vẻ như không phản hồi cho đến khi quá trình chuyển đổi kết thúc.
  • Hệ thống có thể chuyển sang tình huống thu hồi trực tiếp, tạm dừng quá trình thực thi luồng trong khi chờ phân bổ bộ nhớ. Điều này có thể xảy ra với bất kỳ luồng nào, chẳng hạn như luồng chính hoặc các luồng liên quan đến bộ mã hoá và giải mã, có thể gây ra tình trạng sụt khung hình âm thanh và video cũng như sự cố giao diện người dùng.

Những điểm cần lưu ý về bộ nhớ trên Thiết bị TV

Thiết bị TV thường có bộ nhớ ít hơn đáng kể so với điện thoại hoặc máy tính bảng. Ví dụ: cấu hình mà chúng ta có thể thấy trên TV là 1 GB RAM và độ phân giải video 1080p. Đồng thời, hầu hết các ứng dụng TV đều có các tính năng tương tự nhau; do đó, cách triển khai và các thách thức phổ biến cũng tương tự nhau. Hai trường hợp này cho thấy các vấn đề không thấy trong các loại thiết bị và ứng dụng khác:

  • Ứng dụng truyền hình đa phương tiện thường bao gồm cả thành phần hiển thị hình ảnh dạng lướihình nền toàn màn hình, đòi hỏi phải tải nhiều hình ảnh vào bộ nhớ trong một khoảng thời gian ngắn
  • Ứng dụng TV phát luồng nội dung đa phương tiện, yêu cầu phân bổ một lượng bộ nhớ nhất định để phát video và âm thanh, đồng thời cần có bộ đệm nội dung đa phương tiện đáng kể để đảm bảo phát mượt mà.
  • Các tính năng đa phương tiện bổ sung (tìm kiếm, thay đổi tập, thay đổi bản âm thanh, v.v.) có thể gây áp lực bộ nhớ nếu không được triển khai đúng cách.

Tìm hiểu về thiết bị TV

Hướng dẫn này chủ yếu tập trung vào mức sử dụng bộ nhớ ứng dụng và mục tiêu bộ nhớ cho các thiết bị có dung lượng RAM thấp.

Trên thiết bị TV, hãy cân nhắc những đặc điểm sau:

  • Bộ nhớ thiết bị: Dung lượng Bộ nhớ truy cập ngẫu nhiên (RAM) mà thiết bị đã cài đặt.
  • Độ phân giải giao diện người dùng của thiết bị: Độ phân giải mà thiết bị sử dụng để hiển thị giao diện người dùng của hệ điều hành và ứng dụng; độ phân giải này thường thấp hơn độ phân giải video của thiết bị.
  • Độ phân giải video: Độ phân giải tối đa mà thiết bị có thể phát video.

Điều này dẫn đến việc phân loại các loại thiết bị khác nhau và cách các thiết bị đó sử dụng bộ nhớ.

Tóm tắt về thiết bị TV

Bộ nhớ thiết bị Độ phân giải video của thiết bị Độ phân giải giao diện người dùng của thiết bị isLowRAMDevice()
1 GB 1080p 720p
1,5 GB 2160p 1080p
≥1,5 GB 1080p 720p hoặc 1080p Không*
≥2 GB 2160p 1080p Không*

Thiết bị TV có dung lượng RAM thấp

Các thiết bị này đang ở tình huống bị hạn chế về bộ nhớ và sẽ báo cáo ActivityManager.isLowRAMDevice() thành true. Các ứng dụng đang chạy trên thiết bị TV có dung lượng RAM thấp cần triển khai các biện pháp kiểm soát bộ nhớ bổ sung.

Chúng tôi coi những thiết bị có các đặc điểm sau đây thuộc danh mục này:

  • Thiết bị 1 GB: 1 GB RAM, độ phân giải giao diện người dùng 720p/HD (1280x720), độ phân giải video 1080p/FullHD (1920x1080)
  • Thiết bị 1,5 GB: RAM 1,5 GB, độ phân giải giao diện người dùng 1080p/FullHD (1920x1080), độ phân giải video 2160p/UltraHD/4K (3840x2160)
  • Các trường hợp khác mà OEM xác định cờ ActivityManager.isLowRAMDevice() do các quy tắc hạn chế bộ nhớ bổ sung.

Thiết bị TV thông thường

Những thiết bị này không gặp phải tình trạng áp lực bộ nhớ đáng kể như vậy. Chúng tôi coi những thiết bị này có các đặc điểm sau:

  • RAM ≥ 1,5 GB, giao diện người dùng 720p hoặc 1080p và độ phân giải video 1080p
  • RAM ≥2 GB, giao diện người dùng 1080p và độ phân giải video 1080p hoặc 2160p

Điều này không có nghĩa là ứng dụng không nên quan tâm đến mức sử dụng bộ nhớ trên các thiết bị này, vì một số trường hợp sử dụng bộ nhớ sai cách vẫn có thể làm cạn kiệt bộ nhớ có sẵn và hoạt động kém hiệu quả.

Mục tiêu bộ nhớ trên thiết bị TV có dung lượng RAM thấp

Khi đo lường bộ nhớ trên các thiết bị này, bạn nên giám sát mọi phần bộ nhớ bằng trình phân tích bộ nhớ trong Android Studio. Ứng dụng TV nên phân tích mức sử dụng bộ nhớ và cố gắng đưa các danh mục của ứng dụng xuống dưới ngưỡng mà chúng ta xác định trong phần này.

trình phân tích bộ nhớ

Trong phần Cách tính bộ nhớ, bạn sẽ thấy nội dung giải thích chi tiết về các số liệu bộ nhớ được báo cáo. Để xác định ngưỡng cho ứng dụng TV, chúng ta sẽ tập trung vào ba danh mục bộ nhớ:

  • Ẩn danh + Hoán đổi: Bao gồm bộ nhớ phân bổ Java + Gốc + Ngăn xếp trong Android Studio.
  • Đồ hoạ: Được báo cáo trực tiếp trên công cụ phân tích tài nguyên. Thường bao gồm các hoạ tiết đồ hoạ.
  • Tệp: Được báo cáo là danh mục "Mã" + "Khác" trong Android Studio.

Với các định nghĩa này, bảng sau đây cho biết giá trị tối đa mà mỗi loại nhóm bộ nhớ nên sử dụng:

Loại bộ nhớ Mục đích Mục tiêu sử dụng (1 GB)
Anonymous + Swap (Java + Native + Stack) Dùng cho các hoạt động phân bổ, vùng đệm nội dung nghe nhìn, biến và các tác vụ khác cần nhiều bộ nhớ. Dưới 160 MB
Đồ hoạ Được GPU sử dụng cho các kết cấu và hiển thị các bộ đệm liên quan 30-40 MB
Tệp Dùng cho các trang mã và tệp trong bộ nhớ. 60-80 MB

Tổng bộ nhớ tối đa (Ẩn danh + Hoán đổi + Đồ hoạ + Tệp) không được vượt quá các giá trị sau:

  • Tổng mức sử dụng bộ nhớ 280 MB (Anon+Swap + Graphics + File) đối với thiết bị có dung lượng RAM thấp 1 GB.

Bạn nên không vượt quá:

  • Mức sử dụng bộ nhớ 200 MB trên (Anon+Swap + Graphics).

Bộ nhớ tệp

Dưới đây là hướng dẫn chung về bộ nhớ được hỗ trợ bằng tệp:

  • Nhìn chung, bộ nhớ tệp được quản lý bộ nhớ của hệ điều hành xử lý tốt.
  • Hiện tại, chúng tôi chưa tìm thấy nguyên nhân chính gây ra áp lực bộ nhớ.

Tuy nhiên, khi xử lý Bộ nhớ tệp nói chung:

  • Không đưa các thư viện không sử dụng vào bản dựng và sử dụng các tập hợp con nhỏ của thư viện thay vì các tập hợp con đầy đủ khi có thể.
  • Đừng mở các tệp lớn vào bộ nhớ và giải phóng các tệp đó ngay khi bạn hoàn tất.
  • Giảm thiểu kích thước mã đã biên dịch cho các lớp Java và Kotlin, hãy xem hướng dẫn Rút gọn, làm rối mã nguồn và tối ưu hoá ứng dụng.

Nội dung đề xuất cụ thể trên TV

Phần này đưa ra các đề xuất cụ thể để tối ưu hoá mức sử dụng bộ nhớ trên thiết bị TV.

Bộ nhớ đồ hoạ

Sử dụng định dạng và độ phân giải hình ảnh phù hợp.

  • Không tải hình ảnh có độ phân giải cao hơn độ phân giải giao diện người dùng của thiết bị. Ví dụ: hình ảnh 1080p phải được giảm kích thước xuống 720p trên thiết bị có giao diện người dùng 720p.
  • Sử dụng bitmap được hỗ trợ phần cứng khi có thể.
    • Trên các thư viện như Glide, hãy bật tính năng Downsampler.ALLOW_HARDWARE_CONFIG bị tắt theo mặc định. Việc bật tính năng này sẽ tránh việc trùng lặp bitmap, nếu không thì bitmap sẽ nằm trong cả bộ nhớ đồ hoạ và bộ nhớ ẩn danh.
  • Tránh hiển thị trung gian và hiển thị lại
    • Bạn có thể xác định các lỗi này bằng Android GPU Inspector:
    • Hãy xem phần "Textures" (Họa tiết) để biết các hình ảnh là các bước hướng tới kết quả kết xuất cuối cùng thay vì chỉ là các phần tử tạo nên hình ảnh, đây thường được gọi là "kết xuất trung gian".
    • Đối với các ứng dụng SDK Android, bạn thường có thể xoá các thành phần này bằng cách sử dụng cờ bố cục forceHasOverlappedRendering:false để tắt chế độ kết xuất trung gian cho bố cục này.
    • Hãy xem phần Tránh hiển thị chồng chéo về việc hiển thị chồng chéo để biết thêm thông tin hữu ích.
  • Tránh tải hình ảnh giữ chỗ khi có thể, hãy sử dụng @android:color/ hoặc @color cho hoạ tiết giữ chỗ.
  • Tránh kết hợp nhiều hình ảnh trên thiết bị khi có thể thực hiện quá trình kết hợp khi không có kết nối mạng. Ưu tiên tải hình ảnh độc lập thay vì kết hợp hình ảnh từ hình ảnh đã tải xuống
  • Làm theo hướng dẫn Xử lý bitmap để xử lý Bitmap hiệu quả hơn.

Anon+Bộ nhớ hoán đổi

Anon+Swap bao gồm các lượt phân bổ Native + Java + Stack trong trình phân tích bộ nhớ Android Studio. Sử dụng ActivityManager.isLowMemoryDevice() để kiểm tra xem thiết bị có bị hạn chế bộ nhớ hay không và điều chỉnh theo tình huống này theo các nguyên tắc sau.

  • Nội dung đa phương tiện:
    • Chỉ định kích thước biến đổi cho vùng đệm nội dung nghe nhìn tuỳ thuộc vào RAM của thiết bị và độ phân giải phát video. Thời lượng này sẽ tính cho 1 phút phát video:
      1. 40-60 MB cho 1 GB / 1080p
      2. 60-80 MB cho 1,5 GB / 1080p
      3. 80-100 MB cho 1,5 GB / 2160p
      4. 100-120 MB cho 2 GB / 2160p
    • Phân bổ bộ nhớ phương tiện trống khi thay đổi một tập để ngăn tổng dung lượng bộ nhớ Anonymous tăng lên.
    • Giải phóng và dừng tài nguyên đa phương tiện ngay lập tức khi ứng dụng của bạn bị dừng: Sử dụng lệnh gọi lại vòng đời của hoạt động để xử lý tài nguyên âm thanh và video. Nếu bạn không phải là ứng dụng âm thanh, hãy dừng phát khi onStop() xảy ra trên các hoạt động của bạn, lưu tất cả công việc bạn đang thực hiện và đặt tài nguyên của bạn để phát hành. Để lên lịch công việc mà bạn có thể cần sau này. Xem phần Công việc và chuông báo.
    • Lưu ý đến bộ nhớ của vùng đệm khi tua video: Nhà phát triển thường phân bổ thêm 15 đến 60 giây nội dung trong tương lai khi muốn sẵn sàng phát video cho người dùng, nhưng điều này sẽ làm tăng thêm mức hao tổn bộ nhớ. Nhìn chung, không nên lưu vào bộ đệm quá 5 giây cho tương lai cho đến khi người dùng chọn vị trí video mới. Nếu bạn cần phải lưu vào bộ đệm trước thêm thời gian trong khi tua, hãy nhớ:
      • Phân bổ vùng đệm tua trước và sử dụng lại vùng đệm đó.
      • Kích thước vùng đệm không được lớn hơn 15-25 MB (tuỳ thuộc vào bộ nhớ thiết bị).
  • Phân bổ:
    • Hãy sử dụng hướng dẫn về bộ nhớ đồ hoạ để đảm bảo rằng bạn không sao chép hình ảnh trong bộ nhớ Anonymous
      • Hình ảnh thường là yếu tố tiêu tốn bộ nhớ nhiều nhất, vì vậy, việc sao chép hình ảnh có thể gây áp lực lớn cho thiết bị. Điều này đặc biệt đúng trong quá trình điều hướng nhiều lần của chế độ xem lưới hình ảnh.
    • Giải phóng các lượt phân bổ bằng cách thả các tệp tham chiếu khi di chuyển màn hình: Đảm bảo không có tệp tham chiếu nào đến bitmap và đối tượng bị bỏ lại.
  • Thư viện:
    • Phân tích mức phân bổ bộ nhớ từ các thư viện khi thêm các thư viện mới, vì các thư viện này cũng có thể tải các thư viện bổ sung, cũng có thể phân bổ và tạo Liên kết.
  • Kết nối mạng:
    • Không thực hiện các lệnh gọi mạng chặn trong quá trình khởi động ứng dụng, vì các lệnh gọi này làm chậm thời gian khởi động ứng dụng và tạo thêm hao tổn bộ nhớ khi khởi chạy, trong đó bộ nhớ bị hạn chế đặc biệt bởi tải ứng dụng. Trước tiên, hãy hiển thị màn hình tải hoặc màn hình chờ rồi thực hiện các yêu cầu mạng sau khi giao diện người dùng đã sẵn sàng.

Liên kết

Liên kết gây ra thêm hao tổn bộ nhớ vì các liên kết này đưa các ứng dụng khác vào bộ nhớ hoặc tăng mức sử dụng bộ nhớ của ứng dụng được liên kết (nếu ứng dụng đó đã có trong bộ nhớ) để hỗ trợ lệnh gọi API. Do đó, việc này sẽ giảm bộ nhớ có sẵn cho ứng dụng trên nền trước. Khi liên kết một dịch vụ, hãy lưu ý thời điểm và thời lượng bạn sử dụng liên kết. Hãy nhớ giải phóng liên kết ngay khi không cần thiết.

Các liên kết thông thường và các phương pháp hay nhất:

  • API Tính toàn vẹn của Play: Dùng để kiểm tra tính toàn vẹn của thiết bị
    • Kiểm tra tính toàn vẹn của thiết bị sau màn hình tải và trước khi phát nội dung đa phương tiện
    • Phát hành các tệp tham chiếu đến PlayIntegrity StandardIntegrityManager trước khi phát nội dung.
  • Thư viện Play Billing: Dùng để quản lý gói thuê bao và giao dịch mua bằng Google Play
    • Khởi chạy thư viện sau màn hình tải và xử lý tất cả công việc thanh toán trước khi phát nội dung nghe nhìn.
    • Sử dụng BillingClient.endConnection() khi bạn đã sử dụng xong thư viện và luôn sử dụng trước khi phát video hoặc nội dung nghe nhìn.
    • Sử dụng BillingClient.isReady()BillingClient.getConnectionState() để kiểm tra xem dịch vụ có bị ngắt kết nối hay không trong trường hợp cần thực hiện lại bất kỳ công việc thanh toán nào, sau đó thực hiện lại BillingClient.endConnection() sau khi hoàn tất.
  • GMS FontsProvider
    • Ưu tiên sử dụng phông chữ độc lập trên các thiết bị có dung lượng RAM thấp thay vì sử dụng trình cung cấp phông chữ, vì việc tải phông chữ xuống sẽ tốn kém và FontsProvider sẽ liên kết các dịch vụ để thực hiện việc này.
  • Thư viện Trợ lý Google: Đôi khi được dùng để tìm kiếm và tìm kiếm trong ứng dụng, nếu có thể, hãy thay thế thư viện này.
    • Đối với ứng dụng leanback: Sử dụng tính năng chuyển văn bản sang lời nói của Gboard hoặc thư viện androidx.leanback.
      • Tuân thủ Nguyên tắc về Tìm kiếm để triển khai tính năng tìm kiếm.
      • Lưu ý: Chúng tôi không dùng leanback nữa và các ứng dụng nên chuyển sang TV Compose.
    • Đối với ứng dụng Compose:
      • Sử dụng tính năng chuyển văn bản sang lời nói của Gboard để triển khai tính năng tìm kiếm bằng giọng nói.
    • Triển khai tính năng Xem tiếp theo để giúp người dùng khám phá nội dung nghe nhìn trong ứng dụng của bạn.

Dịch vụ trên nền trước

Dịch vụ trên nền trước là một loại dịch vụ đặc biệt liên kết với một thông báo. Thông báo này xuất hiện trên khay thông báo trên điện thoại và máy tính bảng, nhưng các thiết bị TV không có khay thông báo giống như các thiết bị đó. Ngay cả khi Dịch vụ trên nền trước hữu ích vì có thể tiếp tục chạy trong khi ứng dụng ở chế độ nền, các ứng dụng TV vẫn phải tuân thủ các nguyên tắc sau:

Trên Android TV và Google TV, Dịch vụ trên nền trước chỉ được phép tiếp tục chạy sau khi người dùng rời khỏi ứng dụng:

  • Đối với ứng dụng âm thanh: Dịch vụ trên nền trước chỉ được phép tiếp tục chạy sau khi người dùng rời khỏi ứng dụng để tiếp tục phát bản âm thanh. Dịch vụ phải được dừng ngay sau khi quá trình phát âm thanh kết thúc.
  • Đối với mọi ứng dụng khác: phải dừng tất cả Dịch vụ trên nền trước sau khi người dùng rời khỏi ứng dụng của bạn, vì không có thông báo nào cho người dùng biết rằng ứng dụng vẫn đang chạy và tiêu thụ tài nguyên.
  • Đối với các công việc ở chế độ nền như cập nhật nội dung đề xuất hoặc Watch Next, hãy sử dụng WorkManager.

Công việc và chuông báo

WorkManager là API Android hiện đại để lên lịch công việc định kỳ ở chế độ nền. WorkManager sẽ sử dụng JobScheduler mới khi có (SDK 23 trở lên) và AlarmManager cũ khi không có. Để biết các phương pháp hay nhất để thực hiện công việc theo lịch trên TV, hãy làm theo các đề xuất sau:

  • Tránh sử dụng các API AlarmManager trên SDK 23 trở lên, đặc biệt là AlarmManager.set(), AlarmManager.setExact() và các phương thức tương tự, vì các API này không cho phép hệ thống quyết định thời điểm thích hợp để chạy công việc (ví dụ: khi thiết bị ở trạng thái rảnh).
  • Trên các thiết bị có dung lượng RAM thấp, hãy tránh chạy công việc trừ phi thực sự cần thiết. Nếu cần, hãy sử dụng WorkManager WorkRequest chỉ để cập nhật nội dung đề xuất sau khi phát và cố gắng thực hiện việc này khi ứng dụng vẫn đang mở.
  • Xác định WorkManager Constraints để cho phép hệ thống chạy công việc của bạn khi thời điểm thích hợp:

Kotlin

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()

Java

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
  • Nếu bạn phải chạy công việc thường xuyên (ví dụ: để cập nhật Watch Next dựa trên hoạt động xem nội dung của người dùng trong ứng dụng trên một thiết bị khác), hãy giảm mức sử dụng bộ nhớ để mức sử dụng bộ nhớ của công việc dưới 30 MB.

Các nguyên tắc chung khác

Các nguyên tắc sau đây cung cấp thông tin chung về việc phát triển Ứng dụng Android:

  • Giảm thiểu việc phân bổ đối tượng, tối ưu hoá việc sử dụng lại đối tượng và nhanh chóng giải phóng mọi đối tượng không dùng đến.
    • Không giữ tham chiếu đến các đối tượng, đặc biệt là bitmap.
    • Tránh sử dụng System.gc() và các lệnh gọi giải phóng bộ nhớ trực tiếp vì các lệnh này sẽ can thiệp vào quá trình xử lý bộ nhớ của hệ thống: Ví dụ: trong các thiết bị sử dụng zRAM, lệnh gọi buộc đến gc() có thể tạm thời làm tăng mức sử dụng bộ nhớ do quá trình nén và giải nén bộ nhớ.
    • Sử dụng LazyList như được minh hoạ trong trình duyệt danh mục trong Compose hoặc RecyclerView trong bộ công cụ giao diện người dùng Leanback hiện không còn được dùng nữa để sử dụng lại các thành phần hiển thị và không tạo lại các phần tử danh sách.
    • Lưu các phần tử được đọc từ trình cung cấp nội dung bên ngoài vào bộ nhớ đệm cục bộ. Các phần tử này không có khả năng thay đổi và xác định khoảng thời gian cập nhật để ngăn việc phân bổ thêm bộ nhớ ngoài.
  • Kiểm tra xem có thể xảy ra rò rỉ bộ nhớ hay không.
    • Cẩn thận với các trường hợp rò rỉ bộ nhớ điển hình, chẳng hạn như các tệp tham chiếu bên trong luồng ẩn danh, việc phân bổ lại vùng đệm video không bao giờ được giải phóng và các trường hợp tương tự khác.
    • Sử dụng tệp báo lỗi để gỡ lỗi rò rỉ bộ nhớ.
  • Tạo hồ sơ cơ sở để giảm thiểu lượng biên dịch vừa kịp thời cần thiết khi thực thi ứng dụng ở chế độ khởi động nguội.

Tóm tắt về công cụ