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. Các lỗi ANR thường gặp nhất là do việc sử dụng sai mục đích các thành phần Android và Unity cũng như hiểu nhầm các thành phần này.

WebView

WebView là một lớp Android hiển thị các trang web. Bên thứ ba SDK (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 bên thứ ba SDK sử dụng sai WebView.

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

Dấu vết ngăn xếp là nguồn hỗ trợ đầu tiên của bạn để tìm 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 quá trình chờ futex gây ra.

Nguyên nhân

Cho đến nay, nguyên nhân gốc rễ của vấn đề này vẫn chưa rõ ràng. Có thể có một số nguyên nhân bao gồm:

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

Giải pháp

  • Để thu hẹp loại nội dung nào 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 trang web được tải, hiển thị, hoặc đã đóng.
    • Bạn có thể sử dụng Backtrace hoặc Crashlytics báo cáo.
    • Sau đó, sau khi phân tích dữ liệu và tìm ra vấn đề, hãy thử tắt nhà cung cấp quảng cáo vi phạm.
    • Cung cấp nhật ký bộ nhớ để đảm bảo vấn đề không liên quan đến bộ nhớ.
  • Nhắc người dùng cập nhật WebView trên Google Play. Từ Android 5.0 (API cấp 21) trở lên, WebView đã chuyển sang một tệp APK. Do đó, có thể được cập nhật riêng biệt với nền tảng Android. Để biết phiên bản WebView nào đang được sử dụng trên một thiết bị, hãy chuyển đến phần Cài đặt > Ứng dụng > Hệ thống Android WebView và 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. Hãy 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 sau hoạt động bắt đầu:

  1. UnityPlayerActivity thông báo cho công cụ thời gian chạy Unity rằng hoạt động đã bị tạm dừng.
  2. Unity gọi mọi MonoBehaviour triển khai OnApplicationPause sự kiện.
  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ụ được đồng bộ hoá, UAP sẽ chờ 4 giây để động cơ dừng lại.
  5. Nếu quá trình đó mất hơn 5 giây, thì hệ thống sẽ kích hoạt mộ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 semaphore gây ra chưa từng được phát hành.

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 tạm dừng hoặc tiếp tục sự kiện.

  • Phân tích tài nguyên cho trò chơi của bạn và kiểm tra xem OnApplicationPause có tốn kém không hoạt động. Bạn có thể dùng Stopwatch.
  • Tránh các hoạt động I/O hoặc cá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 hàm Task. Unity 2023.1 hỗ trợ phiên bản đơn giản hoá mô hình lập trình không đồng bộ bằng C# asyncawait từ khoá.

UnitySendMessage bị chặn

Các trình bổ trợ và SDK Java cho 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 mã gốc quy trình đồng bộ hoá, 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 do một thao tác dài trong mã C# được gọi bởi Trình bổ trợ Java. Công cụ Unity sử dụng cơ chế Kế thừa không ưu tiên mutex để đả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á gây ra.

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. Tin nhắn được xếp vào hàng đợi vì không gửi được tin nhắn khi đang chơi trò chơi đang ở chế độ nền. Tất cả các tin nhắn sẽ được gửi đi cùng một lúc khi ứng dụng tiếp tục.

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 một người chơi trong trò chơi để người chơi đó có thể quay lại vị trí cũ khi trò chơi tiếp tục.

Khối lượng công việc này, kết hợp với mã của bên thứ ba khác đang tạo ra khối lượng công việc 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. Chính luồng này chạy giao diện người dùng của một ứng dụng và thường là vị trí chính của lỗi ANR. Vì vậy, mọi khối lượng công việc tăng thêm trên luồng chính đều làm tăng nguy cơ 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 tất cả thao tác liên quan đến mã đều cần thiết, hoặc hãy thử lưu trạng thái của người dùng vào bộ nhớ cục bộ của thiết bị. Và tất nhiên là bạn cũng có thể thực hiện cả những hành động này 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 tin nhắn sang chuỗi ngoài luồng chính.
    • Nếu mã của bạn không phụ thuộc vào ngữ cảnh luồng chính của Unity, hãy sử dụng Task để liên lạc thay vì nhắn tin.
  • 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ụ không thể gửi tin nhắn khi trò chơi đang chạy trong nền.
    • Chỉ gửi trạng thái dữ liệu gần đây nhất đến trò chơi nếu trạng thái đó không ảnh hưởng đến trò chơi của Google.

Cài đặt 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 một người dùng nhấp vào 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. Một lần thì ứng dụng sẽ gửi liên kết giới thiệu lượt cài đặt đến đối tác phân bổ, 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 minh hoạ dấu vết ngăn xếp ANR của một trò chơi sử dụng SDK Facebook để truy xuất thuộc tính lượt cài đặt.

Hình 5. Báo cáo Android vitals có chứa lệnh gọi Binder.

Nguyên nhân

Lỗi ANR xảy ra do lệnh gọi liên kết chậm. Tuy nhiên, căn nguyên không thể được xác định mà 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 lạc với nhà phát triển SDK hoặc rất nhiều người tìm kiếm giải pháp tiềm năng trên mạng, kiểm tra xem giải pháp mới hơn phiên bản SDK giải quyết lỗi ANR cho người khác hoặc thậm chí thử nghiệm với một phát hành ứng dụng.

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

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:

  • Gỡ lỗi ANR – Phát triển trò chơi trên Android
  • ANR — Chất lượng ứng dụng