Lỗi ANR thường gặp đối với trò chơi Unity

Lỗi ANR của Unity xảy ra vì nhiều lý do. Lỗi ANR phổ biến nhất là do việc sử dụng sai các thành phần Android và Unity cũng như việc truyền thông tin sai giữa các thành phần này.

WebView

WebView là một lớp Android hiển thị các trang web. SDK của bên thứ ba (chẳng hạn như quảng cáo) sử dụng WebView để hiển thị nội dung web động trong các hoạt động khác ngoài UnityPlayerActivity. Lỗi ANR xảy ra khi SDK của bên thứ ba sử dụng sai WebView.

Dấu vết ngăn xếp

Dấu vết ngăn xếp là biện pháp đầu tiên để bạn hiểu nguyên nhân gây ra lỗi ANR.

/data/app/~~p-0ksfCD6bF6Sdq6kpVePg==/com.google.android.webview-5YQZOqKbbqp-uoLY6WYnTw==/base.apk!libmonochrome.so
  at J.N.Mhc_M_H$ (Native method)
  at org.chromium.components.viz.service.frame_sinks.ExternalBeginFrameSourceAndroid.doFrame (chromium-TrichromeWebViewGoogle.aab-stable-579013831:60)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1054)
  at android.view.Choreographer.doCallbacks (Choreographer.java:878)
  at android.view.Choreographer.doFrame (Choreographer.java:807)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1041)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7721)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:952)

Hình 1.Dấu vết ngăn xếp ANR do lệnh chờ futex gây ra.

Nguyên nhân

Cho đến nay, chúng tôi vẫn chưa xác định được nguyên nhân cốt lõi của vấn đề này. Một số nguyên nhân có thể là:

  • Triển khai quảng cáo không hợp lệ.
  • Một phiên bản WebView đã lỗi thời vì người dùng có thể đã chọn không tự động cập nhật ứng dụng.
  • Mức sử dụng tài nguyên hệ thống (CPU, GPU, v.v.) cao, có thể cần nhiều hoạt động phân tích hiệu suất.
  • Lỗi biên dịch chương trình đổ bóng. Lỗi này có thể cho biết nội dung có chương trình đổ bóng không tương thích hoặc người dùng đã cài đặt phiên bản WebView cũ.

Giải pháp

  • Để thu hẹp loại nội dung đang khiến WebView chặn luồng chính, hãy thêm nhật ký vào trò chơi của bạn bất cứ khi nào một trang web được tải, hiển thị hoặc đóng.
    • Bạn có thể sử dụng dịch vụ báo cáo Backtrace hoặc Crashlytics.
    • Sau đó, sau khi phân tích dữ liệu và tìm ra vấn đề, hãy thử vô hiệu hoá các nhà cung cấp quảng cáo vi phạm.
    • Gửi kèm nhật ký bộ nhớ để đảm bảo vấn đề không liên quan đến bộ nhớ.
  • Cảnh báo người dùng cập nhật WebView từ Google Play. Từ Android 5.0 (API cấp 21) trở lên, WebView đã chuyển sang APK. Do đó, bạn có thể cập nhật riêng biệt với nền tảng Android. Để xem phiên bản WebView đang được dùng trên một thiết bị, hãy chuyển đến phần Cài đặt > Ứng dụng > Android System WebView rồi xem phiên bản ở cuối trang.
Màn hình thông tin ứng dụng cho thấy các phiên bản WebView.
Hình 1. Kiểm tra phiên bản WebView.

Tạm dừng Unity

Khi UnityPlayerActivity nhận được lệnh gọi onPause(), chuỗi thao tác sau sẽ bắt đầu:

  1. UnityPlayerActivity thông báo cho công cụ thời gian chạy Unity rằng hoạt động đã tạm dừng.
  2. Unity gọi mọi MonoBehaviour triển khai sự kiện OnApplicationPause.
  3. Unity sẽ dừng các thành phần và mô-đun của mình, chẳng hạn như phát âm thanh, kết xuất, vòng lặp trò chơi và ảnh động.
  4. Để đảm bảo cả Unity Android Player (UAP) và công cụ đều được đồng bộ hoá, UAP sẽ đợi 4 giây để công cụ dừng.
  5. Nếu thao tác đó mất hơn 5 giây, hệ thống sẽ kích hoạt lỗi ANR.

Dấu vết ngăn xếp

"main" tid=1 Timed Waiting
jdk.internal.misc.Unsafe.park (Native method)
java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:234)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos (AbstractQueuedSynchronizer.java:1079)
java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos (AbstractQueuedSynchronizer.java:1369)
java.util.concurrent.Semaphore.tryAcquire (Semaphore.java:415)
com.unity3d.player.UnityPlayer.pauseUnity (UnityPlayer.java:833)
com.unity3d.player.UnityPlayer.pause (UnityPlayer.java:796)
com.unity3d.player.UnityPlayerActivity.onPause (UnityPlayerActivity.java:117)
android.app.Activity.performPause (Activity.java:8517)
android.app.Instrumentation.callActivityOnPause (Instrumentation.java:1618)
android.app.ActivityThread.performPauseActivityIfNeeded (ActivityThread.java:5061)
android.app.ActivityThread.performPauseActivity (ActivityThread.java:5022)
android.app.ActivityThread.handlePauseActivity (ActivityThread.java:4974)
android.app.servertransaction.PauseActivityItem.execute (PauseActivityItem.java:48)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:179)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2303)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:201)
android.os.Looper.loop (Looper.java:288)
android.app.ActivityThread.main (ActivityThread.java:7884)
java.lang.reflect.Method.invoke (Native method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)

Hình 3. Lỗi ANR do một tín hiệu bán tải không bao giờ được giải phóng.

