Duy trì khả năng phản hồi của ứng dụng

Hình 1. Hộp thoại ANR hiển thị cho người dùng.

Tài liệu này mô tả cách hệ thống Android xác định liệu một ứng dụng có phản hồi hay không và hướng dẫn cách duy trì khả năng phản hồi của ứng dụng.

Bất kể mã của bạn được viết chính xác đến mức nào, ứng dụng vẫn có thể bị chậm, treo, đơ trong một khoảng thời gian đáng kể hoặc mất quá nhiều thời gian để xử lý thông tin đầu vào. Nếu ứng dụng của bạn chạy ở nền trước và không phản hồi, thì người dùng sẽ thấy hộp thoại Ứng dụng không phản hồi (ANR), như minh hoạ trong hình 1. Khi hộp thoại ANR xuất hiện, người dùng có thể buộc thoát khỏi ứng dụng. Nếu không chạy ở nền trước, thì ứng dụng sẽ tự động dừng. Điều quan trọng là bạn phải thiết kế khả năng phản hồi cho ứng dụng để giảm thiểu hộp thoại ANR.

Điều kiện kích hoạt ANR

Nhìn chung, hệ thống sẽ hiển thị một hộp thoại ANR nếu một ứng dụng không thể phản hồi hoạt động đầu vào của người dùng trên luồng chính (còn gọi là luồng giao diện người dùng). Điều này sẽ ngăn hệ thống xử lý các sự kiện đến từ hoạt động đầu vào của người dùng.

Ví dụ: lỗi ANR có thể xảy ra nếu một ứng dụng thực hiện một thao tác chặn I/O (chẳng hạn như quyền truy cập mạng) trên luồng giao diện người dùng. Ví dụ khác là trong trường hợp một ứng dụng dành quá nhiều thời gian cho việc xây dựng một cấu trúc chi tiết trong bộ nhớ hoặc tính toán bước tiếp theo trong một trò chơi trên luồng giao diện người dùng.

Trong Android, khả năng phản hồi của ứng dụng được các dịch vụ hệ thống ActivityManagerWindowManager giám sát. Android hiển thị hộp thoại ANR cho ứng dụng khi phát hiện thấy một trong các điều kiện sau xảy ra:

  • Không phản hồi một sự kiện đầu vào (chẳng hạn như sự kiện nhấn phím hoặc nhấn vào màn hình) trong vòng 5 giây.
  • BroadcastReceiver không hoàn tất quá trình thực thi trong vòng từ 10 đến 20 giây đối với các ý định ở nền trước. Để biết thêm thông tin, hãy xem phần Thời gian chờ của broadcast receiver.

Tránh lỗi ANR

Dưới đây là những lưu ý chung giúp tránh lỗi ANR. Nếu bạn muốn biết thêm thông tin chi tiết về cách chẩn đoán và gỡ nhiều loại lỗi ANR, hãy xem các trang khác trong phần này.

  • Luôn bỏ chặn luồng chính và sử dụng các luồng một cách có chiến lược.

    • Không thực hiện các thao tác chặn hoặc chạy trong thời gian dài trên luồng chính của ứng dụng. Thay vào đó, hãy tạo một luồng worker và thực hiện hầu hết thao tác ở đó.

    • Cố gắng giảm thiểu mọi trường hợp tranh chấp khoá giữa luồng chính và các luồng khác.

    • Giảm thiểu mọi thao tác không liên quan đến giao diện người dùng trên luồng chính, chẳng hạn như khi xử lý thông báo truyền tin hoặc chạy các dịch vụ. Mọi phương thức chạy trong luồng giao diện người dùng đều phải thực hiện ít hoạt động nhất có thể trên luồng đó. Cụ thể, phải thực hiện càng ít hoạt động càng tốt để thiết lập các phương thức chính trong vòng đời, chẳng hạn như onCreate()onResume(). Hãy xem bài viết Tổng quan về hoạt động trong nền nếu bạn muốn biết thêm thông tin về các giải pháp hiện có để lên lịch hoạt động trong luồng ở chế độ nền và giao tiếp trở lại với giao diện người dùng.

    • Thận trọng khi chia sẻ nhóm luồng giữa các thành phần. Không dùng cùng một luồng cho các thao tác chặn có thể diễn ra trong thời gian dài và các tác vụ có giới hạn thời gian, chẳng hạn như tác vụ nhận thông báo truyền tin.

  • Giúp ứng dụng khởi động nhanh. Giảm thiểu các thao tác chặn hoặc làm chậm tốc độ trong mã khởi động của ứng dụng, chẳng hạn như các phương thức chạy trong quá trình khởi chạy Dagger.

  • Nếu bạn đang sử dụng BroadcastReceiver, hãy cân nhắc chạy broadcast receiver trong một luồng không phải luồng chính bằng cách dùng Context.registerReceiver. Để biết thêm thông tin, hãy xem phần Lỗi ANR liên quan đến BroadcastReceiver.

    • Nếu bạn sử dụng goAsync(), hãy đảm bảo rằng PendingResult.finish sẽ nhanh chóng được gọi trước khi hết thời gian chờ ANR.

Lỗi ANR trong BroadcastReceiver

Thời gian thực thi BroadcastReceiver bị hạn chế vì broadcast receiver được dùng để thực hiện các khối lượng công việc nhỏ, riêng biệt ở chế độ nền, chẳng hạn như lưu một chế độ cài đặt hoặc đăng ký một Notification. Vì vậy, giống như các phương thức khác được gọi trong luồng giao diện người dùng, ứng dụng phải tránh các thao tác hoặc hoạt động tính toán có thể diễn ra trong thời gian dài trong một broadcast receiver. Thay vì thực hiện các tác vụ diễn ra trong thời gian dài thông qua luồng giao diện người dùng, hãy thực hiện các tác vụ đó trong nền để thực thi sau. Hãy xem bài viết Tổng quan về hoạt động trong nền để biết thêm thông tin về các giải pháp khả thi.

Một vấn đề thường gặp khác với các đối tượng BroadcastReceiver xảy ra khi các đối tượng này thực thi quá thường xuyên. Việc thường xuyên thực thi trong nền có thể làm giảm dung lượng bộ nhớ dành cho các ứng dụng khác. Để biết thêm thông tin về cách bật và tắt các đối tượng BroadcastReceiver một cách hiệu quả, hãy xem bài viết Tổng quan về thông báo truyền tin.

Tăng cường khả năng phản hồi

Thông thường, người dùng sẽ cảm nhận được một ứng dụng đang chạy chậm nếu thời gian phản hồi vượt quá ngưỡng 100 đến 200 mili giây. Dưới đây là một số lưu ý khác để khiến người dùng cảm thấy có vẻ như ứng dụng của bạn phản hồi nhanh chóng:

  • Nếu ứng dụng của bạn đang hoạt động ở chế độ nền khi phản hồi hoạt động đầu vào của người dùng, hãy cho thấy rằng tiến trình đang được thực hiện, chẳng hạn như bằng một ProgressBar trong giao diện người dùng.

  • Riêng đối với trò chơi, hãy tính toán các bước trong một luồng worker.

  • Nếu ứng dụng của bạn có giai đoạn thiết lập ban đầu mất nhiều thời gian, hãy cân nhắc làm hiện màn hình chờ hoặc kết xuất khung hiển thị chính nhanh nhất có thể. Cho người dùng biết rằng quá trình tải đang diễn ra và hiện thông tin theo cách không đồng bộ. Trong cả hai trường hợp, bằng cách nào đó, bạn nên cho người dùng biết rằng tiến trình đang diễn ra để người dùng không thấy là ứng dụng bị treo.

  • Sử dụng các công cụ hỗ trợ hiệu suất như PerfettoTrình phân tích CPU để xác định điểm tắc nghẽn ảnh hưởng đến khả năng phản hồi của ứng dụng.