Firebase Crashlytics की मदद से, अपने ऐप्लिकेशन के मेमोरी फ़ुटप्रिंट को कम और बेहतर बनाएं. साथ ही, इसे सेगफ़ॉल्ट से बचाएं. इस सेशन में, Firebase कंसोल और Android Studio में उपलब्ध, मेमोरी से जुड़ी समस्याओं को हल करने वाले टूल के सुइट के बारे में बताया गया है. सबसे पहले, हम यह जानेंगे कि एएनआर से जुड़ी अहम जानकारी और GWP-ASan रिपोर्ट का इस्तेमाल करके, मेमोरी से जुड़े ऐसे बग का पता कैसे लगाया जा सकता है जिन्हें ढूंढना मुश्किल होता है. इसके बाद, हम आउट-ऑफ़-मेमोरी (ओओएम) और मेमोरी लिमिटर किल (एमएलके) इवेंट के लिए उपलब्ध हमारे नए टूल के बारे में जानेंगे. इसे खास तौर पर, Android 17 में मेमोरी की नई सीमाओं को समझने और उनके हिसाब से काम करने में आपकी मदद करने के लिए डिज़ाइन किया गया है.
ApplicationExitInfo क्वेरी करता है, ताकि मल्टीथ्रेड ट्रेस को दिखाया जा सके और रूट ब्लॉकिंग थ्रेड की पहचान की जा सके.ProfilingManager के साथ इंटिग्रेट होता है, ताकि डी-ऑब्फ़स्केट किए गए हीप स्नैपशॉट इकट्ठा किए जा सकें. इनकी जांच, Perfetto या Android का सीएलआई के ज़रिए की जा सकती है.ट्रांसक्रिप्ट
पोलीना गोवोर्कोवा
सभी को नमस्ते।
Firebase Crashlytics की मदद से, मेमोरी से जुड़ी समस्या हल करने की सुविधा में आपका स्वागत है.
वाह, क्या कॉर्पोरेट टाइटल है.
चलिए, इसे थोड़ा और मज़ेदार बनाते हैं.
Firebase Crashlytics की मदद से, ऐप्लिकेशन को बेहतर और तेज़ बनाना.
ओह, यह तो और भी अच्छा है.
मेरा नाम पोलिना है और मैं Firebase Crashlytics टीम में सीनियर सॉफ़्टवेयर डेवलपर हूं. आज मैं आपकी प्रज़ेंटर हूं.
डेवलपमेंट साइकल के दौरान, आपको कभी न कभी ऐसा लगेगा कि आपने सभी गड़बड़ियां ठीक कर ली हैं.
JSON पार्स करने से जुड़ी टाइपिंग की गड़बड़ियां, नल पॉइंटर की जांच करना भूल जाना, ये सब अब नहीं होगा!
इसके बाद, आपके मन में यह सवाल आ सकता है, "क्या मेरा काम पूरा हो गया?"
तो, मेरे पास तुम्हारे लिए एक बुरी खबर है, क्योंकि तुम नहीं हो.
क्योंकि, आपके पास सिर्फ़ मुश्किल समस्याएं ही बचेंगी.
मेरा मानना है कि आपको दो तरह की सबसे मुश्किल समस्याओं का सामना करना पड़ेगा. पहली, रिस्पॉन्सिवनेस और दूसरी, मेमोरी.
अच्छी बात यह है कि हमारे पास आपके लिए प्लान है.
आज हम Android और Crashlytics में मौजूद मौजूदा और नए टूल के बारे में जानेंगे. इनसे आपको ऐप्लिकेशन काम नहीं कर रहा है गड़बड़ियों, आउट ऑफ़ मेमोरी गड़बड़ियों, मेमोरी लीक, और नेटिव मेमोरी के खराब होने की वजह से, ऐप्लिकेशन के रिस्पॉन्स न देने की समस्याओं की असल वजह का पता लगाने में मदद मिलेगी.
चलिए, जानते हैं।
रिस्पॉन्सिवनेस और मेमोरी से जुड़ी समस्याएं इतनी बदनाम क्यों हैं?
एक क्लासिक गड़बड़ी का उदाहरण लें: आपने एक स्ट्रिंग तय की, उसे शून्य पर सेट किया, और किसी समय उसकी लंबाई को पढ़ने की कोशिश की.
आपको क्या मिलता है?
एक पुराना NullPointerException.
हालांकि, यहां काम आसान है, क्योंकि एक्सेप्शन स्टैक ट्रेस, कोड की उस लाइन को दिखाता है जिसकी वजह से आपका ऐप्लिकेशन क्रैश हुआ.
बाईं ओर मौजूद स्टैक ट्रेस को देखें.
java.lang.NullPointerException
at MainActivity.onCreate(MainActivity.kt:42)
at android.app.Activity.performCreate
at android.os.Handler.dispatchMessage
at android.os.Looper.loopOnce
at android.app.ActivityThread.mainहम तुरंत बता सकते हैं कि समस्या MainActivity.kt की 42वीं लाइन में है.
यह तरीका, ज़्यादातर सामान्य गड़बड़ियों के लिए सही है.
अब, जब रिस्पॉन्सिवनेस या मेमोरी से जुड़ी कोई समस्या आती है, जैसे कि दाईं ओर मौजूद यह ऐप्लिकेशन काम नहीं कर रहा है, तो स्टैक ट्रेस से असल समस्या का पता नहीं चलता.
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1
| state=S schedstat=( 1450239 48192 145 )
at DataManager.getData(DataManager.kt:25)
- waiting to lock <0x0a1b> held by thread 12
at MainActivity.onResume(MainActivity.kt:50)
at android.app.Activity.performResume
at android.os.Handler.dispatchMessage
at android.os.Looper.loopOnce
at android.app.ActivityThread.mainसबसे सही मामले में, यह आपको सिर्फ़ असर दिखाता है. यह कोई ऐसा थ्रेड या ऐलोकेशन होता है जो ऐप्लिकेशन के फ़्रीज़ होने या आउट ऑफ़ मेमोरी होने के समय चल रहा था.
हालांकि, असल वजह बहुत पहले या शायद कहीं और ही हुई थी.
अच्छी बात यह है कि इस समस्या के बारे में आपको अंदाज़ा लगाने की ज़रूरत नहीं है, क्योंकि Firebase Crashlytics के पास इसका जवाब है.
समस्या हल करने से पहले, हमें बताएं कि क्या आपको Crashlytics के बारे में पता है.
अरे, बहुत बढ़िया.
इतने सारे हाथ देखकर मुझे बहुत खुशी हुई.
अगर आप Firebase Crashlytics के बारे में नहीं जानते, तो बता दें कि यह रीयल-टाइम में गड़बड़ी की रिपोर्टिंग करने वाला टूल है. यह फ़ील्ड डेटा के साथ-साथ, कॉन्टेक्स्ट और एआई की मदद से अहम जानकारी देता है. साथ ही, समस्या हल करने के सुझाव और आपकी नई रिलीज़ के बारे में साफ़ तौर पर सिग्नल देता है. इससे आपको समस्याओं को तेज़ी से ठीक करने में मदद मिलती है.
हमारे डेवलपर, Crashlytics को काफ़ी पसंद करते हैं. यह मोबाइल पर क्रैश की रिपोर्टिंग के लिए सबसे ज़्यादा इस्तेमाल किया जाने वाला टूल है. यह 3,70,000 से ज़्यादा नेटिव ऐप्लिकेशन को बेहतर बनाने में मदद करता है. इनमें 57% टॉप Android ऐप्लिकेशन शामिल हैं.
Crashlytics की मदद से, बग को ट्रैक करने का तरीका जानने के लिए, आइए एक उदाहरण देखें.
आज मैं आपको ChromaClip की कहानी सुनाना चाहता हूं.
यह एक वीडियो कलर पैलेट एक्सट्रैक्टर है, जिसे मैंने बनाया है. मैं आपको बताना चाहता हूं कि इसे डेवलप करते समय, मैंने रिस्पॉन्सिवनेस और मेमोरी से जुड़ी गड़बड़ियों को कैसे ठीक किया.
ChromaClip का आइडिया बहुत आसान है: उपयोगकर्ता कोई वीडियो चुनते हैं, जैसे कि सूर्यास्त की कोई खूबसूरत क्लिप या बेहतरीन कलर ग्रेडिंग वाला कोई सीन. इसके बाद, ऐप्लिकेशन फ़्रेम का विश्लेषण करके, पसंद के मुताबिक कलर पैलेट जनरेट करता है. उपयोगकर्ता इसका इस्तेमाल अपने डिज़ाइन, डिजिटल आर्ट, मूड बोर्ड वगैरह के लिए कर सकते हैं.
हमारी कहानी, मेरे 1.0 वर्शन के रिलीज़ होने से ठीक पहले शुरू होती है.
मैं यह पक्का करना चाहता था कि लॉन्च से पहले यह पूरी तरह से तैयार हो. इसलिए, मैं कुछ डिवाइसों पर स्थानीय तौर पर फ़ाइनल टेस्ट कर रहा था. इसके लिए, मैंने वीडियो चुना और कलर पैलेट निकालने के लिए बटन पर टैप किया. इसके बाद, मुझे यह गड़बड़ी मिली. यह हर Android डेवलपर का पसंदीदा डायलॉग है, है न?
ChromaClip काम नहीं कर रहा है.
इसलिए, इसे 'ऐप्लिकेशन काम नहीं कर रहा है' गड़बड़ी कहा जाता है. इसे एएनआर भी कहा जाता है.
पोलीना गोवोर्कोवा
तो घबराने के अलावा, हम इस स्थिति में क्या करें?
सबसे पहले, हम Android Studio खोलते हैं और Logcat की जांच करते हैं. ऐसा इसलिए, क्योंकि टेस्ट फ़ोन सीधे लैपटॉप से प्लग इन है.
हमें मुख्य थ्रेड के लिए स्टैक ट्रेस दिखता है.
14:32:15.105 1205-1250/system_process E/ActivityManager: ANR in com.chromaclip.app (com.chromaclip.app/.MainActivity)
PID: 14205
Reason: Input dispatching timed out
Dumped ANR trace to /data/anr/traces.txt
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72a8c3d0 self=0x759fb92000
| sysTid=14205 nice=-10 cgrp=default sched=0/0 handle=0x7623910548
| state=S schedstat=( 1450239120 4819203 1453 ) utm=132 stm=13 core=4 HZ=100
| stack=0x7fed2a0000-0x7fed2a2000 stackSize=8MB
at sun.misc.Unsafe.park(Native method)
- waiting on an unknown object
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
at com.chromaclip.app.extractor.PaletteExtractor.extractColors(PaletteExtractor.kt:42)इससे हमें असल में क्या पता चलता है?
सबसे पहले, सबसे ऊपर हमें पुष्टि मिलती है कि यह MainActivity में चल रहा एएनआर है.
और क्या?
हमें इसकी वजह पता चल गई है. ऐसा इनपुट डिस्पैच करने की समयसीमा खत्म होने की वजह से हुआ है.
इसका मतलब है कि उपयोगकर्ता ने स्क्रीन पर टैप किया.
हां, हमने ऐसा किया.
हालांकि, मुख्य थ्रेड इतना व्यस्त था कि वह Android के पांच सेकंड के वॉचडॉग टाइम आउट के अंदर उस इवेंट को प्रोसेस नहीं कर सका.
इसके बाद, हमें यह भी दिखता है कि सिस्टम ने फ़ाइल को traces.txt में डंप किया है. इसके ठीक नीचे, Logcat हमारे मुख्य थ्रेड के लिए स्टैक ट्रेस को प्रिंट करता है.
ध्यान दें कि यहां स्टेट की वैल्यू S है.
हमारा मुख्य थ्रेड, सीपीयू का इस्तेमाल भी नहीं कर रहा है.
S का मतलब है, स्लीप मोड.
इंतज़ार के दौरान, हमारी मुख्य थ्रेड स्लीप मोड में क्यों है?
आइए, स्टैक फ़्रेम के बारे में ज़्यादा जानें.
ट्रेस के बीच में, हमें दिखता है कि हमारा मुख्य थ्रेड Unsafe.park में पार्क किया गया है. यह किसी अज्ञात ऑब्जेक्ट का इंतज़ार कर रहा है.
हमें यह क्यों नहीं पता कि यह किस चीज़ का इंतज़ार कर रहा है?
हालांकि, यह सच है कि कुछ एएनआर के लिए, जैसे कि जब किसी थ्रेड को क्लासिक Java सिंक्रनाइज़ किए गए लॉक से ब्लॉक किया जाता है, तो मुख्य थ्रेड स्टैक ट्रेस से हमें यह पता चलता है कि वह असल में किस चीज़ का इंतज़ार कर रहा है और लॉक को किसने होल्ड किया है.
हालांकि, जब हुड के तहत ज़्यादा लेवल के कॉनकरेंसी प्रिमिटिव का इस्तेमाल किया जाता है, तो Logcat हमें सिर्फ़ यह बताता है कि हम किसी अज्ञात ऑब्जेक्ट का इंतज़ार कर रहे हैं.
इसलिए, हमें नहीं पता कि हम किस ऑब्जेक्ट का इंतज़ार कर रहे हैं, किस बैकग्राउंड वर्कर थ्रेड का इंतज़ार कर रहे हैं या इसमें पांच सेकंड से ज़्यादा समय क्यों लग रहा है.
हम स्थानीय तौर पर टेस्टिंग कर रहे हैं. इसलिए, हम डिवाइस से traces.txt को पुल कर सकते हैं और दर्जनों स्लीपिंग थ्रेड को मैन्युअल तरीके से स्क्रोल कर सकते हैं. इसके बाद, गड़बड़ी का पता लगाया जा सकता है या एआई से पूछकर टोकन बर्न किए जा सकते हैं.
कुछ मामलों में, यह तरीका काम कर सकता है. इस मामले में भी यह तरीका काम करेगा.
हालांकि, मैं रॉ थ्रेड डंप को खुद नहीं पढ़ना चाहता. ईमानदारी से कहूं, तो यह 2019 का तरीका है. मुझे पता है कि प्रोडक्शन में जाने के बाद, यह तरीका बिलकुल भी काम नहीं करता.
हमारे पास उपयोगकर्ता के फ़ोन में मौजूद traces.txt फ़ाइल का सीधा ऐक्सेस नहीं है, है न?
इसलिए, अच्छी बात यह है कि हमने Firebase Crashlytics को इंटिग्रेट किया है.
क्योंकि इससे, एएनआर की वजह खोजने की ज़रूरत नहीं पड़ती और प्रोडक्शन के दौरान भी एएनआर की वजह का पता चल जाता है.
कैसे?
ऐसे में, Android का ApplicationExitInfo API काम आता है.
Android 11 से, जब भी सिस्टम किसी एएनआर को फ़्लैग करता है या आपके ऐप्लिकेशन की प्रोसेस को बंद करता है, तो ओएस, बाहर निकलने का ज़्यादा मेटाडेटा रिकॉर्ड करता है. इसमें क्रैश होने की सटीक वजह, मेमोरी की स्थिति, और उस समय आपकी प्रोसेस में चल रहे हर थ्रेड के लिए पूरा सिस्टम ट्रेस शामिल होता है.
सबसे अच्छी बात यह है कि आपको कोई अतिरिक्त प्लग इन इंस्टॉल करने या कोड की एक लाइन लिखने की ज़रूरत नहीं है.
अगर आपके पास Crashlytics SDK टूल का 18.3.5 या इसके बाद का वर्शन है, तो यह सुविधा अपने-आप काम करती है.
इसलिए, जैसे ही आपका ऐप्लिकेशन रीस्टार्ट होता है, Crashlytics SDK अपने-आप ApplicationExitInfo से क्वेरी करता है. साथ ही, डिवाइस टेलीमेट्री के साथ-साथ पूरे मल्टीथ्रेडेड डंप को कैप्चर करता है और उसे Crashlytics के बैक एंड पर भेजता है.
चलिए, Crashlytics कंसोल खोलते हैं और देखते हैं कि हमें क्या जानकारी मिलती है.
यह Firebase Crashlytics कंसोल में, एएनआर से जुड़ी समस्या की जानकारी वाला व्यू है. इसमें हमें स्टैक ट्रेस टैब के तहत, DefaultDispatcher वर्कर थ्रेड दिख रहा है.
DefaultDispatcher-worker-1 (Root blocking)
dalvik.system.VMRuntime.allocateNode (Native method)
android.graphics.Bitmap.createBitmap (Bitmap.java:1124)
android.graphics.Bitmap.createBitmap (Bitmap.java:1091)
com.chromaclip.app.extractor.FrameDecoder.getUncompressedFrame (FrameDecoder.kt:112)
com.chromaclip.app.extractor.PaletteExtractor$processVideo$2.invokeSuspend (PaletteExtractor.kt:65)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
java.lang.Thread.run (Thread.java:923)यह वही थ्रेड है जिसे Crashlytics ने सबसे ऊपर दिखाया है. यह हमारे मुख्य थ्रेड के लिए रुकावट है. Crashlytics इसे 'रूट ब्लॉकिंग' बैज के साथ मार्क करता है.
बैज पर मौजूद टूलटिप से पता चलता है कि यह वर्कर थ्रेड, उस थ्रेड को ब्लॉक करती है जिसकी वजह से ANR गड़बड़ी हुई.
इसके अलावा, Crashlytics में अन्य समस्याओं के लिए भी स्मार्ट बैज मौजूद हैं.
उदाहरण के लिए, अगर कोई थ्रेड किसी बड़ी फ़ाइल को पढ़ने या डेटाबेस क्वेरी को बहुत धीरे-धीरे प्रोसेस करने में अटक गया है और इसकी वजह से एएनआर ट्रिगर हुआ है, तो उस थ्रेड को I/O रूट ब्लॉकिंग के तौर पर टैग किया जाएगा.
अगर दो थ्रेड एक-दूसरे का इंतज़ार कर रहे हैं, तो Crashlytics उन्हें डेडलॉक के तौर पर टैग कर देगा.
अब रूट ब्लॉकिंग थ्रेड में मौजूद स्टैक फ़्रेम पर नज़र डालते हैं.
इसलिए, हमारे पास PaletteExtractor processVideo है, जो FrameDecoder getUncompressedFrame को कॉल कर रहा है. इसे नीले रंग में हाइलाइट किया गया है. यह createBitmap के अंदर पूरी तरह से अटक गया है. यह सिर्फ़ एक फ़्रेम के लिए मेमोरी असाइन करने की कोशिश कर रहा है.
हमें पता है कि यह किसी दूसरे लॉक का इंतज़ार नहीं कर रहा है और यह नेटवर्क कॉल में नहीं है. इसलिए, हमें सबसे ज़्यादा शक मेमोरी पर है. हम Crashlytics कंसोल में, डिवाइस की खाली मेमोरी की जांच करके, इसकी पुष्टि तुरंत कर सकते हैं.
Crashlytics, इस गड़बड़ी को दो जगहों पर दिखाता है.
यह जानकारी, किसी खास इवेंट के डेटा टैब में, स्टैक ट्रेस टैब के ठीक बगल में मौजूद होती है. साथ ही, यह सबसे ऊपर दाईं ओर भी दिखती है. इसमें सभी इवेंट का औसत दिखाया जाता है.
दोनों ही मामलों में, ऐसा लगता है कि कुछ भी नहीं है. 100 केबी भी नहीं.
तो चलिए, इस बारे में सोचते हैं.
यहां ऐसा इसलिए हुआ, क्योंकि प्रोसेस के लिए मेमोरी कम पड़ गई थी. Android रनटाइम ने अपना पूरा समय, गारबेज कलेक्शन को तेज़ी से चलाने में बिताया. साथ ही, अगले बिटमैप के लिए ज़रूरी जगह ढूंढने की कोशिश की, लेकिन उसे जगह नहीं मिली.
वर्कर थ्रेड को फ़्रीज़ करना और यूज़र इंटरफ़ेस (यूआई) को पांच सेकंड से ज़्यादा समय तक रोकना.
इसलिए, यहां आपके लिए एक सलाह दी गई है.
जब भी आपको इस तरह की कम रैम के साथ एएनआर मिलता है, तो यह इस बात का सबूत होता है कि एएनआर असल में मेमोरी लीक या ब्लोट की समस्या है.
अब सवाल यह है कि क्या हमारा ऐप्लिकेशन इस समस्या का शिकार है या यह भी मेमोरी का ज़्यादा इस्तेमाल कर रहा है?
इसलिए, चलिए अपने प्रोजेक्ट की समस्याओं की सूची पर वापस चलते हैं और देखते हैं कि क्या कोई और समस्या भी काम की है.
पोलीना गोवोर्कोवा
और यहां देखें.
सबसे नीचे, हमें समस्याओं की सूची दिखती है. साथ ही, आठ इवेंट वाले एएनआर के ठीक ऊपर, हमें OutOfMemory अपवाद मिला है.
इसलिए, हो सकता है कि हम ही अपराधी हों.
यह एक बहुत बड़ा सुराग है.
Android पर, Java OutOfMemoryError या ओओएम का मतलब सिर्फ़ यह नहीं होता कि फ़ोन में रैम कम थी.
इसका मतलब है कि हमारे ऐप्लिकेशन की प्रोसेस, Android रनटाइम Java हीप की सीमा से ज़्यादा हो गई.
इसलिए, इस समय मेरा मानना है कि एएनआर और ओओएम, असल में एक ही सिक्के के दो पहलू हैं.
टेस्ट रन के दौरान, ChromaClip या तो गार्बेज कलेक्शन में लंबे समय तक फंसा रहता था, जिससे एएनआर ट्रिगर होता था या हीप पूरी तरह से खत्म हो जाता था. इससे ओओएम की समस्या होती थी.
पक्का करने के लिए, OutOfMemory अपवाद की जांच करें.
स्टैक ट्रेस से शुरुआत करना.
java.lang.OutOfMemoryError: Failed to allocate a 256 byte allocation
at android.graphics.Bitmap.createBitmap
at android.view.LayoutInflater.inflate(LayoutInflater.java:532)
at com.chromaclip.app.SettingsActivity.onCreate(SettingsActivity.kt:21)एक सेकंड इंतज़ार करें.
Bitmap.createBitmap, यह तो जाना-पहचाना है.
क्या हमने इसे पहले नहीं देखा था?
हालांकि, इस बार यह देखें कि इसे कहां से कॉल किया गया है.
सेटिंग ऐक्टिविटी खोलते समय, 256 बाइट असाइन नहीं किए जा सके.
हम फिर से उसी जाल में फंस रहे हैं.
अगर आपका ऐप्लिकेशन क्रैश हो जाता है, क्योंकि वह सेटिंग के छोटे आइकॉन को बड़ा नहीं कर सकता, तो सेटिंग गतिविधि को ज़िम्मेदार नहीं ठहराया जाएगा.
यह उस कॉलर की बदकिस्मती है जिसने मेमोरी का एक ड्रॉप मांगा, जबकि कोई मेमोरी नहीं थी.
इसलिए, असल वजह का पता लगाने के लिए, हमें ज़ूम आउट करके यह देखना होगा कि क्रैश होने के समय मेमोरी में क्या-क्या मौजूद था.
दूसरे शब्दों में, हमें Java के हीप डंप की ज़रूरत होती है.
अगर आपको ओओएम को ट्रिगर करने का तरीका पता है, तो Android Studio के मेमोरी प्रोफ़ाइलर की मदद से, Java हीप डंप को मैन्युअल तरीके से कैप्चर किया जा सकता है.
हालांकि, जब टेस्ट रन के दौरान या असल दुनिया में, ओओएम की समस्या अचानक होती है, तो यह तरीका काम नहीं करता.
पोलीना गोवोर्कोवा
इसलिए, Firebase Crashlytics के लिए यह एक और शानदार मौका है.
ऐप्लिकेशन के बंद होने की जानकारी के अलावा, Crashlytics अब Android के ProfilingManager API के साथ इंटिग्रेट हो गया है.
Android 17 में ProfilingManager API, सिस्टम से ट्रिगर होने वाली मेमोरी प्रोफ़ाइलिंग की सुविधा के साथ आता है. हमें यहां इसी की ज़रूरत है.
इसका मतलब है कि जब आपके ऐप्लिकेशन में OutOfMemory गड़बड़ी होती है या सिस्टम मेमोरी की सीमाएं पार हो जाती हैं, तो ओएस बैकग्राउंड में हीप स्नैपशॉट कैप्चर कर लेता है. इसके बाद, Crashlytics में ट्रिगर किया गया कलेक्शन बिना किसी शुल्क के उपलब्ध हो जाता है. हालांकि, इसके लिए ज़रूरी है कि आपके पास Crashlytics SDK टूल का 20.1.0 या इसके बाद का वर्शन हो.
इसके बाद, Crashlytics आपके ओओएम और मेमोरी लिमिटर की वजह से बंद हुए ऐप्लिकेशन को डैशबोर्ड में अपने-आप ग्रुप कर देगा. हम इनके बारे में बाद में बात करेंगे.
Firebase कंसोल या सीएलआई के ज़रिए हीप डंप कलेक्शन में शामिल होने पर, Crashlytics कैप्चर किए गए हीप स्नैपशॉट को अपने-आप इकट्ठा करना शुरू कर देगा. साथ ही, उन्हें आपके लिए डी-ऑब्फ़स्केट करेगा. इससे आपको क्रैश होने के समय, अपने ऐप्लिकेशन के Java हीप में मौजूद हर चीज़ की पूरी जानकारी मिलेगी.
हीप कैप्चर होने के बाद, Crashlytics कंसोल में ChromaClip ओओएम कुछ ऐसा दिखता है.
सबसे ऊपर, आपको इस बारे में जानकारी दिखेगी कि Java ने हीप का पूरा इस्तेमाल क्यों किया और ऐसा क्यों हुआ.
इसके बाद, आपको समस्या हल करने के लिए काम के सुझाव और बाहरी संसाधनों के लिंक भी दिखेंगे.
स्टैक ट्रेस टैब में, गुमराह करने वाली सेटिंग गतिविधि के ठीक ऊपर, Crashlytics असल में फ़्लैग करता है कि ओओएम स्टैक ट्रेस डिटरमिनिस्टिक नहीं हैं. इससे लोगों को स्टैक ट्रेस देखने में समय बर्बाद नहीं करना पड़ता, जैसा कि हमने अभी किया.
इसके बाद, Crashlytics उस सेशन के लिए, डिकोड की गई Perfetto फ़ाइल और हीप डंप को अटैच करता है.
यहां से, हम फ़ाइल को डाउनलोड कर सकते हैं.
हम Google Cloud Storage में मौजूद सभी डंप ब्राउज़ कर सकते हैं या उन्हें Perfetto यूज़र इंटरफ़ेस (यूआई) में खोल सकते हैं.
इसलिए, Perfetto यूज़र इंटरफ़ेस (यूआई) में 'विश्लेषण करें' पर क्लिक करने से, Perfetto हीप डंप एक्सप्लोरर खुलता है.
अब, अगर आपने इसे पहले ही देख लिया है, तो हाथ ऊपर करें.
ठीक है, कुछ लोगों ने हाथ उठाया है.
मुझे यह व्यू बहुत डरावना लगता है. हालांकि, इस मामले में समस्या काफ़ी आसान है.
लगभग पूरे Java हीप में फैले बड़े बार के सेट को देखें.
इसलिए, ज़्यादातर मामलों में यह पेयर ऑब्जेक्ट का एक कलेक्शन होता है. इसमें हर ऑब्जेक्ट, एक रंग और कुछ स्ट्रिंग इंस्टेंस को रैप करता है.
ओह!
मुझे लगता है कि मुझे पता है कि क्या हो रहा है.
हमारा कोड, हर फ़्रेम के हर पिक्सल के लिए एक रंग और कुछ ज़रूरी डेटा असाइन करता है. साथ ही, लाखों पिक्सल को एक सूची में रखता है.
इसलिए, इसमें कोई हैरानी की बात नहीं है कि सेटिंग ऐक्टिविटी, 256 बाइट को ऐलोकेट नहीं कर सकी.
चलो, कम से कम एक रहस्य तो सुलझा.
चलिए, इसे ठीक करते हैं.
इस समय, हमारे पास कुछ विकल्प हैं.
Perfetto ने हमें PaletteExtractor के बारे में पहले ही बता दिया था. इसलिए, हम अपना IDE खोलकर, लूप को मैन्युअल तरीके से फिर से लिख सकते थे या एआई कोडिंग एजेंट का इस्तेमाल कर सकते थे.
इसके अलावा, मैन्युअल तरीके से हीप की जांच करने की प्रोसेस को पूरी तरह से बायपास किया जा सकता है. साथ ही, Android का सीएलआई का इस्तेमाल किया जा सकता है.
# 1. Install Android CLI & Perfetto skills
$ android skills add android-cli perfetto
# 2. List recent heap dumps from Crashlytics
$ android signals heaps list --kind oom --timeframe=90d
# 3. Download a heap dump as a Perfetto trace
$ android signals heaps download <heap-dump-id> \
--output ./heaps
# 4. Query top retained objects in the heap dump
$ trace_processor query ./heaps/oom.perfetto-trace ...
# 5. Fetch matching Crashlytics events
$ android signals heaps describe <heap-dump-id>इसलिए, Android का सीएलआई और Perfetto स्किल इंस्टॉल करने पर, आपको Crashlytics के डीऑब्फ़स्केट किए गए हीप डंप की सूची देखने और उन्हें डाउनलोड करने की सुविधा मिलेगी. साथ ही, trace_processor की मदद से, इन हीप डंप से सबसे ज़्यादा समय तक सेव किए गए ऑब्जेक्ट के बारे में क्वेरी की जा सकेगी. इसके अलावा, Crashlytics से जुड़ी समस्या का मैच होने वाला डेटा फ़ेच किया जा सकेगा. जैसे, कस्टम कुंजियां, लॉग, ब्रेडक्रंब, असोसिएट किए गए रोलआउट वगैरह. यह सब आपके टर्मिनल से किया जा सकेगा.
इससे भी बेहतर तरीका यह है कि स्लाइड में सबसे नीचे देखें.
इस निर्देश को खुद न चलाएं.
इसके बाद, अपने पसंदीदा एआई कोडिंग एजेंट से नैचुरल लैंग्वेज में कहें कि वह Android कमांड का इस्तेमाल करके, ओओएम हीप डंप की जांच करे और समस्या को ठीक करे.
# Or ask your favorite LLM in natural language
> Using `android` command, inspect the latest heap dump
for my OOM issue and propose a fix
● Ran android signals heaps list · download · describe
● Analyzed ./heaps/oom.perfetto-trace (perfetto)
● Updated PaletteExtractor.kt (+6 -4 lines)साथ ही, इसके पास आपके लिए पूरा फ़्लो पूरा करने के लिए ज़रूरी कॉन्टेक्स्ट होगा.
इसलिए, ओओएम की गड़बड़ियों की सूची बनाना, ट्रेस डाउनलोड करना, विश्लेषण करना, और सीधे आपके प्रोजेक्ट में कोड पैच जनरेट करना.
PaletteExtractor फ़ाइल में मौजूद इस पैच को देखें.
// PaletteExtractor.kt
fun extractPalette(framePixels: IntArray): SparseIntArray {
- val pixelColors = mutableListOf<Pair<Color, String>>() // 8.3M objects per 4K frame!
- for (px in framePixels) {
- pixelColors.add(Color.valueOf(px) to "#%06X".format(px))
- }
+ val colorHistogram = SparseIntArray() // Downsampled & packed ARGB ints!
+ for (i in framePixels.indices step 16) {
+ val argb = framePixels[i]
+ colorHistogram.put(argb, colorHistogram.get(argb) + 1)
+ }
+ return colorHistogram
}लाखों ऑब्जेक्ट असाइन करने के बजाय, अब हम फ़्रेम को डाउनसैंपल करते हैं और रॉ इंटीजर को सीधे गिनते हैं.
इसलिए, चलिए समस्या को ठीक करते हैं, समस्याओं को बंद करते हैं, अपने वर्शन को बंप करते हैं, और टेस्ट करना जारी रखते हैं.
उम्मीद है कि इससे न सिर्फ़ मेमोरी खत्म होने की गड़बड़ी ठीक हो जाएगी, बल्कि गार्बेज कलेक्शन में थ्रैशिंग न होने की वजह से, एएनआर की समस्या भी दूर हो जाएगी.
इसलिए, हमने अपने टेस्ट डिवाइसों पर पैच बिल्ड को डिप्लॉय किया, एक नई क्लिप चुनी, और पैलेट निकालना शुरू किया.
इसके बाद, यूज़र इंटरफ़ेस (यूआई) फिर से फ़्रीज़ हो जाता है.
ANR वाली एक और गड़बड़ी.
दिलचस्प बात यह है कि ज़्यादा टेस्टिंग करने पर, हमें पता चला कि कुछ डिवाइसों में अब भी एएनआर की समस्या आ रही है. हालांकि, अन्य डिवाइसों में ऐप्लिकेशन अब अचानक क्रैश हो जाता है. ऐसा अक्सर होता है और ओओएम की तुलना में ज़्यादा बार होता है.
हालांकि, हमने ओओएम की समस्या को ठीक कर दिया है. ऐसे में, अब क्या हो रहा है?
पोलीना गोवोर्कोवा
चलिए, Crashlytics पर वापस चलते हैं.
सबसे पहले अच्छी खबर.
समस्याओं की हमारी सूची देखें.
हमारी उम्मीद के मुताबिक, आउट-ऑफ़-मेमोरी की समस्या पूरी तरह से खत्म हो गई है.
बुरी खबर क्या है?
अब ChromaClip ज़्यादा समय तक ज़िंदा रह सकता है. इसलिए, उसके पास यह जानने का समय है कि नीचे और क्या छिपा है.
इसलिए, एएनआर की समस्या बनी रही.
अब हमें सबसे नीचे दो नेटिव क्रैश दिख रहे हैं. अच्छी बात यह है कि इनमें सिर्फ़ कुछ इवेंट हैं.
सबसे ऊपर, 11 इवेंट के साथ, हमारे पास मेमोरी लिमिटर किल है. शुक्र है कि इसे पहले से ही हीप डंप के तौर पर बैज किया गया है.
एक मिनट।
मेमोरी लिमिटर किल क्या है?
क्या आप में से कोई ऐसा है जिसने इसके बारे में सुना है?
ठीक है।
कुछ खास हाथ.
आइए, साथ मिलकर सीखें.
अगर आपने पहले कभी मेमोरी लिमिटर को किसी ऐप्लिकेशन को बंद करते हुए नहीं देखा है, तो इसकी वजह यह है कि यह सुविधा Android 17 पर बिलकुल नई है.
Java ओओएम सिर्फ़ तब ट्रिगर होता है, जब मैनेज की गई Java हीप की सीमा पार हो जाती है. हालांकि, जब आपके ऐप्लिकेशन के कुल मेमोरी फ़ुटप्रिंट में नेटिव रैम भी शामिल होती है, जैसे कि ग्राफ़िक बफ़र और C++ ऐलोकेशन, तो पहले हर ऐप्लिकेशन के लिए कोई सीमा नहीं होती थी.
इसलिए, हमारे पास लो मेमोरी किलर था, जो ग्लोबल था.
यह तब काम करता था, जब कोई ऐप्लिकेशन सिस्टम की रैम को पूरी तरह से इस्तेमाल कर लेता था और फ़ोन हैंग हो जाता था.
अब Android 17 से, मेमोरी लिमिटर हर ऐप्लिकेशन के मेमोरी फ़ुटप्रिंट को सीमित करता है.
इसलिए, जब कोई प्रोसेस बहुत ज़्यादा रैम का इस्तेमाल करती है, तो ओएस उस ऐप्लिकेशन को बंद कर देता है.
Crashlytics, इन सभी जानकारी को कैप्चर करता है और डैशबोर्ड में, बंद होने की वजहों को एक साथ ग्रुप करता है.
इसलिए, Crashlytics में मेमोरी लिमिटर किल पर क्लिक करने पर, आपको ओओएम की तरह ही यह जानकारी मिलती है कि ओएस ने दखल क्यों दिया. इसलिए, आपको मेरी कही गई बातों को याद रखने की ज़रूरत नहीं है.
आपको इस समस्या को हल करने के बारे में सलाह भी मिलती है. साथ ही, Android मेमोरी एनफ़ोर्समेंट के दस्तावेज़ों के लिंक भी मिलते हैं.
ओओएम की तरह ही, स्टैक ट्रेस किसी ऐसे फ़्रेम को ज़िम्मेदार ठहराता है जिसकी कोई गलती नहीं होती. ऐसा तब होता है, जब ओएस प्लग को खींचता है और Crashlytics डैशबोर्ड उसे हाइलाइट करता है.
अब मेमोरी लिमिटर की वजह से, मुख्य रूप से नेटिव कोड में प्रोसेस बंद होती हैं. ऐसा इसलिए होता है, क्योंकि नेटिव एनवायरमेंट में वह लेयर मौजूद नहीं होती जो मेमोरी की सुरक्षा और ऐलोकेशन को मैनेज करती है.
ऐसा न होने पर, यह ओओएम होता.
इसलिए, हमारा पहला सवाल यह है कि क्या हमारे Kotlin ऑब्जेक्ट, नेटिव-बैकड ऐलोकेशन को होल्ड कर रहे हैं?
या फिर यह कोई रॉ C++ कोड है जो खुद ही लीक हो रहा है?
चलिए, Perfetto यूज़र इंटरफ़ेस (यूआई) में 'विश्लेषण करें' पर क्लिक करके, इस बारे में जानते हैं.
Perfetto हीप डंप एक्सप्लोरर में, खास जानकारी वाला टैब देखें.
सामान्य जानकारी में, हमें पता चलता है कि बिना सोर्स फ़ाइल वाली आरएसएस मेमोरी और स्वैप स्पेस से पता चलता है कि प्रोसेस की कुल मेमोरी 4 जीबी तक पहुंच गई है.
ठीक है।
हालांकि, इसके नीचे, हमें दिखता है कि हीप में सेव किए गए बाइट के तहत, हमारा पूरा मैनेज किया गया हीप सिर्फ़ 42 एमबी का है. इसमें बिटमैप जैसे Kotlin ऑब्जेक्ट से जुड़ी नेटिव मेमोरी भी शामिल है.
ठीक है।
इसलिए, हमने 10 सेकंड में Kotlin और Java कोड को हटा दिया.
इसलिए, हो सकता है कि नेटिव C++ लेयर से मेमोरी लीक हो रही हो.
ChromaClip में फ़्रेम प्रोसेसिंग के लिए C++ की एक पतली लेयर होती है. इसलिए, हमें ज़्यादा दूर जाने की ज़रूरत नहीं है.
हमारे कोडिंग एजेंट या Android Studio के नेटिव प्रोफ़ाइलर को NativePaletteEngine.cpp processFrame पर ले जाने पर, गड़बड़ी वहीं दिखती है.
// NativePaletteEngine.cpp
void NativePaletteEngine::processFrame(const uint8_t* rgba, size_t size) {
uint8_t* scratch = new uint8_t[size]; // Temporary frame buffer
transformColorSpace(rgba, scratch, size);
// BUG: Allocated every frame, but never freed!
+ delete[] scratch; // Free native buffer when done
}इसलिए, हर वीडियो फ़्रेम पर, हमारा C++ कोड एक अस्थायी स्क्रैच बफ़र असाइन करता है. अगर आप इसे रिलीज़ नहीं करते हैं, तो यह अस्थायी नहीं रहता. यह एक नौसिखिया गलती है.
इसलिए, अब तथ्यों के बीच संबंध खोजने का समय आ गया है.
क्या हुआ?
Java लीक को ठीक करने के बाद, C++ लीक को इकट्ठा होने और तेज़ी से बढ़ने का समय मिल जाएगा.
Android 17 पर, मेमोरी लिमिटर ने इसे डिटेक्ट किया और ऐप्लिकेशन को बंद कर दिया.
हालांकि, पुराने डिवाइसों पर मेमोरी लिमिटर की सुविधा नहीं थी. इसलिए, वहां यह समस्या ANR के तौर पर दिखती थी.
इसलिए, आखिरकार यह सब समझ में आता है.
चलिए, यहां delete[] scratch जोड़ते हैं, अपने वर्शन को अपडेट करते हैं, और अपडेट को शिप करते हैं.
मेमोरी लिमिटर की वजह से होने वाली क्रैश और एएनआर की समस्याएं अब खत्म हो गई हैं. इसलिए, अब हम 1.0 वर्शन को रिलीज़ कर सकते हैं.
यह तो बहुत अच्छी बात है.
ChromaClip अब तेज़ी से और बिना किसी रुकावट के काम करता है. इस वजह से, हमारा ऐप्लिकेशन अब काफ़ी लोकप्रिय हो गया है.
हर दिन हज़ारों लोग वीडियो अपलोड कर रहे हैं और रीमिक्स बना रहे हैं.
हालांकि, इतने सारे ट्रैफ़िक के साथ, क्या आपको डैशबोर्ड के सबसे नीचे मौजूद, नेटिव क्रैश की वे रहस्यमयी गड़बड़ियां याद हैं जो हमने पहले देखी थीं?
वैसे, वे भी बेकाबू हो रहे हैं.
अचानक, उनकी संख्या बहुत ज़्यादा हो गई है.
हालांकि, हमारा काम अभी खत्म नहीं हुआ है.
पोलीना गोवोर्कोवा
इसलिए, Crashlytics में उस नेटिव क्रैश को खोलकर, स्टैक ट्रेस देखें.
हमारे ऐप्लिकेशन में कोड की एक भी लाइन नहीं है.
मेमोरी खाली करते समय, libc.so में RenderThread क्रैश हो रहा है.
एक और सलाह: जब कोई सिस्टम थ्रेड, मेमोरी ऐलोकेटर के अंदर क्रैश हो जाता है और उसमें आपके ऐप्लिकेशन का कोई कोड नहीं होता, तो यह नेटिव मेमोरी के खराब होने का एक अहम संकेत होता है.
इसलिए, हमारे C++ कोड ने बफ़र को पहले ही खराब कर दिया या खाली कर दिया. इसके बाद, रेंडर थ्रेड ने उसे ट्रिप कर दिया.
तो फिर, भ्रष्टाचार होने के समय ही असली अपराधी को कैसे पकड़ा जाए?
Android हमें दो बेहतरीन टूल देता है. Crashlytics इन दोनों को सपोर्ट करता है.
पहला, GWP-ASan. यह एक लाइटवेट सेफ्टी नेट है, जिसे खास तौर पर प्रोडक्शन के लिए बनाया गया है.
यह आपके नेटिव हीप ऐलोकेशन के एक छोटे से हिस्से का सैंपल लेता है और उन्हें गार्ड पेजों से घेरता है, ताकि सीपीयू और मेमोरी ओवरहेड करीब-करीब शून्य हो.
जब किसी बग की वजह से, सैंपल के तौर पर तय की गई मेमोरी में से खाली की गई या तय सीमा से बाहर की मेमोरी का इस्तेमाल किया जाता है, तो GWP-ASan उसे तुरंत पकड़ लेता है. ऐसा तब होता है, जब उपयोगकर्ता के हज़ारों सेशन चल रहे हों.
हालांकि, GWP-ASan की प्रकृति संभाव्यता पर आधारित होती है. इसलिए, जब चीज़ों को स्थानीय तौर पर टेस्ट किया जाता है, तो रैंडम सैंपल को हिट करने के लिए, टेस्ट फ़ोन को 16 या उससे ज़्यादा घंटों तक नहीं चलाया जाता.
तो आपको क्या मिलता है?
लोकल डीबगिंग के लिए, ARM MTE या Memory Tagging Extension का इस्तेमाल किया जा सकता है.
एमटीई, हर ऐलोकेशन की जांच करने के लिए हार्डवेयर पॉइंटर और मेमोरी टैग का इस्तेमाल करता है. यह सुविधा, ARM v9 डिवाइसों पर उपलब्ध है. जैसे, Pixel 8 और इसके बाद के वर्शन वाले डिवाइस.
इसलिए, इसे प्रोडक्शन में GWP-ASan के साथ जोड़ें. इससे आपको दोनों का फ़ायदा मिलेगा.
जैसा कि आपको उम्मीद होगी, जब इनमें से कोई भी टूल गड़बड़ी का पता लगाता है, तो Crashlytics उस समस्या को अपने-आप बैज कर देता है. साथ ही, आपको GWP-ASan या MTE के हिसाब से फ़िल्टर करने की सुविधा देता है. इसके अलावा, डैशबोर्ड में गड़बड़ी की सटीक वजह बताने वाले जानकारी कार्ड को पिन करता है.
इसलिए, देखें कि जब GWP-ASan क्रैश का पता लगाता है, तो Crashlytics कंसोल में क्या होता है.
रेगुलर स्टैक ट्रेस टैब के बगल में, मेमोरी स्टैक ट्रेस मौजूद होता है.
सबसे ऊपर, हमें मेमोरी करप्शन की असल वजह दिखती है, जो कि यूज़-आफ़्टर-फ़्री है.
libc.so में रेंडर थ्रेड के क्रैश होने की जानकारी दिखाने के बजाय, GWP-ASan अब हमें effect_pipeline.cc में उस बफ़र की पूरी जानकारी देता है.
इसलिए, Allocated By में हमें दिखता है कि बफ़र कहां से जनरेट हुआ.
'डीऐलोकेटेड बाय' के ठीक नीचे, हमें वह गड़बड़ी दिखती है जिसकी वजह से रेंडर थ्रेड से मेमोरी को खाली किया गया.
यह हमारी इफ़ेक्ट पाइपलाइन की 208वीं लाइन में छिपा हुआ है.
इसलिए, हम EffectPipeline में अपना नेटिव कोड खोलते हैं और हमें गड़बड़ी दिखती है.
// effect_pipeline.cc
void EffectPipeline::onFilterSwitched(FilterPresetId preset) {
// BUG: Freed on worker thread while RenderThread is still reading it!
+ std::lock_guard<std::mutex> lock(lut_mutex_);
releaseLutBuffer(); // Frees old color-grading LUT
loadFilterPreset(preset); // Allocates new color-grading LUT
}जब भी कोई उपयोगकर्ता बैकग्राउंड थ्रेड में कलर फ़िल्टर को स्विच करता है, तो हम पुराने बफ़र को खाली कर देते हैं. हालांकि, RenderThread अब भी उसे पढ़ रहा होता है.
ठीक है, तो हम बफ़र स्वैप के आस-पास म्यूटेक्स लॉक जोड़ते हैं, अपडेट शिप करते हैं, और आखिर में GWP-ASan की दोनों रिपोर्ट और उन सभी रहस्यमयी क्रैश को लगभग रातों-रात गायब होते हुए देखते हैं.
साथ ही, क्रैश-फ़्री उपयोगकर्ताओं की मेट्रिक 100% तक पहुंच गई.
वाह!
याद है, बातचीत की शुरुआत में हमने खुद से पूछा था: "क्या मेरा काम हो गया?"
इसके बाद, इस गेम में रिस्पॉन्सिवनेस और मेमोरी से जुड़े हर बॉस से मुकाबला करना होगा.
अब एएनआर, आउट ऑफ़ मेमोरी गड़बड़ियां, मेमोरी लिमिटर की वजह से ऐप्लिकेशन बंद होने की समस्या, और नेटिव मेमोरी करप्शन की वजह से ऐप्लिकेशन क्रैश होने की समस्या हल हो गई है.
अगर स्टैक ट्रेस में गड़बड़ी की जानकारी गलत है और गड़बड़ी C++ में मौजूद थ्रेड में कहीं छिपी है, तो भी Android और Firebase Crashlytics आपको गड़बड़ी का पता लगाने और उसे ठीक करने के लिए टूल उपलब्ध कराते हैं.
पोलीना गोवोर्कोवा
चलिए, एक बार फिर से उन सुविधाओं पर नज़र डालते हैं जिनका इस्तेमाल, लैपटॉप पर वापस आने के बाद किया जा सकता है: ApplicationExitInfo की मदद से, एएनआर थ्रेड की अहम जानकारी और अन्य काम का मेटाडेटा; ProfilingManager की मदद से, ओओएम और मेमोरी लिमिटर किल ट्रिगर का अपने-आप इकट्ठा होना; और नेटिव मेमोरी करप्शन के लिए, GWP-ASan और Arm MTE का पूरा सपोर्ट.
साथ ही, Android 17 या इसके बाद के वर्शन पर, फ़ील्ड हीप डंप की सुविधा को निजी झलक के तौर पर लॉन्च किया जा रहा है.
अगर आपको Firebase Crashlytics पर, हीप डंप अपने-आप इकट्ठा होने की सुविधा को सबसे पहले आज़माना है और Perfetto और Android का सीएलआई की मदद से उनका विश्लेषण करना है, तो कृपया स्लाइड की एक फ़ोटो लें और लिंक पर जाकर साइन अप करें.
अब हमने ज़्यादातर विषयों पर बात कर ली है. इसलिए, आज हमारे साथ जुड़ने और अपना समय देने के लिए धन्यवाद.
हम उम्मीद करते हैं कि आपकी मुख्य थ्रेड ब्लॉक न हो, हीप मेमोरी साफ़ रहे, और आपकी अगली रिलीज़ अब तक की सबसे स्टेबल रिलीज़ हो.
इसलिए, डीबग करने का आनंद लें.
एक बार फिर से आपका बहुत-बहुत धन्यवाद.
java.lang.NullPointerException
at MainActivity.onCreate(MainActivity.kt:42)
at android.app.Activity.performCreate
at android.os.Handler.dispatchMessage
at android.os.Looper.loopOnce
at android.app.ActivityThread.main"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1
| state=S schedstat=( 1450239 48192 145 )
at DataManager.getData(DataManager.kt:25)
- waiting to lock <0x0a1b> held by thread 12
at MainActivity.onResume(MainActivity.kt:50)
at android.app.Activity.performResume
at android.os.Handler.dispatchMessage
at android.os.Looper.loopOnce
at android.app.ActivityThread.main14:32:15.105 1205-1250/system_process E/ActivityManager: ANR in com.chromaclip.app (com.chromaclip.app/.MainActivity)
PID: 14205
Reason: Input dispatching timed out
Dumped ANR trace to /data/anr/traces.txt
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72a8c3d0 self=0x759fb92000
| sysTid=14205 nice=-10 cgrp=default sched=0/0 handle=0x7623910548
| state=S schedstat=( 1450239120 4819203 1453 ) utm=132 stm=13 core=4 HZ=100
| stack=0x7fed2a0000-0x7fed2a2000 stackSize=8MB
at sun.misc.Unsafe.park(Native method)
- waiting on an unknown object
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
at com.chromaclip.app.extractor.PaletteExtractor.extractColors(PaletteExtractor.kt:42)DefaultDispatcher-worker-1 (Root blocking)
dalvik.system.VMRuntime.allocateNode (Native method)
android.graphics.Bitmap.createBitmap (Bitmap.java:1124)
android.graphics.Bitmap.createBitmap (Bitmap.java:1091)
com.chromaclip.app.extractor.FrameDecoder.getUncompressedFrame (FrameDecoder.kt:112)
com.chromaclip.app.extractor.PaletteExtractor$processVideo$2.invokeSuspend (PaletteExtractor.kt:65)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
java.lang.Thread.run (Thread.java:923)java.lang.OutOfMemoryError: Failed to allocate a 256 byte allocation
at android.graphics.Bitmap.createBitmap
at android.view.LayoutInflater.inflate(LayoutInflater.java:532)
at com.chromaclip.app.SettingsActivity.onCreate(SettingsActivity.kt:21)# 1. Install Android CLI & Perfetto skills
$ android skills add android-cli perfetto
# 2. List recent heap dumps from Crashlytics
$ android signals heaps list --kind oom --timeframe=90d
# 3. Download a heap dump as a Perfetto trace
$ android signals heaps download <heap-dump-id> \
--output ./heaps
# 4. Query top retained objects in the heap dump
$ trace_processor query ./heaps/oom.perfetto-trace ...
# 5. Fetch matching Crashlytics events
$ android signals heaps describe <heap-dump-id># Or ask your favorite LLM in natural language
> Using `android` command, inspect the latest heap dump
for my OOM issue and propose a fix
● Ran android signals heaps list · download · describe
● Analyzed ./heaps/oom.perfetto-trace (perfetto)
● Updated PaletteExtractor.kt (+6 -4 lines)// PaletteExtractor.kt
fun extractPalette(framePixels: IntArray): SparseIntArray {
- val pixelColors = mutableListOf<Pair<Color, String>>() // 8.3M objects per 4K frame!
- for (px in framePixels) {
- pixelColors.add(Color.valueOf(px) to "#%06X".format(px))
- }
+ val colorHistogram = SparseIntArray() // Downsampled & packed ARGB ints!
+ for (i in framePixels.indices step 16) {
+ val argb = framePixels[i]
+ colorHistogram.put(argb, colorHistogram.get(argb) + 1)
+ }
+ return colorHistogram
}// NativePaletteEngine.cpp
void NativePaletteEngine::processFrame(const uint8_t* rgba, size_t size) {
uint8_t* scratch = new uint8_t[size]; // Temporary frame buffer
transformColorSpace(rgba, scratch, size);
// BUG: Allocated every frame, but never freed!
+ delete[] scratch; // Free native buffer when done
}// effect_pipeline.cc
void EffectPipeline::onFilterSwitched(FilterPresetId preset) {
// BUG: Freed on worker thread while RenderThread is still reading it!
+ std::lock_guard<std::mutex> lock(lut_mutex_);
releaseLutBuffer(); // Frees old color-grading LUT
loadFilterPreset(preset); // Allocates new color-grading LUT
}