מקרי ANR נפוצים במשחקי Unity

מקרי ANR ב-Unity מתרחשים מסיבות שונות. מקרי ה-ANR הנפוצים ביותר נגרמים על ידי שימוש לרעה ברכיבי Android ו-Unity והתקשורת השגויה שלהם.

WebView

WebView הוא מחלקה של Android שמציגה דפי אינטרנט. צד שלישי ערכות SDK (כמו מודעות) משתמשות ב-WebView כדי להציג תוכן אינטרנט דינמי בפעילויות אחרות מלבד UnityPlayerActivity. מקרי ANR מתרחשים כשצדדים שלישיים ערכות SDK משתמשות לרעה ב-WebView.

דוח קריסות

דוח הקריסות הוא הפנייה הראשונה להבנת הגורם ל-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 compilation, מה שיכול להעיד על כך לתוכן יש תוכנת הצללה (shader) לא תואמת או שיש למשתמש WebView ישן הותקנה.

הפתרון

  • כדי לצמצם את סוג התוכן שגורם ל-WebView לחסום את ה-thread הראשי, הוספת יומנים למשחק בכל פעם שדף אינטרנט נטען, מוצג, או סגור.
    • אפשר להשתמש ב-Backtrace או ב-Crashlytics שירותי דיווח.
    • לאחר מכן, לאחר ניתוח הנתונים ואיתור הבעיה, נסו להשבית את כדי לפגוע בספקי המודעות.
    • צריך לכלול יומני זיכרון כדי לוודא שהבעיה לא קשורה לזיכרון.
  • התראה למשתמש לעדכן את WebView מ-Google Play. מ-Android מגרסה 5.0 (רמת API 21) ומעלה, WebView עבר ל-APK. לכן, יכול להיות מתעדכנת בנפרד מפלטפורמת Android. כדי לראות את הגרסה של WebView נמצא בשימוש במכשיר מסוים. יש לעבור אל הגדרות > אפליקציות > מערכת Android 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 שנגרמה על ידי סמפור שלא שוחרר אף פעם.

הפתרון

מוודאים שקוד המשחק C# לא נמשך זמן רב מדי במהלך ההרצה של הקוד להשהות או להמשיך את האירוע.

  • יוצרים פרופיל של המשחק ובודקים אם OnApplicationPause יקר פעולה. אפשר להשתמש ב-Stopwatch.
  • יש להימנע מפעולות קלט/פלט (I/O) או מבקשות רשת סינכרוניות.
  • מעבירים את הפעולות ל-Thread אחר באמצעות Task ב-Unity 2023.1 יש תמיכה בגרסה פשוטה יותר מודל תכנות אסינכרוני באמצעות C# async ו-await מילות מפתח.

UnitySendMessage נחסמה

ערכות SDK ויישומי פלאגין של Java Unity שולחים נתונים לשכבת המשחק C# באמצעות JNI. אבל יכול להיות שהשיחה הזו תחסום את ה-thread הראשי בגלל שהיא מיוצגת. תרחיש סנכרון, כמו mutex, שגורם ל-ANR בגלל תחרות על נעילה.

דוח קריסות

שגיאת ה-ANR בדוגמה 4 נגרמה על ידי פעולה ארוכה בקוד C# שנקראה על ידי פלאגין של Java. המנוע של Unity משתמש בירושה שאינה בעדיפות גבוהה 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 שנגרמה בגלל תחרות על נעילה.

הסיבה

הבעיה היא שכמה הודעות נשלחות כאשר האפליקציה מתחדשת. ההודעות ממתינות כי אי אפשר לשלוח אותן בזמן המשחק ברקע. כל ההודעות נשלחות בו-זמנית כאשר האפליקציה תמשיך לפעול.

במהלך תקופת השהיה, אתה בדרך כלל מאחסן את פרטי המשחק שרת; לדוגמה, מקליטים את המיקום של שחקן במשחק, כדי שהשחקן יכולים לחזור לאותו המקום כשהמשחק ימשיך.

עומס העבודה הזה, בשילוב עם קוד אחר של צד שלישי, שיוצר עומס עבודה משלו, עלול לגרום לעומס יתר על המשאבים של המכשיר, במיוחד על ה-thread הראשי. המוצר העיקרי ה-thread מריץ את ממשק המשתמש של האפליקציה, ולרוב הוא המיקום העיקרי של מקרי ANR. אז, כל עומס עבודה נוסף ב-thread הראשי מגדיל את הסיכוי לשגיאת ANR.

הפתרון

בזמן השהיה של אפליקציה, מוודאים שכל הפעולות בקוד נחוצות, או אפשר לנסות לשמור את מצב המשתמש בזיכרון של המכשיר המקומי. וגם, כמובן, לראות אם ניתן לבצע את הפעולות האלה גם מחוץ לתקופת ההשהיה.

יש כמה גישות:

  • העברה של פעולת# C שמטפלת בהודעה לשרשור מלבד ה-thread הראשי.
    • אם הקוד לא תלוי בהקשר של ה-thread הראשי ב-Unity, משתמשים Task לתקשורת במקום להודעה.
  • אין לשלוח מספר הודעות מהפלאגין כשהמשחק מושהה.
    • המנוע לא יכול לשלוח הודעות בזמן שהמשחק פועל ברקע.
    • צריך לשלוח למשחק את מצב הנתונים האחרון רק אם הוא לא משפיע על המשחק החדשה.

התקנת מקור ההפניה

מקור ההפניה של Play להתקנה הוא מחרוזת ייחודית שנשלחת לחנות Play בכל פעם משתמש לוחץ על מודעה. זהו מזהה מעקב אחר מודעות ספציפי ל-Android. פעם אחת מותקנת, האפליקציה שולחת את הגורם המפנה להתקנה אל שותף השיוך (Attribution), תואם בין המקור להתקנה (מיוחסת להמרה).

דוח קריסות

באיור 5 מוצג דוח קריסות של מקרי ANR ממשחק שמשתמש ב-Facebook SDK כדי: מאחזרים את ייחוס ההתקנות.

איור 5. דוח תפקוד האפליקציה על סמך קריאה ל-Binnder.

הסיבה

שגיאת ה-ANR נגרמה כתוצאה מהפעלה איטית של קלסר. עם זאת, שורש הבעיה נקבע ללא גישה לקוד המקור של ה-SDK.

הפתרון

פתרון בעיות מהסוג הזה כרוך בתקשורת עם מפתח ה-SDK או רבים מחפשים באינטרנט פתרון פוטנציאלי, ובודקים אם של ה-SDK פותרת את שגיאת ה-ANR עבור אחרים, או אפילו מתנסים אסטרטגיית ההשקה.

Google מספקת דף SDK Index שמשלב נתוני שימוש מאפליקציות של Google Play עם מידע שנאסף באמצעות זיהוי קוד, לספק מאפיינים ואותות שיעזרו לכם להחליט אם להטמיע, לשמור או להסיר SDK מהאפליקציה.

מקורות מידע נוספים

למידע נוסף על מקרי ANR, אפשר לעיין במקורות המידע הבאים: