यूनिटी गेम से जुड़े सामान्य ANR

Unity में एएनआर की समस्या कई वजहों से होती है. ज़्यादातर एएनआर, Android और Unity कॉम्पोनेंट के गलत इस्तेमाल और उनके बीच सही तरीके से कम्यूनिकेशन न होने की वजह से होते हैं.

WebView

WebView एक Android क्लास है, जो वेब पेज दिखाती है. तीसरे पक्ष के एसडीके (जैसे कि विज्ञापन) WebView का इस्तेमाल, UnityPlayerActivity के अलावा अन्य गतिविधियों में डाइनैमिक वेब कॉन्टेंट दिखाने के लिए करते हैं. तीसरे पक्ष के एसडीके, WebView का गलत इस्तेमाल करते हैं, तब एएनआर की समस्या होती है.

स्‍टैक ट्रेस

स्टैक ट्रेस, एएनआर की वजह जानने का पहला तरीका है.

/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. futex के इंतज़ार की वजह से होने वाली एएनआर स्टैक ट्रेस.

वजह

अब तक, इस समस्या की मुख्य वजह का पता नहीं चल पाया है. इसके पीछे ये वजहें हो सकती हैं:

  • विज्ञापन को सही तरीके से लागू न किया गया हो.
  • WebView का पुराना वर्शन, क्योंकि उपयोगकर्ता ने शायद ऐप्लिकेशन को अपने-आप अपडेट होने की सुविधा बंद की हो.
  • सिस्टम के संसाधनों (सीपीयू, जीपीयू वगैरह) का ज़्यादा इस्तेमाल किया जा रहा है. इसके लिए, बहुत ज़्यादा प्रोफ़ाइलिंग की ज़रूरत पड़ सकती है.
  • शेडर कंपाइलेशन क्रैश हो जाता है. इससे पता चल सकता है कि कॉन्टेंट में ऐसा शेडर है जो काम नहीं करता या उपयोगकर्ता ने WebView का पुराना वर्शन इंस्टॉल किया है.

समाधान

  • यह पता लगाने के लिए कि किस तरह के कॉन्टेंट की वजह से WebView, मुख्य थ्रेड को ब्लॉक कर रहा है, जब भी कोई वेब पेज लोड, दिखाया या बंद किया जाता है, तब अपने गेम में लॉग जोड़ें.
    • Backtrace या Crashlytics की रिपोर्टिंग सेवाओं का इस्तेमाल किया जा सकता है.
    • इसके बाद, डेटा का विश्लेषण करें और समस्या का पता लगाएं. फिर, विज्ञापन दिखाने वाली उन कंपनियों को बंद करने की कोशिश करें जिनकी वजह से समस्या आ रही है.
    • मेमोरी लॉग शामिल करें, ताकि यह पक्का किया जा सके कि समस्या मेमोरी से जुड़ी नहीं है.
  • उपयोगकर्ता को Google Play से WebView अपडेट करने के लिए सूचना दें. Android 5.0 (एपीआई लेवल 21) और इसके बाद के वर्शन में, WebView को APK में बदल दिया गया है. इसलिए, इसे Android प्लैटफ़ॉर्म से अलग अपडेट किया जा सकता है. किसी डिवाइस पर WebView का कौनसा वर्शन इस्तेमाल किया जा रहा है, यह देखने के लिए सेटिंग > ऐप्लिकेशन > Android System WebView पर जाएं. इसके बाद, पेज के सबसे नीचे मौजूद वर्शन देखें.
ऐप्लिकेशन की जानकारी वाली स्क्रीन पर, WebView के वर्शन दिख रहे हैं.
पहली इमेज. WebView का वर्शन देखें.

Unity pause

जब UnityPlayerActivity को onPause() कॉल मिलता है, तो ये कार्रवाइयां शुरू हो जाती हैं:

  1. UnityPlayerActivity, Unity रनटाइम इंजन को सूचना देता है कि गतिविधि रुक गई है.
  2. Unity, MonoBehaviour को हर बार कॉल करता है, जो OnApplicationPause इवेंट को लागू करता है.
  3. Unity, अपने कॉम्पोनेंट और मॉड्यूल को बंद कर देता है. जैसे, आवाज़ चलाना, रेंडरिंग, गेम लूप, और ऐनिमेशन.
  4. यह पक्का करने के लिए कि Unity Android Player (UAP) और इंजन, दोनों सिंक हो जाएं, UAP इंजन के रुकने के लिए चार सेकंड तक इंतज़ार करता है.
  5. अगर इस ऑपरेशन में पांच सेकंड से ज़्यादा समय लगता है, तो सिस्टम एएनआर ट्रिगर करता है.

स्‍टैक ट्रेस

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

तीसरी इमेज. सेमाफ़ोर के रिलीज़ न होने की वजह से एएनआर हुआ.

समाधान

पक्का करें कि आपका C# गेम कोड, pause या resume इवेंट के दौरान एक्ज़ीक्यूट होने में ज़्यादा समय न ले.

  • अपने गेम की प्रोफ़ाइल बनाएं और देखें कि क्या OnApplicationPause एक महंगा ऑपरेशन है. Stopwatch का इस्तेमाल किया जा सकता है.
  • I/O कार्रवाइयों या सिंक्रोनस नेटवर्क अनुरोधों से बचें.
  • Task का इस्तेमाल करके, कार्रवाइयों को किसी दूसरे Thread पर ले जाएं. Unity 2023.1 में, C# async और await कीवर्ड का इस्तेमाल करके, आसान एसिंक्रोनस प्रोग्रामिंग मॉडल का इस्तेमाल किया जा सकता है.

UnitySendMessage को ब्लॉक किया गया

Java Unity प्लगिन और SDK टूल, JNI का इस्तेमाल करके C# गेम लेयर को डेटा भेजते हैं. हालांकि, इस कम्यूनिकेशन की वजह से मुख्य थ्रेड ब्लॉक हो सकती है. ऐसा म्यूटेक्स जैसे नेटिव सिंक्रनाइज़ेशन रूटीन की वजह से होता है. इससे लॉक कंटेंशन की वजह से, एएनआर की गड़बड़ी हो सकती है.

स्‍टैक ट्रेस

चौथी इमेज में दिखाया गया एएनआर, 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)

चौथी इमेज. लॉक कंटेंशन की वजह से एएनआर की समस्या हुई.

वजह

समस्या यह है कि ऐप्लिकेशन को फिर से शुरू करने पर, कई मैसेज भेजे जा रहे हैं. मैसेज को कतार में रखा जाता है, क्योंकि गेम के बैकग्राउंड में चलने के दौरान उन्हें नहीं भेजा जा सकता. ऐप्लिकेशन फिर से शुरू होने पर, सभी मैसेज एक साथ भेज दिए जाते हैं.

गेम को रोकने की अवधि के दौरान, आम तौर पर गेम की जानकारी सर्वर पर सेव की जाती है. उदाहरण के लिए, गेम में किसी खिलाड़ी की पोज़िशन रिकॉर्ड की जाती है, ताकि गेम फिर से शुरू होने पर खिलाड़ी उसी जगह से गेम खेलना शुरू कर सके.

इस वर्कलोड के साथ-साथ, तीसरे पक्ष के अन्य कोड भी अपना वर्कलोड बनाते हैं. इससे डिवाइस के संसाधनों, खासकर मुख्य थ्रेड पर ज़्यादा लोड पड़ सकता है. मुख्य थ्रेड, ऐप्लिकेशन के यूज़र इंटरफ़ेस को चलाती है. साथ ही, अक्सर यह ANR की मुख्य वजह होती है. इसलिए, मुख्य थ्रेड पर जोड़ा गया कोई भी वर्कलोड, एएनआर की संभावना को बढ़ाता है.

समाधान

ऐप्लिकेशन को कुछ समय के लिए रोकने के दौरान, पक्का करें कि आपके कोड की सभी कार्रवाइयां ज़रूरी हों. इसके अलावा, उपयोगकर्ता की स्थिति को अपने डिवाइस की लोकल मेमोरी में सेव करने की कोशिश करें. साथ ही, यह भी देखें कि क्या इन कार्रवाइयों को वीडियो को रोकने की अवधि के बाहर भी पूरा किया जा सकता है.

कुछ तरीके:

  • मैसेज को हैंडल करने वाली C# कार्रवाई को मुख्य थ्रेड के अलावा किसी अन्य थ्रेड पर ले जाएं.
    • अगर आपका कोड, Unity के मुख्य थ्रेड कॉन्टेक्स्ट पर निर्भर नहीं करता है, तो मैसेज के बजाय कम्यूनिकेशन के लिए Task का इस्तेमाल करें.
  • गेम के रुकने पर, अपने प्लगिन से कई मैसेज न भेजें.
    • गेम के बैकग्राउंड में चलने के दौरान, इंजन मैसेज नहीं भेज सकता.
    • अगर गेम की फ़ंक्शनैलिटी पर असर नहीं पड़ता है, तो गेम को सिर्फ़ डेटा की आखिरी स्थिति भेजें.

रेफ़रर इंस्टॉल करें

Play Install Referrer एक यूनीक स्ट्रिंग होती है. जब भी कोई उपयोगकर्ता किसी विज्ञापन पर क्लिक करता है, तब इसे Play Store को भेजा जाता है. यह Android के लिए विज्ञापन ट्रैक करने वाला आइडेंटिफ़ायर है. ऐप्लिकेशन इंस्टॉल हो जाने के बाद, यह इंस्टॉल रेफ़रर को एट्रिब्यूशन पार्टनर को भेजता है. इसके बाद, एट्रिब्यूशन पार्टनर, सोर्स को इंस्टॉल से मैच करता है और कन्वर्ज़न को एट्रिब्यूट करता है.

स्‍टैक ट्रेस

पांचवीं इमेज में, ऐसे गेम का एएनआर स्टैक ट्रेस दिखाया गया है जो इंस्टॉल एट्रिब्यूशन पाने के लिए Facebook SDK टूल का इस्तेमाल करता है.

पांचवीं इमेज. Android की ज़रूरी जानकारी की रिपोर्ट में बाइंडर कॉल शामिल है.

वजह

ANR की गड़बड़ी, बाइंडर कॉल में ज़्यादा समय लगने की वजह से हुई. हालांकि, एसडीके के सोर्स कोड का ऐक्सेस न होने पर, समस्या की असली वजह का पता नहीं लगाया जा सकता.

समाधान

इस तरह की समस्या को हल करने के लिए, एसडीके डेवलपर से संपर्क करना पड़ता है या संभावित समाधान के लिए ऑनलाइन खोज करनी पड़ती है. साथ ही, यह देखना पड़ता है कि एसडीके के नए वर्शन से, अन्य लोगों के लिए एएनआर की समस्या हल हुई है या नहीं. इसके अलावा, छोटे पैमाने पर रोलआउट करने की रणनीति का इस्तेमाल भी किया जा सकता है.

Google, एसडीके इंडेक्स पेज उपलब्ध कराता है. इसमें, Google Play पर मौजूद अलग-अलग ऐप्लिकेशन के इस्तेमाल के बारे में डेटा और कोड की पहचान करके इकट्ठा की गई जानकारी को एक साथ दिखाया जाता है. इससे आपको एट्रिब्यूट और सिग्नल मिलते हैं, जो यह तय करने में आपकी मदद करते हैं कि ऐप्लिकेशन के लिए कौनसा एसडीके टूल चुनना है या किसी एसडीके टूल का इस्तेमाल जारी रखना है या उसे हटाना है.

अन्य संसाधन

एएनआर के बारे में ज़्यादा जानने के लिए, यहां दिए गए संसाधनों का इस्तेमाल करें: