Tìm luồng không phản hồi

Tài liệu này trình bày cách xác định luồng không phản hồi trong một tệp kết xuất ngăn xếp ANR. Luồng không phản hồi sẽ khác nhau tuỳ theo loại lỗi ANR, như minh hoạ trong bảng sau.

Loại lỗi ANR Luồng không phản hồi
Phân phối đầu vào Luồng chính
Phân phối đầu vào không có cửa sổ đặt tiêu điểm Luồng chính. Loại lỗi ANR này thường không phải do luồng bị chặn gây ra.
Broadcast receiver (đồng bộ) Luồng chạy onReceive(). Đây là luồng chính, trừ phi trình xử lý tuỳ chỉnh trên một luồng không phải luồng chính được chỉ định bằng Context.registerReceiver.
Broadcast receiver (không đồng bộ) Hãy kiểm tra mã nguồn để xem luồng hoặc nhóm luồng nào chịu trách nhiệm xử lý thông báo truyền tin sau khi goAsync được gọi.
Hết thời gian thực thi dịch vụ Luồng chính
Bắt đầu dịch vụ trên nền trước Luồng chính
Trình cung cấp nội dung không phản hồi Hoặc:
  • Luồng Binder nếu ANR xảy ra do truy vấn trình cung cấp nội dung bị chậm.
  • Luồng chính nếu ANR xảy ra do việc khởi động ứng dụng diễn ra trong thời gian dài.
onStartJob hoặc onStopJob không có phản hồi Luồng chính

Đôi khi, căn nguyên khiến luồng không phản hồi nằm trong một luồng hoặc quy trình khác. Luồng có thể không phản hồi do phải chờ:

  • Khoá do một luồng khác giữ.
  • Lệnh gọi liên kết chậm đến một tiến trình khác.

Các nguyên nhân phổ biến khiến luồng không phản hồi

Sau đây là các nguyên nhân phổ biến khiến luồng không phản hồi.

Lệnh gọi Binder chậm

Mặc dù hầu hết các lệnh gọi liên kết đều diễn ra nhanh, nhưng những phiên kéo theo nó có thể rất tốn thời gian. Điều này càng dễ xảy ra hơn nếu thiết bị được tải hoặc luồng phản hồi liên kết mất nhiều thời gian, chẳng hạn như do tranh chấp khoá, có nhiều lệnh gọi liên kết đến, hoặc thời gian chờ lớp trừu tượng phần cứng (HAL).

Bạn có thể giải quyết vấn đề này bằng cách di chuyển lệnh gọi liên kết đồng bộ đến các luồng trong nền bất cứ khi nào có thể. Nếu lệnh gọi phải diễn ra trên luồng chính, hãy tìm hiểu lý do tại sao lệnh gọi bị chậm. Cách tốt nhất để làm điều này là sử dụng dấu vết Perfetto.

Tìm BinderProxy.transactNative hoặc Binderproxy.transact trong ngăn xếp. Điều này có nghĩa là một lệnh gọi liên kết đang diễn ra. Từ 2 dòng này, bạn có thể thấy API liên kết được gọi. Trong ví dụ sau, lệnh gọi đến IAccessibilityManager.addClient.

main tid=123

...
android.os.BinderProxy.transactNative (Native method)
android.os.BinderProxy.transact (BinderProxy.java:568)
android.view.accessibility.IAccessibilityManager$Stub$Proxy.addClient (IAccessibilityManager.java:599)
...

Nhiều lệnh gọi liên kết liên tiếp

Việc thực hiện nhiều lệnh gọi liên kết liên tiếp trong vòng lặp chặt chẽ có thể chặn luồng trong thời gian dài.

Chặn I/O

Không bao giờ chặn I/O trên luồng chính. Đây là phản mẫu.

Tranh chấp khoá

Nếu một luồng bị chặn khi có khoá, thì điều đó có thể dẫn đến lỗi ANR.

Ví dụ sau đây cho thấy luồng chính bị chặn khi cố gắng có được khoá:

main (tid=1) Blocked

Waiting for com.example.android.apps.foo.BarCache (0x07d657b7) held by
ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD
[...]
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5412)
[...]

Luồng chặn đang tạo một yêu cầu HTTP để tải video xuống:

ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD (tid=110) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1047)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
at com.example.android.apps.foo.HttpRequest.execute(HttpRequest:136)
at com.example.android.apps.foo$Task$VideoLoadTask.downloadVideoToFile(RequestExecutor:711)
[...]

Khung tốn nhiều tài nguyên

Kết xuất quá nhiều nội dung trong một khung hình có thể khiến luồng chính không phản hồi trong suốt thời gian kết xuất khung hình, chẳng hạn như sau:

  • Kết xuất nhiều mục không cần thiết ngoài màn hình.
  • Sử dụng một thuật toán không hiệu quả, chẳng hạn như O(n^2), khi hiển thị nhiều phần tử trên giao diện người dùng.

Bị thành phần khác chặn

Nếu một thành phần khác, chẳng hạn như broadcast receiver, chặn luồng chính trong hơn 5 giây, thì phân phối đầu vào bị ANR và hiện tượng giật nghiêm trọng sẽ xảy ra.

Tránh thực hiện nhiều thao tác trên luồng chính trong các thành phần của ứng dụng. Chạy broadcast receiver trên một luồng khác bất cứ khi nào có thể.