Giải pháp

Đảm bảo rằng mã trò chơi C# của bạn không mất quá nhiều thời gian để hoàn tất quá trình thực thi trong sự kiện tạm dừng hoặc tiếp tục.

  • Lập hồ sơ cho trò chơi của bạn và kiểm tra xem OnApplicationPause có phải là một thao tác tốn kém hay không. Bạn có thể sử dụng Stopwatch.
  • Tránh các thao tác I/O hoặc yêu cầu mạng đồng bộ.
  • Di chuyển các thao tác sang một Thread khác bằng cách sử dụng Task. Unity 2023.1 hỗ trợ một mô hình lập trình không đồng bộ đơn giản bằng cách sử dụng các từ khoá asyncawait C#.

UnitySendMessage bị chặn

Các trình bổ trợ và SDK Java Unity gửi dữ liệu đến lớp trò chơi C# bằng JNI. Tuy nhiên, hoạt động giao tiếp này có thể chặn luồng chính do một quy trình đồng bộ hoá gốc (chẳng hạn như mutex), gây ra lỗi ANR do tranh chấp khoá.

Dấu vết ngăn xếp

Lỗi ANR trong hình 4 là do một thao tác diễn ra trong thời gian dài trong mã C# do một trình bổ trợ Java gọi. Công cụ Unity sử dụng mutex Kế thừa không ưu tiên để đảm bảo thực thi chính xác.

libc.so NonPI::MutexLockWithTimeout(pthread_mutex_internal_t*, bool, timespec const*) + 604
com.unity3d.player.UnityPlayer.nativeUnitySendMessage (Native method)
com.unity3d.player.UnityPlayer.UnitySendMessage (UnityPlayer.java:665)

Hình 4. Lỗi ANR do tranh chấp khoá.

Nguyên nhân

Vấn đề là một số thông báo đang được gửi đi khi ứng dụng được tiếp tục. Các thông báo được xếp hàng đợi vì không thể gửi khi trò chơi ở chế độ nền. Tất cả các thông báo đều được gửi cùng lúc khi ứng dụng tiếp tục hoạt động.

Trong thời gian tạm dừng, bạn thường lưu trữ thông tin của trò chơi trên máy chủ; ví dụ: bạn ghi lại vị trí của người chơi trong trò chơi để người chơi có thể quay lại cùng một vị trí khi trò chơi tiếp tục.

Tải này, kết hợp với mã của bên thứ ba khác tạo ra tải riêng, có thể làm quá tải tài nguyên của thiết bị, đặc biệt là luồng chính. Luồng chính chạy giao diện người dùng của ứng dụng và thường là vị trí chính của lỗi ANR. Vì vậy, mọi tải công việc được thêm vào luồng chính đều làm tăng khả năng xảy ra lỗi ANR.

Giải pháp

Trong thời gian tạm dừng ứng dụng, hãy đảm bảo mọi thao tác mã của bạn đều cần thiết hoặc thử lưu trạng thái của người dùng vào bộ nhớ thiết bị cục bộ. Và tất nhiên, hãy xem liệu bạn có thể hoàn tất những hành động này bên ngoài khoảng thời gian tạm dừng hay không.

Một số phương pháp:

  • Di chuyển thao tác C# xử lý một message sang một luồng không phải là luồng chính.
    • Nếu mã của bạn không phụ thuộc vào ngữ cảnh của luồng chính của Unity, hãy sử dụng Task để giao tiếp thay vì thông báo.
  • Không gửi nhiều thông báo từ trình bổ trợ khi trò chơi đang tạm dừng.
    • Công cụ này không thể gửi thông báo khi trò chơi ở chế độ nền.
    • Chỉ gửi trạng thái dữ liệu cuối cùng đến trò chơi nếu trạng thái đó không ảnh hưởng đến chức năng của trò chơi.

Install Referrer

Play Install Referrer là một chuỗi duy nhất được gửi đến Cửa hàng Play bất cứ khi nào người dùng nhấp vào một quảng cáo. Đây là giá trị nhận dạng theo dõi quảng cáo dành riêng cho Android. Sau khi được cài đặt, ứng dụng sẽ gửi trình giới thiệu lượt cài đặt cho đối tác phân bổ. Đối tác này sẽ so khớp nguồn với lượt cài đặt (phân bổ lượt chuyển đổi).

Dấu vết ngăn xếp

Hình 5 cho thấy dấu vết ngăn xếp ANR của một trò chơi sử dụng Facebook SDK để truy xuất thông tin phân bổ lượt cài đặt.

Hình 5. Báo cáo Android Vitals chứa một lệnh gọi Binder.

Nguyên nhân

Lỗi ANR là do lệnh gọi liên kết bị chậm gây ra. Tuy nhiên, bạn không thể xác định nguyên nhân gốc rễ nếu không có quyền truy cập vào mã nguồn SDK.

Giải pháp

Để giải quyết loại vấn đề này, bạn cần liên hệ với nhà phát triển SDK hoặc tìm kiếm nhiều trên mạng để tìm ra giải pháp tiềm năng, kiểm tra xem phiên bản SDK mới hơn có giải quyết được lỗi ANR cho những người khác hay không, hoặc thậm chí thử nghiệm với một chiến lược phát hành quy mô nhỏ.

Google cung cấp một trang Chỉ mục SDK kết hợp dữ liệu sử dụng của các ứng dụng trên Google Play với thông tin thu thập được thông qua tính năng phát hiện đoạn mã để cung cấp các thuộc tính và tín hiệu giúp bạn quyết định nên sử dụng, giữ hay xoá một SDK khỏi ứng dụng.

Tài nguyên khác

Để tìm hiểu thêm về lỗi ANR, hãy tham khảo các tài nguyên sau: