ANR ทั่วไปสำหรับเกม Unity

ANR ของ Unity เกิดขึ้นได้จากหลายสาเหตุ ANR ที่พบบ่อยที่สุดเกิดจากการ ใช้คอมโพเนนต์ของ Android และ Unity ในทางที่ผิด รวมถึงการสื่อสารที่ไม่ถูกต้อง

WebView

WebView เป็นคลาส Android ที่แสดงหน้าเว็บ SDK ของบุคคลที่สาม (เช่น โฆษณา) ใช้ WebView เพื่อแสดงเนื้อหาเว็บแบบไดนามิก ในกิจกรรมอื่นๆ นอกเหนือจาก UnityPlayerActivity ANR เกิดขึ้นเมื่อ SDK ของบุคคลที่สาม ใช้ WebView ในทางที่ผิด

สแต็กเทรซ

Stack Trace เป็นแหล่งข้อมูลแรกที่คุณควรใช้เพื่อทำความเข้าใจสาเหตุของ 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)

รูปที่ 1. สแต็กเทรซของ ANR ที่เกิดจากการรอ futex

สาเหตุ

ขณะนี้เรายังไม่ทราบสาเหตุของปัญหานี้ สาเหตุที่เป็นไปได้บางส่วนมีดังนี้

  • การติดตั้งโฆษณาที่ไม่ดี
  • WebViewเนื่องจากผู้ใช้อาจเลือกที่จะไม่อัปเดต แอปโดยอัตโนมัติ
  • การใช้งานทรัพยากรระบบ (CPU, GPU ฯลฯ) สูง ซึ่งอาจต้องมีการทำโปรไฟล์จำนวนมาก
  • การคอมไพล์ Shader ขัดข้อง ซึ่งอาจบ่งชี้ว่าเนื้อหามี Shader ที่เข้ากันไม่ได้ หรือผู้ใช้ได้ติดตั้งWebView เวอร์ชันเก่า

โซลูชัน

  • หากต้องการจำกัดประเภทเนื้อหาที่ทำให้ WebView บล็อก เธรดหลัก ให้เพิ่มบันทึกลงในเกมทุกครั้งที่มีการโหลด แสดง หรือปิดหน้าเว็บ
    • คุณใช้บริการรายงาน Backtrace หรือ Crashlytics ได้
    • จากนั้นหลังจากวิเคราะห์ข้อมูลและพบปัญหาแล้ว ให้ลองปิดใช้ผู้ให้บริการโฆษณาที่ทำให้เกิดปัญหา
    • รวมบันทึกหน่วยความจำเพื่อให้แน่ใจว่าปัญหาไม่ได้เกี่ยวข้องกับหน่วยความจำ
  • แจ้งเตือนให้ผู้ใช้อัปเดต WebView จาก Google Play ตั้งแต่ Android 5.0 (API ระดับ 21) ขึ้นไป WebView ได้ย้ายไปอยู่ใน APK แล้ว จึงสามารถอัปเดตแยกจากแพลตฟอร์ม Android ได้ หากต้องการดูเวอร์ชันของ WebView ที่ใช้อยู่ในอุปกรณ์ ให้ไปที่การตั้งค่า > แอป > Android System WebView แล้วดูเวอร์ชันที่ด้านล่างของหน้า
หน้าจอข้อมูลแอปที่แสดงเวอร์ชัน WebView
รูปที่ 1 ตรวจสอบWebViewเวอร์ชัน

หยุด Unity ชั่วคราว

เมื่อ UnityPlayerActivity รับสาย onPause() ระบบจะเริ่มลำดับการทำงานต่อไปนี้

  1. UnityPlayerActivity จะแจ้งให้เอ็นจินรันไทม์ของ Unity ทราบว่ากิจกรรมได้ หยุดชั่วคราวแล้ว
  2. Unity จะเรียกใช้ทุก MonoBehaviour ที่ใช้เหตุการณ์ OnApplicationPause
  3. Unity จะหยุดคอมโพเนนต์และโมดูลต่างๆ เช่น การเล่นเสียง การแสดงผล ลูปเกม และภาพเคลื่อนไหว
  4. เพื่อให้แน่ใจว่าทั้ง Unity Android Player (UAP) และเครื่องยนต์ จะซิงค์กัน UAP จะรอ 4 วินาทีเพื่อให้เครื่องยนต์หยุด
  5. หากการดำเนินการดังกล่าวใช้เวลานานกว่า 5 วินาที ระบบจะทริกเกอร์ ANR

สแต็กเทรซ

"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)

รูปที่ 3 ANR ที่เกิดจาก Semaphore ที่ไม่เคยปล่อย

โซลูชัน

ตรวจสอบว่าโค้ดเกม C# ใช้เวลาไม่นานเกินไปในการดำเนินการให้เสร็จสมบูรณ์ในระหว่างเหตุการณ์หยุดชั่วคราวหรือเหตุการณ์กลับมาทำงานต่อ

  • สร้างโปรไฟล์เกมและตรวจสอบว่า OnApplicationPause เป็นการดำเนินการที่ใช้ทรัพยากรมาก หรือไม่ คุณใช้ Stopwatch ได้
  • หลีกเลี่ยงการดำเนินการ I/O หรือคำขอเครือข่ายแบบซิงโครนัส
  • ย้ายการดำเนินการไปยัง Thread อื่นโดยใช้ Task Unity 2023.1 รองรับรูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสที่เรียบง่ายโดยใช้คีย์เวิร์ด C# async และ await

UnitySendMessage ถูกบล็อก

ปลั๊กอินและ SDK ของ Java Unity จะส่งข้อมูลไปยังเลเยอร์เกม C# โดยใช้ JNI อย่างไรก็ตาม การสื่อสารนี้อาจบล็อกเทรดหลักเนื่องจากกิจวัตรการซิงค์แบบเนทีฟ เช่น Mutex ซึ่งทำให้เกิด ANR เนื่องจากมีการแย่งชิงล็อก

สแต็กเทรซ

ANR ในรูปที่ 4 เกิดจากการดำเนินการที่ใช้เวลานานในโค้ด C# ที่เรียกใช้โดยปลั๊กอิน Java เอนจิน Unity ใช้ Non-Priority Inheritance mutex เพื่อให้มั่นใจว่าการดำเนินการถูกต้อง

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)

รูปที่ 4 ANR มีสาเหตุมาจากการแย่งชิงล็อก

สาเหตุ

ปัญหาคือมีการส่งข้อความหลายข้อความเมื่อแอปพลิเคชัน กลับมาทำงานต่อ ระบบจะจัดคิวข้อความเนื่องจากส่งไม่ได้ขณะที่เกมทำงานอยู่เบื้องหลัง ระบบจะส่งข้อความทั้งหมดพร้อมกันเมื่อ แอปกลับมาทำงานต่อ

โดยปกติแล้ว ในช่วงหยุดชั่วคราว คุณจะจัดเก็บข้อมูลของเกมไว้ในเซิร์ฟเวอร์ เช่น บันทึกตำแหน่งของผู้เล่นในเกมเพื่อให้ผู้เล่นกลับมายังตำแหน่งเดิมได้เมื่อเกมกลับมาเล่นต่อ

ภาระงานนี้เมื่อรวมกับโค้ดของบุคคลที่สามอื่นๆ ที่สร้างภาระงานของตนเอง อาจทำให้ทรัพยากรของอุปกรณ์ทำงานหนักเกินไป โดยเฉพาะอย่างยิ่งเธรดหลัก เธรดหลักจะเรียกใช้อินเทอร์เฟซผู้ใช้ของแอป และมักเป็นตำแหน่งหลักของ ANR ดังนั้น ปริมาณงานที่เพิ่มขึ้นในเทรดหลักจะเพิ่มโอกาสในการเกิด ANR

โซลูชัน

ในระหว่างที่แอปหยุดชั่วคราว ให้ตรวจสอบว่าการดำเนินการของโค้ดทั้งหมดมีความจำเป็น หรือ ลองบันทึกสถานะของผู้ใช้ในหน่วยความจำของอุปกรณ์ในเครื่อง และแน่นอนว่าคุณจะดูได้ว่า คุณสามารถดำเนินการเหล่านี้นอกช่วงหยุดชั่วคราวได้หรือไม่

แนวทางบางส่วน

  • ย้ายการดำเนินการ C# ที่จัดการข้อความไปยังเทรดอื่นที่ไม่ใช่เทรดหลัก
    • หากโค้ดไม่ได้ขึ้นอยู่กับบริบทของเทรดหลักของ Unity ให้ใช้ Task เพื่อการสื่อสารแทนข้อความ
  • อย่าส่งข้อความหลายรายการจากปลั๊กอินเมื่อเกมหยุดชั่วคราว
    • โดยเครื่องมือจะส่งข้อความไม่ได้ขณะที่เกมทำงานในเบื้องหลัง
    • ส่งเฉพาะสถานะข้อมูลล่าสุดไปยังเกมหากไม่ส่งผลต่อฟังก์ชันการทำงานของเกม

เครื่องมือระบุแหล่งที่มาของการติดตั้ง

เครื่องมืออ้างอิงการติดตั้ง Play คือสตริงที่ไม่ซ้ำกันซึ่งส่งไปยัง Play Store ทุกครั้งที่ผู้ใช้คลิกโฆษณา ซึ่งเป็นตัวระบุการติดตามโฆษณาที่เฉพาะเจาะจงสำหรับ Android เมื่อติดตั้งแล้ว แอปจะส่งผู้แนะนำการติดตั้งไปยังพาร์ทเนอร์ระบุแหล่งที่มา ซึ่งจะจับคู่แหล่งที่มากับการติดตั้ง (ระบุแหล่งที่มาของ Conversion)

สแต็กเทรซ

รูปที่ 5 แสดง Stack Trace ของ ANR จากเกมที่ใช้ Facebook SDK เพื่อ ดึงข้อมูลการระบุแหล่งที่มาของการติดตั้ง

รูปที่ 5 รายงาน Android Vitals ที่มีการเรียก Binder

สาเหตุ

ANR มีสาเหตุมาจากการเรียกใช้ Binder ที่ช้า อย่างไรก็ตาม เราไม่สามารถระบุสาเหตุที่แท้จริงได้หากไม่มีสิทธิ์เข้าถึงซอร์สโค้ดของ SDK

โซลูชัน

การแก้ปัญหาประเภทนี้เกี่ยวข้องกับการสื่อสารกับนักพัฒนาซอฟต์แวร์ SDK หรือ การค้นหาออนไลน์จำนวนมากเพื่อหาโซลูชันที่เป็นไปได้ การตรวจสอบว่า SDK เวอร์ชันใหม่ แก้ปัญหา ANR ให้กับผู้อื่นได้หรือไม่ หรือแม้แต่การทดลองใช้กลยุทธ์การเปิดตัวขนาดเล็ก

Google มีหน้าดัชนี SDK ที่รวมข้อมูลการใช้งาน จากแอปของ Google Play เข้ากับข้อมูลที่รวบรวมผ่านการตรวจหาโค้ดเพื่อ ระบุแอตทริบิวต์และสัญญาณต่างๆ ที่ออกแบบมาเพื่อช่วยคุณตัดสินใจว่าจะใช้ เก็บ หรือนำ SDK ออกจากแอปหรือไม่

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ ANR ได้จากแหล่งข้อมูลต่อไปนี้