Khi luồng giao diện người dùng của một ứng dụng Android bị chặn quá lâu, hệ thống sẽ gửi lỗi "Ứng dụng không phản hồi" (ANR). Trang này mô tả nhiều loại lỗi ANR, cách chẩn đoán các lỗi này và những đề xuất cách khắc phục lỗi. Tất cả các khoảng thời gian của thời gian chờ mặc định được liệt kê đều dành cho Dự án nguồn mở Android (AOSP) và các thiết bị Pixel; những khoảng thời gian này có thể khác nhau tuỳ theo Nhà sản xuất thiết bị gốc (OEM).
Xin lưu ý rằng khi xác định nguyên nhân gây ra lỗi ANR, bạn cần phân biệt giữa các vấn đề xảy ra với hệ thống và vấn đề xảy ra với ứng dụng.
Khi hệ thống ở trạng thái không tốt, những vấn đề sau có thể gây ra lỗi ANR:
- Các vấn đề tạm thời xảy ra ở máy chủ hệ thống khiến các lệnh gọi liên kết vốn thường nhanh sẽ bị chậm.
- Các vấn đề xảy ra với máy chủ hệ thống và mức tải cao trên thiết bị khiến các luồng ứng dụng không được lên lịch.
Nếu có thể, bạn nên sử dụng dấu vết Perfetto để phân biệt giữa các vấn đề xảy ra với hệ thống và vấn đề xảy ra với ứng dụng:
- Xem liệu luồng chính của ứng dụng có được lên lịch hay không bằng cách xem theo dõi trạng thái luồng trong Perfetto để biết luồng đó đang chạy hay có khả năng sẽ chạy.
- Xem các luồng
system_serverđể phát hiện ra các vấn đề, chẳng hạn như tình trạng tranh chấp khoá. - Đối với lệnh gọi liên kết bị chậm, hãy xem luồng trả lời (nếu có) để biết nguyên nhân khiến lệnh gọi này bị chậm.
Hết thời gian chờ truyền dữ liệu đầu vào
Lỗi ANR khi truyền dữ liệu đầu vào xảy ra khi luồng chính của ứng dụng không phản hồi kịp thời một sự kiện đầu vào, chẳng hạn như thao tác vuốt hoặc nhấn phím. Vì ứng dụng chạy ở nền trước khi hết thời gian chờ truyền dữ liệu đầu vào, nên người dùng hầu như luôn thấy được lỗi này và do đó, việc giảm thiểu lỗi là điều rất quan trọng.
Khoảng thời gian chờ mặc định: 5 giây.
Lỗi ANR khi truyền dữ liệu đầu vào thường do các vấn đề trên luồng chính gây ra. Nếu luồng chính bị chặn khi đang chờ lấy một khoá, thì luồng phần tử giữ cũng có thể có liên quan.
Để tránh các lỗi ANR khi truyền dữ liệu đầu vào, hãy làm theo các phương pháp hay nhất sau đây:
- Không thực hiện các thao tác chặn hoặc các hoạt động diễn ra trong thời gian dài trên luồng chính. Hãy cân nhắc dùng
StrictModeđể phát hiện hoạt động do nhầm lẫn trên luồng chính. - Giảm thiểu tranh chấp khoá giữa luồng chính và các luồng khác.
- Giảm thiểu 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ụ.
Nguyên nhân thường gặp
Dưới đây là một số nguyên nhân thường gặp và cách khắc phục được đề xuất cho lỗi ANR khi truyền dữ liệu đầu vào.
| Nguyên nhân | Điều sẽ xảy ra | Cách khắc phục được đề xuất |
|---|---|---|
| Lệnh gọi liên kết bị chậm | Luồng chính thực hiện một lệnh gọi liên kết đồng bộ diễn ra trong thời gian dài. | Di chuyển lệnh gọi đó ra khỏi luồng chính hoặc cố gắng tối ưu hoá lệnh gọi nếu bạn sở hữu API. |
| Nhiều lệnh gọi liên kết liên tiếp | Luồng chính thực hiện nhiều lệnh gọi liên kết đồng bộ liên tiếp. | Không thực hiện các lệnh gọi liên kết trong một chu kỳ tối giản. |
| Chặn I/O | Luồng chính giúp chặn lệnh gọi I/O, chẳng hạn như quyền truy cập cơ sở dữ liệu hoặc mạng. | Di chuyển tất cả lệnh chặn I/O ra khỏi luồng chính. |
| Tranh chấp khoá | Luồng chính bị chặn đang chờ lấy một khoá. | Giảm tranh chấp khoá giữa luồng chính và luồng khác. Tối ưu hoá mã có tốc độ chậm trong luồng khác. |
| Khung tốn nhiều tài nguyên | Kết xuất quá nhiều trong một khung hình, gây ra tình trạng giật nghiêm trọng. | Giảm bớt thao tác khi kết xuất khung. Không sử dụng thuật toán n2. Sử dụng các thành phần hiệu quả cho những tính năng như cuộn hoặc phân trang – ví dụ: Thư viện Jetpack Paging. |
| Bị thành phần khác chặn | Một thành phần khác (chẳng hạn như broadcast receiver) đang chạy chặn luồng chính. | Di chuyển thao tác không liên quan đến giao diện người dùng ra khỏi luồng chính càng nhiều càng tốt. Chạy broadcast receiver trên một luồng khác. |
| GPU bị treo | GPU bị treo là một sự cố xảy ra với hệ thống hoặc phần cứng khiến quá trình kết xuất bị chặn, dẫn đến lỗi ANR khi truyền dữ liệu đầu vào. | Rất tiếc, về phía ứng dụng, thường không có bất kỳ cách khắc phục nào. Nếu có thể, hãy liên hệ với nhóm phụ trách phần cứng để khắc phục sự cố. |
Cách gỡ lỗi
Hãy bắt đầu gỡ lỗi bằng cách xem chữ ký cụm lỗi ANR trong Google Play Console hoặc Firebase Crashlytics. Cụm này thường chứa các khung hình ở trên cùng bị nghi là nguyên nhân gây ra lỗi ANR.
Biểu đồ quy trình sau đây cho biết cách xác định nguyên nhân gây ra lỗi ANR do hết thời gian chờ truyền dữ liệu đầu vào.
Play vitals có thể phát hiện và giúp giải quyết một số nguyên nhân thường gặp gây ra lỗi ANR. Ví dụ: nếu phát hiện thấy một lỗi ANR xảy ra do tranh chấp khoá, thì vitals có thể tóm tắt sự cố và đề xuất cách khắc phục trong phần Thông tin chi tiết về lỗi ANR.
Không có cửa sổ nào được lấy tiêu điểm
Mặc dù các sự kiện chẳng hạn như chạm được gửi trực tiếp đến cửa sổ liên quan dựa trên thử nghiệm nhấn, nhưng các sự kiện chẳng hạn như khoá lại cần có một mục tiêu. Mục tiêu này được gọi là cửa sổ được lấy tiêu điểm. Mỗi màn hình chỉ có một cửa sổ được lấy tiêu điểm và thường là cửa sổ mà người dùng đang tương tác. Nếu không tìm thấy cửa sổ được lấy tiêu điểm, thao tác đầu vào sẽ gây ra lỗi ANR do không có cửa sổ được lấy tiêu điểm. Lỗi ANR do không có cửa sổ được lấy tiêu điểm là một loại lỗi ANR khi truyền dữ liệu đầu vào.
Khoảng thời gian chờ mặc định: 5 giây.
Nguyên nhân thường gặp
Lỗi ANR do không có cửa sổ được lấy tiêu điểm thường do một trong những vấn đề sau gây ra:
- Ứng dụng đang thực hiện quá nhiều hoạt động và có tốc độ quá chậm nên không vẽ được khung đầu tiên.
- Không thể lấy cửa sổ chính làm tiêu điểm. Nếu một cửa sổ được gắn cờ bằng
FLAG_NOT_FOCUSABLE, thì người dùng sẽ không thể gửi các sự kiện liên quan tới nút hoặc phím đến cửa sổ đó.
Kotlin
override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) window.addFlags(WindowManager.LayoutParams.FLAG_FLAG_NOT_FOCUSABLE) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); }
Hết thời gian chờ broadcast receiver
Lỗi ANR liên quan đến broadcast receiver xảy ra khi một broadcast receiver không xử lý thông báo truyền tin kịp thời. Đối với các receiver đồng bộ hoặc các receiver không gọi goAsync(), hết thời gian chờ có nghĩa là onReceive() không hoàn tất đúng thời gian. Đối với các receiver không đồng bộ hoặc các receiver gọi goAsync(), hết thời gian chờ có nghĩa là PendingResult.finish() không được gọi kịp thời.
Lỗi ANR liên quan đến broadcast receiver thường xảy ra trong các luồng sau:
- Luồng chính, nếu vấn đề là ứng dụng khởi động chậm.
- Luồng đang chạy broadcast receiver nếu vấn đề là mã
onReceive()có tốc độ chậm. - Các luồng worker truyền tin, nếu vấn đề là mã truyền tin
goAsync()có tốc độ chậm.
Để tránh các lỗi ANR liên quan đến broadcast receiver, hãy làm theo các phương pháp hay nhất sau:
- Đảm bảo ứng dụng khởi động nhanh vì thời gian này được tính vào thời gian chờ ANR nếu ứng dụng bắt đầu xử lý thông báo truyền tin.
- Nếu dùng
goAsync(), hãy nhớ gọiPendingResult.finish()thật nhanh. Mã này có thời gian chờ ANR tương tự như các broadcast receiver đồng bộ. - Nếu dùng
goAsync(), hãy đảm bảo (các) luồng worker không được chia sẻ với các thao tác chặn hoặc hoạt động khác diễn ra trong thời gian dài. - Cân nhắc sử dụng
registerReceiver()để chạy broadcast receiver trong một luồng không phải luồng chính, nhằm tránh chặn mã giao diện người dùng chạy trong luồng chính.
Khoảng thời gian chờ
Khoảng thời gian chờ broadcast receiver phụ thuộc vào việc cờ ý định trên nền trước có được đặt hay không và phiên bản nền tảng.
| Loại ý định | Android 13 trở xuống | Android 14 trở lên |
|---|---|---|
Ý định ưu tiên khi ở nền trước ( |
10 giây |
10 – 20 giây, tuỳ thuộc vào việc xử lý có bị thiếu CPU hay không |
Ý định ưu tiên khi ở nền sau ( |
60 giây |
60 – 120 giây, tuỳ thuộc vào việc xử lý có bị thiếu CPU hay không |
Để biết cờ FLAG_RECEIVER_FOREGROUND có được đặt hay không, hãy tìm "flg=" trong tiêu đề ANR và xem có 0x10000000 hay không. Nếu bit này được đặt thì ý định đã được đặt FLAG_RECEIVER_FOREGROUND, do đó, thời gian chờ sẽ ngắn hơn.
Dưới đây là ví dụ về tiêu đề ANR có thời gian chờ truyền tin ngắn (10 – 20 giây):
Broadcast of Intent { act=android.inent.action.SCREEN_ON flg=0x50200010 }
Dưới đây là ví dụ về tiêu đề ANR có thời gian chờ truyền tin dài (60 – 120 giây):
Broadcast of Intent { act=android.intent.action.TIME_SET flg=0x25200010 }
Cách đo thời gian truyền tin
Quá trình đo lường thời lượng truyền tin bắt đầu khi thông báo truyền tin được gửi từ system_server đến ứng dụng và kết thúc khi ứng dụng xử lý xong thông báo truyền tin đó. Nếu quy trình xử lý ứng dụng chưa chạy, thì quy trình này cũng cần khởi động nguội trong khoảng thời gian chờ ANR. Do đó, việc ứng dụng khởi động chậm có thể dẫn đến lỗi ANR liên quan đến broadcast receiver.
Hình sau đây minh hoạ tiến trình ANR liên quan đến broadcast receiver tương ứng với một số quy trình của ứng dụng.
Quá trình đo lường thời gian chờ ANR kết thúc khi receiver xử lý xong thông báo truyền tin: thời điểm chính xác diễn ra việc này phụ thuộc vào việc receiver đó là receiver đồng bộ hay không đồng bộ.
- Đối với các receiver đồng bộ, quá trình đo lường sẽ dừng khi trả về
onReceive(). - Đối với các receiver không đồng bộ, quá trình đo lường sẽ dừng khi
PendingResult.finish()được gọi.
Nguyên nhân thường gặp
Dưới đây là một số nguyên nhân thường gặp và cách khắc phục được đề xuất cho lỗi ANR liên quan đến broadcast receiver.
| Nguyên nhân | Áp dụng cho | Điều đã xảy ra | Cách khắc phục được đề xuất |
|---|---|---|---|
| Ứng dụng khởi động chậm | Tất cả các receiver | Ứng dụng mất quá nhiều thời gian để khởi động nguội. | Tối ưu hoá quá trình khởi động cho ứng dụng bị khởi động chậm. |
onReceive() chưa lên lịch | Tất cả các receiver | Luồng broadcast receiver bận thực hiện công việc khác và không thể bắt đầu phương thức onReceive(). | Không thực hiện các tác vụ chạy trong thời gian dài trên luồng của receiver (hoặc di chuyển receiver sang luồng chuyên dụng). |
onReceive() có tốc độ chậm | Tất cả các receiver, nhưng chủ yếu là các receiver đồng bộ | Phương thức onReceive() đã bắt đầu nhưng bị chặn hoặc có tốc độ chậm nên không hoàn thành đúng thời gian. | Tối ưu hoá mã receiver có tốc độ chậm. |
| Các tác vụ của receiver không đồng bộ chưa được lên lịch | Các receiver goAsync() | Phương thức onReceive() đã cố thực thi thao tác trên một nhóm luồng worker bị chặn, vì vậy, thao tác sẽ không bao giờ bắt đầu. |
Tối ưu hoá các lệnh gọi bị chậm hoặc chặn hoặc sử dụng luồng cho worker truyền tin khác với luồng cho các tác vụ khác chạy trong thời gian dài. |
| Worker bị chậm hoặc bị chặn | Các receiver goAsync() |
Có một thao tác chặn hoặc thao tác bị chậm ở đâu đó trong nhóm luồng worker khi xử lý thông báo truyền tin. Vì vậy, PendingResult.finish không được gọi kịp thời. | Tối ưu hoá mã receiver async bị chậm. |
Quên gọi PendingResult.finish |
Các receiver goAsync() |
Đường dẫn mã thiếu lệnh gọi đến finish(). |
Đảm bảo finish() luôn được gọi. |
Cách gỡ lỗi
Dựa vào báo cáo ANR và chữ ký cụm, bạn có thể xác định luồng mà receiver chạy trên đó cũng như mã cụ thể bị thiếu hoặc chạy chậm.
Biểu đồ quy trình sau cho biết cách xác định nguyên nhân gây ra lỗi ANR liên quan đến broadcast receiver.
Tìm mã receiver
Google Play Console hiện lớp receiver và ý định truyền tin trong chữ ký ANR. Hãy tìm các mã sau:
cmp=<receiver class>act=<broadcast_intent>
Dưới đây là ví dụ về một chữ ký ANR liên quan đến broadcast receiver:
com.example.app.MyClass.myMethod
Broadcast of Intent { act=android.accounts.LOGIN_ACCOUNTS_CHANGED
cmp=com.example.app/com.example.app.MyAccountReceiver }
Tìm luồng chạy phương thức onReceive()
Nếu bạn đang sử dụng Context.registerReceiver để chỉ định một trình xử lý tuỳ chỉnh, thì đó chính là luồng đang chạy trình xử lý này. Còn không, thì đó là luồng chính.
Ví dụ: các tác vụ của receiver không đồng bộ chưa được lên lịch
Phần này trình bày một ví dụ về cách gỡ lỗi ANR liên quan đến broadcast receiver.
Giả sử chữ ký ANR trông như sau:
com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }
Dựa vào chữ ký này, có vẻ như ý định truyền tin là android.accounts.LOG_ACCOUNTS_CHANGED và lớp receiver là com.example.app.MyReceiver.
Từ mã receiver, bạn có thể xác định rằng nhóm luồng "Luồng BG [0,1,2,3]" thực hiện thao tác chính để xử lý thông báo truyền tin này. Khi xem tệp kết xuất ngăn xếp, bạn có thể thấy rằng cả 4 luồng ở nền sau (BG) đều có cùng một mẫu: các luồng này chạy một lệnh gọi chặn, getDataSync. Vì tất cả các luồng BG đều bận nên thông báo truyền tin không được xử lý kịp thời, dẫn đến một lỗi ANR.
BG Thread #0 (tid=26) Waiting
at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture:563)
at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture:68)
at com.example.app.getDataSync(<MyClass>:152)
...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.libraries.concurrent.AndroidExecutorsModule.lambda$withStrictMode$5(AndroidExecutorsModule:451)
at com.google.android.libraries.concurrent.AndroidExecutorsModule$$ExternalSyntheticLambda8.run(AndroidExecutorsModule:1)
at java.lang.Thread.run(Thread.java:1012)
at com.google.android.libraries.concurrent.ManagedPriorityThread.run(ManagedPriorityThread:34)
There are several approaches to fix the issue:
- Find out why
getDataSyncis slow and optimize. - Don't run
getDataSyncon all four BG threads. - More generally, ensure that the BG thread pool isn't saturated with long-running operations.
- Use a dedicated thread pool for
goAsyncworker tasks. - Use an unbounded thread pool instead of the bounded BG thread pool
Example: slow app startup
A slow app startup can cause several types of ANRs, especially broadcast
receiver and execute service ANRs. The cause of an
ANR is likely slow app startup if you see ActivityThread.handleBindApplication
in the main thread stacks.
Execute service timeout
An execute service ANR happens when the app's main thread doesn't start a
service in time. Specifically, a service doesn't finish executing
onCreate() and onStartCommand() or onBind() within the
timeout period.
Default timeout period: 20 seconds for foreground service; 200 seconds for
background service. The ANR timeout period includes the app cold start, if
necessary, and calls to onCreate(), onBind(), or onStartCommand().
To avoid execute service ANRs, follow these general best practices:
- Make sure that app startup is fast, since it's counted in the ANR timeout if the app is started to run the service component.
- Make sure that the service's
onCreate(),onStartCommand(), andonBind()methods are fast. - Avoid running any slow or blocking operations on the main thread from other components; these operations can prevent a service from starting quickly.
Common causes
The following table lists common causes of execute service ANRs and suggested fixes.
| Cause | What | Suggested fix |
|---|---|---|
| Slow app startup | The app takes too long to perform a cold start. | Optimize slow app start. |
Slow onCreate(), onStartCommand(), or
onBind() |
The service component's onCreate(),
onStartCommand(), or onBind() method takes too long to
execute on the main thread. |
Optimize slow code. Move slow operations off the critical path where possible. |
Not scheduled (main thread blocked before onStart()) |
The app's main thread is blocked by another component before the service can be started. | Move other component's work off the main thread. Optimize other component's blocking code. |
How to debug
From the cluster signature and ANR report in Google Play Console or Firebase Crashlytics, you can often determine the cause of the ANR based on what the main thread is doing.
The following flow chart describes how to debug an execute service ANR.
If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:
Find the service component class in the ANR signature. In Google Play Console, the service component class is shown in the ANR signature. In the following example ANR details, it's
com.example.app/MyService.com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly Executing service com.example.app/com.example.app.MyServiceDetermine whether the slow or block operation is part of app startup, the service component, or elsewhere by checking for the following important function call(s) in the main threads.
Function call(s) in main thread stacks What it means android.app.ActivityThread.handleBindApplicationApp was starting up, so the ANR was caused by slow app start. <ServiceClass>.onCreate()
[...]
android.app.ActivityThread.handleCreateService
Service was being created, so the ANR was likely caused by slow onCreate()code.<ServiceClass>.onBind()
[...]
android.app.ActivityThread.handleBindService
Service was being bound, so the ANR was likely caused by slow onBind()code.<ServiceClass>.onStartCommand()
[...]
android.app.ActivityThread.handleServiceArgs
Service was being started, so the ANR was likely caused by slow onStartCommand()code.For example, if the
onStartCommand()method in theMyServiceclass is slow, the main threads will look like this:at com.example.app.MyService.onStartCommand(FooService.java:25) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4820) at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(unavailable:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2289) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:205) at android.os.Looper.loop(Looper.java:294) at android.app.ActivityThread.main(ActivityThread.java:8176) at java.lang.reflect.Method.invoke(Native method:0)Nếu bạn không thấy bất kỳ lệnh gọi hàm quan trọng nào, thì dưới đây là một số khả năng khác:
- Dịch vụ đang chạy hoặc ngừng hoạt động, tức là các ngăn xếp được lấy quá muộn. Trong trường hợp này, bạn có thể bỏ qua lỗi ANR vì là dạng lỗi dương tính giả.
- Một thành phần ứng dụng khác đang chạy, chẳng hạn như broadcast receiver. Trong trường hợp như vậy, luồng chính có thể bị chặn trong thành phần này, khiến dịch vụ không bắt đầu được.
Nếu bạn thấy một lệnh gọi hàm chính và có thể xác định vị trí thường xảy ra lỗi ANR, hãy kiểm tra các ngăn xếp luồng chính còn lại để tìm thao tác có tốc độ chậm và tiến hành tối ưu hoá hoặc chuyển thao tác đó ra khỏi quá trình quan trọng.
- Đảm bảo ứng dụng khởi động nhanh vì thời gian này được tính vào thời gian chờ ANR nếu ứng dụng bắt đầu chạy trình cung cấp nội dung.
- Đảm bảo rằng các truy vấn của trình cung cấp nội dung có tốc độ nhanh.
- Không thực hiện nhiều lệnh gọi liên kết chặn đồng thời có khả năng chặn tất cả các luồng liên kết của ứng dụng.
- Giao diện người dùng bị treo mà không có thông báo: Giao diện người dùng của ứng dụng ngừng phản hồi hoạt động đầu vào của người dùng.
- Sự cố: Ứng dụng có thể gặp sự cố với
illegalStateException. - Lỗi ANR: Hệ thống có thể kích hoạt thời gian chờ ANR.
- Lập lịch cho các lượt truyền tải:
ViewRootImpllập lịch cho một lượt truyền tải (bố cục và lượt vẽ) bằng cách đăng một rào cản đồng bộ hoá vàoMessageQueuecủa luồng giao diện người dùng. Rào cản này tạm dừng quá trình xử lý thông báo thông thường để bố cục giao diện người dùng có thể được ưu tiên. - Tình huống tương tranh: Khi nhiều luồng cố gắng làm mất hiệu lực một khung hiển thị đồng thời, chúng sẽ tranh nhau lên lịch cho quá trình truyền tải này. Cả hai luồng đều có thể chèn thành công một rào cản đồng bộ hoá, nhưng khung chỉ lưu trữ mã thông báo cho một trong hai luồng.
- Giao diện người dùng bị treo: Khi quá trình di chuyển thực thi, quá trình này chỉ xoá một rào cản đã lưu. Các rào cản thứ cấp, "bị rò rỉ" vẫn nằm trong hàng đợi vô thời hạn, chặn vĩnh viễn luồng giao diện người dùng xử lý mọi thông báo đồng bộ.
- Chỉ tương tác với các đối tượng
View(bao gồm cả việc đọc các thuộc tính nhưwidthhoặcheighthoặc thiết lập các thuộc tính nhưTextView's text) trên luồng mà bạn đã tạo hệ phân cấp view. Đây hầu như luôn là luồng chính hoặc luồng giao diện người dùng. - Nếu không đảm bảo rằng bạn đang chạy trên luồng giao diện người dùng khi truy cập vào các khung hiển thị, hãy giả định rằng việc này không an toàn. Ví dụ: nếu đang dùng
Coroutinescho hoạt động ở chế độ nền, bạn phải chuyển sang trình điều phối chính trước khi thao tác với các đối tượngView. ContextCompat#getMainExecutor(android.content.Context)View.post(Runnable)Activity.runOnUiThread(Runnable)- Cài đặt phiên bản có thể gỡ lỗi của ứng dụng trên thiết bị chạy Android 17 trở lên.
- Mở ứng dụng Cài đặt trên thiết bị rồi chuyển đến phần Hệ thống > Nâng cao > Tuỳ chọn cho nhà phát triển > Các thay đổi về khả năng tương thích của ứng dụng.
- Chọn ứng dụng của bạn trong danh sách.
- Trong danh sách thay đổi, hãy tìm và bật nút chuyển
ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS. - Đo từ xa và theo dõi: Trình nghe này cho phép ứng dụng của bạn nhận các lệnh gọi lại khi một API Xem được gọi từ một luồng không chính xác, giúp bạn dễ dàng ghi nhật ký đo từ xa và theo dõi các lỗi.
- Thực thi nội tuyến: Trình nghe được gọi nội tuyến, cho phép bạn ghi lại và kiểm tra dấu vết ngăn xếp chính xác tại thời điểm xảy ra lỗi vi phạm.
- Sự cố trên toàn hệ thống. Quá trình này không được lên lịch do hệ thống quá tải hoặc xảy ra sự cố ở máy chủ hệ thống.
- Tệp kết xuất ngăn xếp muộn. Luồng đã khôi phục trong khoảng thời gian ngắn giữa thời điểm kích hoạt lỗi ANR và thời điểm ngăn xếp được kết xuất. Độ trễ trong Pixel trên Android 13 là khoảng 100 mili giây, nhưng có thể vượt quá 1 giây. Độ trễ trong Pixel trên Android 14 thường dưới 10 mili giây.
- Phân bổ sai luồng. Luồng dùng để tạo chữ ký ANR không phải là luồng không phản hồi thực sự gây ra lỗi ANR.
- Xem lỗi vi phạm về luồng. Nếu ứng dụng của bạn sửa đổi một khung hiển thị trên một luồng trong nền, thì ứng dụng đó có thể kích hoạt một tình huống tương tranh trong nội bộ khung hiển thị, khiến các tác vụ trên Luồng giao diện người dùng không chạy được
- Hệ thống quá tải: Đánh giá áp lực tổng thể về tài nguyên của thiết bị, chẳng hạn như CPU, bộ nhớ hoặc tình trạng thiếu hụt I/O trên toàn hệ thống là nguyên nhân chính gây ra tình trạng không phản hồi.
- Gán sai luồng: Kiểm tra luồng worker và luồng liên kết để tìm các tình trạng bế tắc, tranh chấp khoá ảnh hưởng đến luồng chính hoặc các luồng nền bị treo xử lý các thành phần không đồng bộ (ví dụ:
goAsync()). - Xem các lỗi vi phạm về luồng: Quét các luồng trong nền để tìm những nội dung sửa đổi bất hợp pháp về hệ phân cấp khung hiển thị giao diện người dùng. Những nội dung này có thể khiến một rào cản đồng bộ hoá bị bỏ rơi trong MessageQueue và chặn vĩnh viễn tất cả các thông báo đồng bộ.
- Việc lấy ngăn xếp mất quá nhiều thời gian và hết thời gian chờ.
- Quá trình bị gián đoạn hoặc bị dừng trước khi các ngăn xếp được lấy.
Để biết thêm thông tin về các dịch vụ, hãy xem các trang sau:
Trình cung cấp nội dung không phản hồi
Lỗi ANR của trình cung cấp nội dung xảy ra khi một trình cung cấp nội dung từ xa mất nhiều thời gian hơn so với khoảng thời gian chờ để phản hồi một truy vấn và bị dừng.
Khoảng thời gian chờ mặc định: do trình cung cấp nội dung chỉ định bằng cách sử dụng ContentProviderClient.setDetectNotResponding. Khoảng thời gian chờ ANR bao gồm tổng thời gian cần để chạy một truy vấn của trình cung cấp nội dung từ xa, kể cả thời gian khởi động nguội ứng dụng từ xa nếu ứng dụng chưa chạy.
Để tránh lỗi ANR của trình cung cấp nội dung, hãy làm theo các phương pháp hay nhất sau:
Nguyên nhân thường gặp
Bảng sau đây liệt kê các nguyên nhân thường gặp gây ra lỗi ANR của trình cung cấp nội dung và cách khắc phục được đề xuất.
| Nguyên nhân | Điều sẽ xảy ra | Tín hiệu | Cách khắc phục được đề xuất |
|---|---|---|---|
| Truy vấn của trình cung cấp nội dung có tốc độ chậm | Trình cung cấp nội dung mất quá nhiều thời gian để thực thi hoặc bị chặn. | Khung android.content.ContentProvider$Transport.query nằm trong luồng liên kết. |
Tối ưu hoá truy vấn của trình cung cấp nội dung. Tìm hiểu điều đang chặn luồng liên kết. |
| Ứng dụng khởi động chậm | Ứng dụng của trình cung cấp nội dung mất quá nhiều thời gian để khởi động. | Khung ActivityThread.handleBindApplication nằm trong luồng chính. |
Tối ưu hoá quá trình khởi động ứng dụng. |
| Tình trạng cạn kiệt luồng liên kết – tất cả các luồng liên kết đều đang bận | Tất cả các luồng liên kết đều đang bận phân phát các yêu cầu đồng bộ khác nên không thể chạy lệnh gọi liên kết của trình cung cấp nội dung. | Ứng dụng không khởi động, tất cả các luồng liên kết đều bận và trình cung cấp nội dung hiện không chạy. | Giảm tải cho các luồng liên kết. Điều này nghĩa là giảm bớt các lệnh gọi liên kết đi đồng bộ hoặc thực hiện ít thao tác hơn khi xử lý các cuộc gọi đến. |
Cách gỡ lỗi
Để gỡ lỗi lỗi ANR của trình cung cấp nội dung bằng chữ ký cụm và báo cáo ANR trong Google Play Console hoặc Firebase Crashlytics, hãy xem hoạt động của luồng chính và (các) luồng liên kết.
Biểu đồ sau đây mô tả cách gỡ lỗi ANR của trình cung cấp nội dung:
Đoạn mã sau đây cho thấy giao diện của luồng liên kết khi bị chặn do truy vấn của trình cung cấp nội dung có tốc độ chậm. Trong trường hợp này, truy vấn của trình cung cấp nội dung sẽ chờ khoá khi mở một cơ sở dữ liệu.
binder:11300_2 (tid=13) Blocked
Waiting for osm (0x01ab5df9) held by at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers:182)
at com.example.app.MyClass.blockingGetOpenDatabase(FooClass:171)
[...]
at com.example.app.MyContentProvider.query(MyContentProvider.java:915)
at android.content.ContentProvider$Transport.query(ContentProvider.java:292)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:107)
at android.os.Binder.execTransactInternal(Binder.java:1339)
at android.os.Binder.execTransact(Binder.java:1275)
Đoạn mã sau đây cho thấy giao diện của luồng chính khi bị chặn do ứng dụng khởi động chậm. Trong trường hợp này, quá trình khởi động ứng dụng bị chậm do tranh chấp khoá trong quá trình khởi chạy Dagger.
main (tid=1) Blocked
[...]
at dagger.internal.DoubleCheck.get(DoubleCheck:51)
- locked 0x0e33cd2c (a qsn)at dagger.internal.SetFactory.get(SetFactory:126)
at com.myapp.Bar_Factory.get(Bar_Factory:38)
[...]
at com.example.app.MyApplication.onCreate(DocsApplication:203)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6991)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(unavailable:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2235)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8170)
at java.lang.reflect.Method.invoke(Native method:0)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Chậm phản hồi lệnh
ANR phản hồi chậm với lệnh khi ứng dụng mất quá nhiều thời gian phản hồi JobService.onStartJob() hoặc JobService.onStopJob() hoặc mất quá nhiều thời gian để đưa ra một thông báo bằng JobService.setNotification(). Điều này cho thấy luồng chính của ứng dụng bị chặn khi thực hiện một thao tác nào đó khác.
Nếu đó là vấn đề xảy ra với JobService.onStartJob() hoặc JobService.onStopJob(), hãy kiểm tra những gì đang diễn ra trên luồng chính. Nếu đây là vấn đề xảy ra với JobService.setNotification(), hãy nhớ gọi lệnh này nhanh nhất có thể.
Không thực hiện nhiều thao tác trước khi đưa ra thông báo.
Xem lỗi vi phạm về việc tạo luồng
Nếu ứng dụng của bạn sửa đổi một khung hiển thị trên một luồng nền, thì ứng dụng đó có thể kích hoạt một điều kiện xung đột trong trạng thái nội bộ của hệ phân cấp khung hiển thị. Lỗi vi phạm này có thể ngăn luồng chính (giao diện người dùng) thực thi mọi thông báo đồng bộ, dẫn đến các vấn đề nghiêm trọng về độ ổn định:
Lỗi vi phạm về luồng này là một trong những nguyên nhân gốc gây ra dấu vết ngăn xếp ANR, trong đó luồng chính có vẻ như không hoạt động (ví dụ: được ghi lại trong "nativePollOnce" hoặc "main thread idle").
Rò rỉ rào cản đồng bộ hoá
Bạn có thể huỷ hiệu lực của các khung hiển thị một cách gián tiếp bằng cách thay đổi trạng thái của chúng (ví dụ: gọi TextView.setText, View.setVisibility) hoặc trực tiếp bằng cách gọi View.invalidate hoặc View.requestLayout. Khi một khung hiển thị bị vô hiệu hoá, ViewRootImpl sẽ lên lịch cho một lượt truyền tải để thực hiện đo lường, bố cục và vẽ nhằm cập nhật trạng thái giao diện người dùng.
Do đó, giao diện người dùng bị treo. Vì MessageQueue không thể xử lý bất kỳ thông báo đồng bộ nào vượt quá các rào cản bị rò rỉ, nên luồng chính sẽ chuyển sang trạng thái rảnh. Vấn đề này thường xuất hiện dưới dạng lỗi ANR có nativePollOnce trong dấu vết ngăn xếp.
Để tránh các lỗi vi phạm về luồng hiển thị, hãy làm theo các phương pháp hay nhất sau:
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { val data = myRepository.getData() withContext(Dispatchers.Main) { // Switch context to Main myTextView.text = data.title } }
Để giúp bạn áp dụng các phương pháp hay nhất, Android cung cấp một số cách để truy cập vào luồng giao diện người dùng từ các luồng khác:
Các trình tải hình ảnh phổ biến, khung tiện ích phản ứng, thư viện bus sự kiện và các giải pháp phân luồng khác thường cung cấp những cách để quan sát một số sự kiện nhất định trên luồng chính. Điều này hữu ích cho các trình quan sát và trình xử lý sự kiện cần lấy và đặt trạng thái của các khung hiển thị.
Cách gỡ lỗi
Android 17 giới thiệu các công cụ mới giúp bạn xác định, theo dõi và giải quyết các lỗi vi phạm về luồng xem trong quá trình phát triển.
1. Công cụ khung tương thích
Các công cụ khung tương thích cho phép nhà phát triển ứng dụng bật và tắt các thay đổi về hành vi một cách riêng lẻ bằng cách sử dụng các tuỳ chọn cho nhà phát triển hoặc ADB. Bạn có thể bật/tắt các thay đổi về hành vi vi phạm luồng xem bằng cách làm theo các bước sau:
Bạn cũng có thể bật hoặc tắt cờ bằng ADB:
$ adb shell am compat enable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
$ adb shell am compat disable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
Việc bật cờ này buộc ứng dụng đưa ra một ngoại lệ và gặp sự cố bất cứ khi nào một View API được gọi từ sai luồng, giúp bạn phát hiện và khắc phục các vấn đề về lỗi vi phạm luồng của khung hiển thị.
2. API CalledFromWrongThreadListener
Bạn cũng có thể triển khai API View#registerCalledFromWrongThreadListener chung để phát hiện quyền truy cập không đúng cách vào luồng theo phương thức lập trình.
android {
//...
compileSdk = 37
}
val listener = object : View.CalledFromWrongThreadListener { override fun onCalledFromWrongThread() { // Handle the issue, e.g. crash if this is a dev build, or log an event // e.g. Log.d(TAG, "CalledFromWrongThread: ${Exception().stackTraceToString()}") // Unregister the listener to avoid redundant notifications for the same issue View.unregisterCalledFromWrongThreadListener(this) } } View.registerCalledFromWrongThreadListener(listener)
nativePollOnce
Nếu bạn thấy khung "nativePollOnce" hoặc "message queue idle" trong ngăn xếp ANR, thì điều này thường cho biết rằng luồng bị nghi ngờ không phản hồi thực sự ở trạng thái rảnh và đang chờ các thông báo của trình lặp. Trong Google Play Console, thông tin chi tiết về lỗi ANR sẽ có dạng như sau:
Native method - android.os.MessageQueue.nativePollOnce
Executing service com.example.app/com.example.app.MyService
Ví dụ: nếu luồng chính đang ở trạng thái rảnh, các ngăn xếp sẽ có dạng như sau:
"main" tid=1 NativeMain threadIdle
#00 pc 0x00000000000d8b38 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
#01 pc 0x0000000000019d88 /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
#02 pc 0x0000000000019c68 /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
#03 pc 0x000000000011409c /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce (Native method)
at android.os.MessageQueue.next (MessageQueue.java:339) at android.os.Looper.loop (Looper.java:208)
at android.app.ActivityThread.main (ActivityThread.java:8192)
at java.lang.reflect.Method.invoke (Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:626)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1015)
Dưới đây là một số lý do khiến luồng bị nghi ngờ không phản hồi có thể ở trạng thái rảnh:
Vì các điều kiện kích hoạt cơ bản của lỗi ANR "nativePollOnce" thay đổi theo loại lỗi ANR, nên việc chẩn đoán danh mục lỗi ANR cụ thể có thể giúp xác định các bước có thể thực hiện để giải quyết vấn đề trong ứng dụng của bạn.
| Danh mục | Nguyên nhân | Cách khắc phục được đề xuất |
|---|---|---|
| Hết thời gian chờ truyền dữ liệu đầu vào | Sự cố trên toàn hệ thống Tệp kết xuất ngăn xếp muộn |
Bạn không cần làm gì cả. |
| Không có cửa sổ nào được lấy tiêu điểm | Sự cố trên toàn hệ thống Kết xuất ngăn xếp muộn Xem lỗi về luồng |
Kiểm tra cơ sở mã để tìm các lỗi vi phạm về luồng xem. |
| Hết thời gian chờ bộ nhận tín hiệu truyền tin | Kết xuất ngăn xếp muộn Vấn đề trên toàn hệ thống Gán sai luồng Xem Lỗi về luồng |
Kiểm tra các luồng liên quan trong kết xuất ngăn xếp. Kiểm tra cơ sở mã để tìm các lỗi vi phạm về luồng xem. |
| Hết thời gian chờ thực thi dịch vụ | Kết xuất ngăn xếp muộn Vấn đề trên toàn hệ thống Xem Lỗi vi phạm luồng |
Kiểm tra cơ sở mã để tìm các lỗi vi phạm về luồng xem. |
| Trình cung cấp nội dung không phản hồi | Tệp kết xuất ngăn xếp muộn Sự cố trên toàn hệ thống Gán sai luồng |
Kiểm tra các luồng liên quan trong kết xuất ngăn xếp. |
Sau đây là các bước được đề xuất để phân tích lỗi ANR nativePollOnce.
Không có khung ngăn xếp
Một số báo cáo ANR không chứa các ngăn xếp có lỗi ANR, nghĩa là việc kết xuất ngăn xếp không thành công khi tạo báo cáo ANR. Dưới đây là một số lý do có thể dẫn đến thiếu khung ngăn xếp:
[...]
--- CriticalEventLog ---
capacity: 20
timestamp_ms: 1666030897753
window_ms: 300000
libdebuggerd_client: failed to read status response from tombstoned: timeout reached?
----- Waiting Channels: pid 7068 at 2022-10-18 02:21:37.<US_SOCIAL_SECURITY_NUMBER>+0800 -----
[...]
Bạn sẽ không thể dùng báo cáo lỗi ANR hoặc chữ ký cụm để xử lý lỗi ANR do không có khung ngăn xếp. Để gỡ lỗi, hãy xem xét các cụm khác của ứng dụng, vì nếu một vấn đề đủ lớn, ứng dụng thường sẽ có cụm riêng chứa khung ngăn xếp. Hoặc bạn có thể xem dấu vết Perfetto.
Vấn đề đã biết
Việc giữ bộ tính giờ trong quy trình của ứng dụng nhằm hoàn tất quá trình xử lý thông báo truyền tin trước khi một điều kiện kích hoạt lỗi ANR có thể không hoạt động chính xác do hệ thống theo dõi các lỗi ANR theo cách không đồng bộ.