Firebase Crashlytics-এর সাহায্যে আপনার অ্যাপের মেমরি ফুটপ্রিন্টকে লিন-অ্যান্ড-মিন এবং নন-সেগফল্টি রাখুন। এই সেশনে, Firebase কনসোল এবং Android Studio-তে উপলভ্য মেমরি সংক্রান্ত সমস্যার সমাধান করার টুলের আমাদের সর্বাঙ্গীন স্যুট এক্সপ্লোর করা হয়েছে। আমরা প্রথমে ANR ইনসাইট ও GWP-ASan রিপোর্টের মাধ্যমে মেমরি সংক্রান্ত জটিল বাগ ডায়াগনসিস করার বিষয়ে আলোচনা করব। তারপর Out-Of-Memory (OOM) ও Memory Limiter Kill (MLK) ইভেন্টের জন্য আমাদের লেটেস্ট টুলিংয়ের বিষয়ে বিস্তারিত আলোচনা করব। এই টুলিং বিশেষভাবে ডিজাইন করা হয়েছে যাতে আপনি Android 17-এ চালু করা নতুন মেমরি লিমিট নেভিগেট ও অ্যাডাপ্ট করতে পারেন।
ApplicationExitInfo কোয়েরি করে যাতে মাল্টিথ্রেড ট্রেস দেখানো যায় এবং রুট ব্লকিং থ্রেড শনাক্ত করা যায়।ProfilingManager-এর সাথে ইন্টিগ্রেট করে ডি-অবফাসকেট করা হিপ স্ন্যাপশট সংগ্রহ করে যা Perfetto বা Android CLI-এর মাধ্যমে পরীক্ষা করা যেতে পারে।ট্রান্সস্ক্রিপ্ট করা
Polina Govorkova
হ্যালো, সবাইকে স্বাগত।
Firebase Crashlytics-এর মাধ্যমে মেমরি সংক্রান্ত সমস্যার সমাধানকরণে আপনাকে স্বাগত।
দাঁড়ান, কী কর্পোরেট টাইটেল।
চলুন, একটু অন্যরকম কিছু করা যাক।
Firebase Crashlytics-এর মাধ্যমে অ্যাপকে হালকা ও কার্যকর রাখা।
ওহ, এটা আরও ভালো।
আমার নাম পলিনা, আমি Firebase Crashlytics টিমের একজন সিনিয়র সফ্টওয়্যার ডেভেলপার এবং আজকের প্রেজেন্টার।
ডেভেলপমেন্ট সাইকেলের কোনও এক পর্যায়ে আপনার মনে হতে পারে যে আপনি সব সমস্যার সমাধান করে ফেলেছেন।
JSON পার্সিং টাইপো, ভুলে যাওয়া নাল পয়েন্টার চেক, সব চলে গেছে!
আপনি হয়ত নিজেকে প্রশ্ন করতে পারেন, "আমার কাজ কি শেষ?"
তাহলে, তোমার জন্য খারাপ খবর আছে, কারণ তুমি তা নও।
কারণ, এর পরে আপনার কাছে যে সমস্যাগুলি পড়ে থাকবে, সেগুলি আসলে কঠিন সমস্যা।
And I claim today that the two categories of the hardest problems you're going to be facing are going to be responsiveness and memory.
সুখবর হল, আমরা আপনাকে সাহায্য করব।
আজ, আমরা 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-এর ৪২ নম্বর লাইনে সমস্যাটি রয়েছে।
এবং এই পদ্ধতিটি বেশিরভাগ তুচ্ছ সমস্যার ক্ষেত্রে প্রযোজ্য।
এখন, যখন কোনও রেসপন্সিভনেস বা মেমরি সংক্রান্ত সমস্যা হয়, যেমন ডানদিকে দেখানো এই অ্যাপ্লিকেশন সাড়া দিচ্ছে না, তখন স্ট্যাক ট্রেস প্রায় কখনই আসল অপরাধীকে নির্দেশ করে না।
"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 হল একটি রিয়েল-টাইম ত্রুটি রিপোর্টিং সমাধান যা ফিল্ড ডেটা প্রদান করে, সাথে প্রাসঙ্গিক এবং AI-চালিত ইনসাইট, সমস্যার সমাধানকরণ সংক্রান্ত পরামর্শ এবং আপনার নতুন রিলিজ সম্পর্কে স্পষ্ট সিগন্যাল, সবই আপনাকে দ্রুত সমস্যার সমাধান করতে সাহায্য করে।
Crashlytics আমাদের ডেভেলপারদের কাছে অত্যন্ত জনপ্রিয়, এটি মোবাইলে সবচেয়ে বেশি ব্যবহৃত ক্র্যাশ রিপোর্টিং সমাধান হিসেবে পরিচিত। এটি ৩৭,০০০-এর বেশি নেটিভ অ্যাপে ব্যবহৃত হয়, যার মধ্যে ৫৭% শীর্ষস্থানীয় Android অ্যাপ অন্তর্ভুক্ত।
Crashlytics কীভাবে আমাদের বাগ ট্র্যাক করতে সাহায্য করে তা দেখতে, আসুন একটি উদাহরণ দেখি।
আজ আমি আপনাকে ChromaClip-এর গল্প বলতে চাই।
এটি একটি ভিডিও কালার প্যালেট এক্সট্র্যাক্টর যা আমি তৈরি করছি এবং আমি আপনাকে বলতে চাই যে এটি ডেভেলপ করার সময় আমি কীভাবে রেসপন্সিভনেস এবং মেমরি বাগগুলি সমাধান করেছি।
ChromaClip-এর আইডিয়াটি খুবই সহজ: ব্যবহারকারী কোনও ভিডিও বেছে নেন, যেমন সূর্যাস্তের কোনও সুন্দর ক্লিপ বা দারুণ কালার গ্রেডিং করা কোনও দৃশ্য। অ্যাপটি ফ্রেমগুলি বিশ্লেষণ করে একটি কাস্টম কালার প্যালেট তৈরি করে। ব্যবহারকারীরা এটি তাদের ডিজাইন, ডিজিটাল আর্ট, মুড বোর্ড বা অন্য যেকোনও কাজে ব্যবহার করতে পারেন।
তাই, আমাদের গল্প শুরু হয় আমার 1.0 রিলিজের ঠিক আগে।
লঞ্চ করার আগে আমি নিশ্চিত করতে চেয়েছিলাম যে এটি যেন একদম নিখুঁত হয়, তাই আমি স্থানীয়ভাবে কয়েকটি ডিভাইসে চূড়ান্ত পরীক্ষা করছিলাম, ভিডিও বেছে নিয়ে, কালার প্যালেট এক্সট্র্যাক্ট করার জন্য বোতামে ট্যাপ করছিলাম এবং, আমার ভাগ্য, প্রতিটি Android ডেভেলপারের প্রিয় ডায়ালগ, তাই না?
ChromaClip উত্তর দিচ্ছে না।
সুতরাং, এটি হল অ্যাপ্লিকেশন সাড়া দিচ্ছে না সংক্রান্ত সমস্যা, যা ANR নামেও পরিচিত।
Polina Govorkova
তাহলে, এই পরিস্থিতিতে আতঙ্কিত হওয়া ছাড়া আমরা আর কী করতে পারি?
আমাদের প্রথম সহজাত প্রবৃত্তি অনুসরণ করে, যেহেতু টেস্ট ফোনটি ল্যাপটপের সাথে প্লাগ-ইন করা আছে, তাই আমরা 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)আসলে, এটি আমাদের কী বলে?
প্রথমে, একদম উপরে, আমরা কনফার্মেশন পাই যে এটি একটি ANR, যা MainActivity-তে চলছে।
আর কী?
আমরা কারণটি দেখতে পাচ্ছি, সেটি হল ইনপুট ডিসপ্যাচিং টাইম আউট।
এর অর্থ হল, ব্যবহারকারী স্ক্রিনে ট্যাপ করেছেন।
হ্যাঁ, আমরা করেছি।
কিন্তু মূল থ্রেডটি এতটাই ব্যস্ত ছিল যে এটি Android-এর পাঁচ সেকেন্ডের ওয়াচডগ টাইমআউটের মধ্যে সেই ইভেন্টটি প্রসেস করতে পারেনি।
তারপর, আমরা আরও দেখতে পাই যে সিস্টেম ফাইলটি traces.txt-এ ডাম্প করেছে এবং ঠিক তার নিচে, logcat আমাদের প্রধান থ্রেডের জন্য স্ট্যাক ট্রেস প্রিন্ট করে।
লক্ষ্য করুন যে সেখানে স্টেট S-এর সমান।
আমাদের মূল থ্রেড এমনকি সক্রিয়ভাবে CPU বার্ন করছে না।
S মানে স্লিপিং।
আমরা অপেক্ষা করার সময় আমাদের মূল থ্রেড কেন স্লিপিং মোডে আছে?
স্ট্যাক ফ্রেমগুলি আরও কাছ থেকে দেখা যাক।
ট্রেসের ঠিক মাঝখানে আমরা দেখতে পাচ্ছি যে আমাদের মূল থ্রেডটি Unsafe.park-এর মধ্যে পার্ক করা আছে এবং একটি অজানা অবজেক্টের জন্য অপেক্ষা করছে।
এটি কিসের জন্য অপেক্ষা করছে তা আমরা জানি না কেন?
আসলে, কিছু ANR-এর ক্ষেত্রে, যেমন কোনও থ্রেড ক্লাসিক জাভা সিঙ্ক্রোনাইজড লক দ্বারা ব্লক করা হলে, মূল থ্রেড স্ট্যাক ট্রেস আমাদের বলে দেয় যে এটি আসলে কিসের জন্য অপেক্ষা করছে, কী লকটি ধরে রেখেছে।
কিন্তু আপনি যখন আরও উন্নত লেভেলের কনকারেন্সি প্রিমিটিভ ব্যবহার করেন, তখন logcat আমাদের শুধু এইটুকুই জানায় যে আমরা কোনও অজানা অবজেক্টের জন্য অপেক্ষা করছি।
তাই, আমরা কোন অবজেক্টের জন্য অপেক্ষা করছি, কোন ব্যাকগ্রাউন্ড ওয়ার্কার থ্রেডের জন্য বিশেষভাবে অপেক্ষা করছি বা কেন এটি ৫ সেকেন্ডের বেশি সময় নিচ্ছে তা আমরা জানি না।
আর যেহেতু আমরা লোকালি টেস্ট করছি, তাই আমরা ডিভাইসের সেই traces.txt পুল করতে পারি এবং ম্যানুয়ালি ডজন ডজন স্লিপিং থ্রেড স্ক্রল করতে পারি এবং সম্ভবত অপরাধীকে খুঁজে বের করতে পারি অথবা AI-কে জিজ্ঞাসা করে টোকেন বার্ন করতে পারি।
In some cases, it would actually work, and in truth, it would work for this case.
কিন্তু আমি শুধু যে নিজে থেকে র থ্রেড ডাম্প পড়তে চাই না তা নয়-- সত্যি বলতে, এটা ২০১৯ সালের পদ্ধতি-- আমি জানি যে প্রোডাকশনে গেলে এই পদ্ধতি একেবারেই কার্যকর হবে না।
ব্যবহারকারীর ফোনে traces.txt ফাইলে আমাদের সরাসরি অ্যাক্সেস নেই, তাই তো?
তাই, Firebase Crashlytics-এর সাথে ইন্টিগ্রেট করাটা ভালো হয়েছে।
কারণ এটি সার্চ করার প্রয়োজনীয়তা দূর করে এবং প্রোডাকশনেও ANR-এর মূল কারণ খুঁজে বের করে।
কীভাবে?
এখানেই Android-এর ApplicationExitInfo API কাজে লাগে।
Android 11 থেকে শুরু করে, যখনই সিস্টেম কোনও ANR ফ্ল্যাগ করে বা আপনার অ্যাপ প্রসেস বন্ধ করে দেয়, OS তখন এক্সিট মেটাডেটা রেকর্ড করে, যার মধ্যে ক্র্যাশ হওয়ার সঠিক কারণ, মেমরি স্টেট এবং সেই মুহূর্তে আপনার প্রসেসে চলা প্রতিটি থ্রেডের জন্য সম্পূর্ণ সিস্টেম ট্রেস অন্তর্ভুক্ত থাকে।
সবচেয়ে ভালো বিষয় হল, আপনাকে কোনও অতিরিক্ত প্লাগ-ইন ইনস্টল করতে বা কোডের একটি লাইনও লিখতে হবে না।
আপনি Crashlytics SDK 18.3.5 বা তার পরবর্তী যেকোনও ভার্সন ব্যবহার করলে, এটি কোনও সমস্যা ছাড়াই কাজ করে।
তাই, আপনার অ্যাপ রিস্টার্ট হওয়ার সাথে সাথেই Crashlytics SDK অটোমেটিক ApplicationExitInfo কোয়েরি করবে, ডিভাইস টেলিমেট্রির পাশাপাশি সম্পূর্ণ মাল্টিথ্রেডেড ডাম্প ক্যাপচার করবে এবং Crashlytics ব্যাক এন্ডে পাঠিয়ে দেবে।
Crashlytics কনসোল খুলে দেখা যাক এটি আমাদের কী বলে।
এটি হল ANR-এর জন্য 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-এ অন্যান্য সমস্যার জন্য স্মার্ট ব্যাজ আছে।
যেমন, কোনও থ্রেড যদি বিশাল ফাইল রিড করার সময় বা খুব ধীরগতির ডেটাবেস কোয়েরি পারফর্ম করার সময় আটকে যায় এবং এর ফলে ANR ট্রিগার হয়, তাহলে থ্রেডটিকে I/O রুট ব্লকিং হিসেবে ট্যাগ করা হবে।
এবং যদি দুটি থ্রেড একে অপরের জন্য অপেক্ষা করতে করতে আটকে যায়, তাহলে Crashlytics সেগুলিকে ডেডলকড হিসেবে ট্যাগ করবে।
এখন, রুট ব্লকিং থ্রেডের মধ্যে স্ট্যাক ফ্রেমগুলি দেখা যাক।
তাই, আমাদের PaletteExtractor processVideo, FrameDecoder getUncompressedFrame-কে কল করছে, যা নীল রঙে হাইলাইট করা আছে এবং এটি createBitmap-এর মধ্যে সম্পূর্ণভাবে আটকে গেছে, কারণ এটি একটি ফ্রেমের জন্য মেমরি বরাদ্দ করার চেষ্টা করছে।
যেহেতু আমরা জানি যে এটি অন্য কোনও লকের জন্য অপেক্ষা করছে না এবং আমরা জানি যে এটি কোনও নেটওয়ার্ক কলে নেই, তাই আমাদের প্রধান সন্দেহ হল মেমরি এবং আমরা আসলে Crashlytics কনসোলে ডিভাইসের ফ্রি মেমরি চেক করে এখনই এটি কনফার্ম করতে পারি।
Crashlytics যে দুটি জায়গায় এটি দেখায় সেগুলি দেখুন।
নির্দিষ্ট ইভেন্টের জন্য ডেটা ট্যাবের নিচে, আমরা যে স্ট্যাক ট্রেস ট্যাবটি এক্সপ্লোর করছিলাম তার ঠিক পাশে এবং উপরে ডানদিকে সমস্ত ইভেন্টের গড় দেখানো হচ্ছে।
আর দুটি ক্ষেত্রেই, উফ, মনে হচ্ছে কিছুই নেই, ১০০ কেবিও নয়।
তাহলে, ভাবা যাক।
এখানে যা ঘটেছে তা হল, প্রসেসটি মেমরির জন্য ক্ষুধার্ত ছিল, Android রানটাইম তার পুরো সময়টি আবর্জনা সংগ্রহে ব্যয় করেছে, পরবর্তী বিটম্যাপের জন্য পর্যাপ্ত জায়গা খোঁজার চেষ্টা করেছে এবং কখনই তা পায়নি।
আমাদের ওয়ার্কার থ্রেড ফ্রিজ করে দেওয়া এবং পাঁচ সেকেন্ডের বেশি সময় ধরে UI-কে স্টার্ভ করা।
তাই, আপনার জন্য এখানে একটি চটজলদি পরামর্শ দেওয়া হল।
আপনি পাওয়ার আগে, যখনই আপনি এই ধরনের, কম ফ্রি RAM-এর সাথে পেয়ার করা ANR পান, তখনই আপনি নিশ্চিত হতে পারেন যে ANR আসলে মেমরি লিক বা ব্লট সমস্যা।
এখন প্রশ্ন হল, আমাদের অ্যাপ কি ভুক্তভোগী নাকি এটিও মেমরি জমা করছে?
তাই, আমাদের প্রোজেক্টের সমস্যার তালিকায় ফিরে গিয়ে দেখা যাক, প্রাসঙ্গিক মনে হয় এমন আর কিছু আছে কিনা।
Polina Govorkova
আর ঠিক ওখানে দেখুন।
নিচে, আমরা সমস্যার তালিকা দেখতে পাচ্ছি এবং আটটি ইভেন্ট সহ আমাদের ANR-এর ঠিক উপরে, আমরা OutOfMemory ব্যতিক্রম পেয়েছি।
তাহলে, আমরাই হয়ত অপরাধী।
আর এটি আসলে একটি বিশাল ক্লু।
কারণ Android-এ, Java OutOfMemoryError বা সংক্ষেপে OOM-এর অর্থ শুধু এই নয় যে ফোনে RAM কম ছিল।
এর অর্থ হল, আমাদের নিজস্ব অ্যাপ প্রসেস তার Android রানটাইম জাভা হিপ সীমা অতিক্রম করেছে।
তাই, এই মুহূর্তে, আমার ওয়ার্কিং থিওরি হল আমাদের ANR এবং OOM আসলে একই মুদ্রার দুটি দিক।
টেস্ট রান চলাকালীন টাইমিংয়ের উপর নির্ভর করে, ChromaClip হয় গার্বেজ কালেকশনের মধ্যে দীর্ঘক্ষণ থ্র্যাশ করে আটকে থাকবে, যার ফলে ANR ট্রিগার হবে, অথবা হিপ সম্পূর্ণভাবে শেষ হয়ে যাবে, যার ফলে OOM হবে।
নিশ্চিতভাবে জানতে 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, এটা পরিচিত।
আমরা কি এটা আগে দেখিনি?
তবে, এবার কোথা থেকে কল করা হয়েছে তা দেখুন।
সেটিংস অ্যাক্টিভিটি খোলার সময় ২৫৬ বাইট বরাদ্দ করা যায়নি।
আমরা আবার একই ফাঁদে পা দিচ্ছি।
আপনার অ্যাপ যদি ক্র্যাশ করে কারণ এটি একটি ছোট সেটিংস আইকন ইনফ্লেট করতে পারে না, তাহলে সেটিংস অ্যাক্টিভিটি ভিলেন হতে যাচ্ছে না।
এটি হল সেই হতভাগ্য কলার যে মেমরি ড্রপ করার জন্য অনুরোধ করেছে, যখন কোনও মেমরিই নেই।
তাই, আসল অপরাধীকে খুঁজে বের করতে, আমাদের জুম আউট করতে হবে এবং ক্র্যাশ হওয়ার সময় মেমরিতে থাকা সবকিছু দেখতে হবে।
অন্যভাবে বলতে গেলে, আমাদের জাভা হিপ ডাম্প প্রয়োজন।
আপনার ডেস্কে, আপনি যদি জানেন যে কীভাবে OOM ট্রিগার করতে হয়, তাহলে আপনি Android Studio মেমরি প্রোফাইলারের সাহায্যে ম্যানুয়ালি জাভা হিপ ডাম্প নিতে পারেন।
কিন্তু যখন আপনার টেস্ট রান বা অন্য কোথাও অপ্রত্যাশিতভাবে OOM হয়, তখন এই পদ্ধতি কাজ করবে না।
Polina Govorkova
তাই, Firebase Crashlytics-এর জন্য এটি আরেকটি উজ্জ্বল মুহূর্ত।
অ্যাপ্লিকেশন থেকে বেরিয়ে আসার তথ্য ছাড়াও, Crashlytics এখন Android-এর ProfilingManager API-এর সাথে ইন্টিগ্রেট করে।
Android 17-এ ProfilingManager API সিস্টেম-ট্রিগার করা মেমরি প্রোফাইলিংয়ের মাধ্যমে সুপারচার্জ করা হয়েছে এবং ঠিক এটাই আমাদের এখানে প্রয়োজন।
এর অর্থ হল, আপনার অ্যাপে OutOfMemory সংক্রান্ত সমস্যা দেখা দিলে বা সিস্টেম মেমরির সীমা লঙ্ঘন করলে, OS ব্যাকগ্রাউন্ডে হিপ স্ন্যাপশট ক্যাপচার করবে এবং তারপরে Crashlytics-এ, আপনি Crashlytics SDK 20.1.0 বা তার পরবর্তী ভার্সন ব্যবহার করলে, ট্রিগার করা কালেকশন ফ্রিতে পাওয়া যাবে।
তারপর, Crashlytics অটোমেটিক আপনার OOM এবং মেমরি লিমিটার কিল গ্রুপ করবে, আমরা ড্যাশবোর্ডে পরে এই বিষয়ে আরও কথা বলব।
এবং আপনি Firebase কনসোল বা CLI-এর মাধ্যমে হিপ ডাম্প সংগ্রহ করার বিকল্প চালু করলে, Crashlytics অটোমেটিক সেইসব ক্যাপচার করা হিপ স্ন্যাপশট সংগ্রহ করা শুরু করবে, আপনার জন্য সেগুলি ডি-অবফাসকেট করবে এবং মূলত, ক্র্যাশ হওয়ার মুহূর্তে আপনার অ্যাপের Java হিপে থাকা সবকিছুর একটি সম্পূর্ণ এক্স-রে আপনি পাবেন।
তাই, হিপ ক্যাপচার করার পরে Crashlytics কনসোলে ChromaClip OOM কেমন দেখায় তা এখানে দেওয়া হল।
সবচেয়ে উপরে, Java কেন হিপ শেষ করে ফেলেছে এবং কেন এটি ঘটেছে তার একটি ব্যাখ্যা আপনি দেখতে পাবেন।
এছাড়াও, আপনি সমস্যার সমাধান করার জন্য প্রয়োজনীয় পরামর্শ এবং এক্সটার্নাল রিসোর্সের লিঙ্ক দেখতে পাবেন।
স্ট্যাক ট্রেস ট্যাবের নিচে, বিভ্রান্তিকর সেটিংস অ্যাক্টিভিটির ঠিক উপরে, Crashlytics আসলে ফ্ল্যাগ করে যে OOM স্ট্যাক ট্রেস নির্ধারক নয়, যাতে লোকজনকে স্ট্যাক ট্রেস দেখার জন্য সময় নষ্ট করতে না হয়, যেমনটি আমরা করেছি।
তারপর, Crashlytics সেশনের জন্য ডি-অবফাসকেটেড Perfetto ফাইল, হিপ ডাম্প, অ্যাটাচ করে।
এবং এখান থেকে আমরা ফাইল ডাউনলোড করতে পারব।
আমরা Google Cloud Storage-এ সব ডাম্প ব্রাউজ করতে পারি অথবা Perfetto UI-তে এটি খুলতে পারি।
তাই, Perfetto UI-তে 'বিশ্লেষণ করুন' বিকল্পে ক্লিক করলে Perfetto হিপ ডাম্প এক্সপ্লোরার খুলে যায়।
এখন, আপনি যদি এটি আগেই দেখে থাকেন, তাহলে হাত তুলুন।
ঠিক আছে, দু'জন হাত তুলেছেন।
আমার কাছে এই ভিউটি খুবই ভীতিজনক, তবে এই ক্ষেত্রে সমস্যাটি আসলে খুবই সহজ।
প্রায় সম্পূর্ণ জাভা হিপ জুড়ে থাকা বিশাল বারের সেটটি দেখুন।
তাই, বেশিরভাগ ক্ষেত্রে, এটি হল পেয়ার অবজেক্টের একটি অ্যারে, যার প্রতিটি একটি রঙ এবং কিছু স্ট্রিং ইনস্ট্যান্সকে র্যাপ করে।
ওহ্!
আমি আসলে জানি কী হচ্ছে, আমার মনে হয়।
আমাদের কোড প্রতিটি ফ্রেমের প্রতিটি পিক্সেলের জন্য একটি রঙ এবং কিছু সহায়ক ডেটা বরাদ্দ করছে এবং একটি তালিকায় লক্ষ লক্ষ পিক্সেল ধরে রাখছে।
তাই, সেটিংস অ্যাক্টিভিটি যে ২৫৬ বাইট বরাদ্দ করতে পারেনি, তাতে অবাক হওয়ার কিছু নেই।
যাই হোক, অন্তত একটা রহস্যের সমাধান তো হল।
আসুন এটি ঠিক করা যাক।
আর এই মুহূর্তে, আমাদের কাছে কয়েকটি বিকল্প আছে।
Perfetto যেহেতু PaletteExtractor-এর দিকে আমাদের দৃষ্টি আকর্ষণ করেছে, তাই আমরা IDE খুলে লুপটি ম্যানুয়ালি আবার লিখতে পারি অথবা AI কোডিং এজেন্ট ব্যবহার করতে পারি।
অথবা আমরা ম্যানুয়াল হিপ ইন্সপেকশন সম্পূর্ণভাবে বাইপাস করে Android CLI ব্যবহার করতে পারি।
# 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 CLI ও Perfetto স্কিল ইনস্টল করলে আপনি ডিঅবফাসকেট করা Crashlytics হিপ ডাম্পের তালিকা দেখতে ও ডাউনলোড করতে পারবেন, trace_processor-এর মাধ্যমে এইসব হিপ ডাম্প থেকে সবচেয়ে বেশি রিটেন করা অবজেক্ট কোয়েরি করতে পারবেন এবং আপনার টার্মিনাল থেকে কাস্টম কী, লগ, ব্রেডক্রাম্ব, সংশ্লিষ্ট রোল-আউট ও আরও অনেক কিছুর মতো ম্যাচিং Crashlytics সমস্যার ডেটা আনতে পারবেন।
আরও ভাল হয়, স্লাইডের নিচে দেখলে।
এই কমান্ড নিজে রান করবেন না।
আবার, আপনার পছন্দের AI কোডিং এজেন্টকে ন্যাচারাল ল্যাঙ্গুয়েজে বলুন যাতে Android কমান্ড ব্যবহার করে লেটেস্ট OOM হিপ ডাম্প পরীক্ষা করে এবং সমাধানটি শিপ করে।
# 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)এবং আপনার জন্য সম্পূর্ণ ফ্লো করার জন্য এতে পর্যাপ্ত প্রসঙ্গ থাকবে।
তাই, OOM-এর তালিকা তৈরি করা, ট্রেস ডাউনলোড করা, বিশ্লেষণ চালানো এবং সরাসরি আপনার প্রোজেক্টে কোড প্যাচ জেনারেট করা।
এবং 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-ও চলে যাবে।
তাই, আমরা আমাদের টেস্ট ডিভাইস জুড়ে প্যাচ বিল্ড ডিপ্লয় করেছি, একটি নতুন ক্লিপ বেছে নিয়েছি এবং প্যালেট এক্সট্র্যাক্ট করা শুরু করেছি।
এবং UI আবার ফ্রিজ হয়ে যায়।
আরও একটি ANR।
মজার ব্যাপার হল, আরও পরীক্ষা করে আমরা দেখেছি যে কিছু ডিভাইসে এখনও ANR দেখা যাচ্ছে, কিন্তু অন্যান্য ডিভাইসে অ্যাপটি এখন র্যান্ডমলি ক্র্যাশ করছে এবং সেটিও বেশ ঘন ঘন, OOM-এর থেকেও বেশি ঘন ঘন।
কিন্তু আমরা OOM ঠিক করেছি, তাহলে কী হচ্ছে?
Polina Govorkova
তাহলে, Crashlytics-এ ফিরে যাওয়া যাক।
প্রথমে সুখবর।
আমাদের সমস্যার তালিকা দেখুন।
আমাদের আউট-অফ-মেমরি ব্যতিক্রম সম্পূর্ণভাবে চলে গেছে, যেমনটি আমরা আশা করছিলাম।
খারাপ খবর জানতে চান?
ChromaClip এখন একটু বেশি সময় ধরে বেঁচে থাকছে, তাই এর কাছে এখন সময় আছে যে নিচে আর কী কী লুকিয়ে আছে তা খুঁজে বের করার।
তাই, আমাদের ANR-এর সমস্যা দূর হয়নি।
আর এখন আমরা নিচে দুটি নেটিভ ক্র্যাশ দেখতে পাচ্ছি, সৌভাগ্যবশত মাত্র কয়েকটি ইভেন্ট সহ।
আর একদম উপরে, ১১টি ইভেন্ট সহ, আমাদের কাছে মেমরি লিমিটার কিল আছে, ঈশ্বরকে ধন্যবাদ, ইতিমধ্যেই হিপ ডাম্প থাকার জন্য ব্যাজ করা হয়েছে।
অপেক্ষা করুন।
মেমরি লিমিটার কিল কী?
আপনারা কারা কারা এই বিষয়ে শুনেছেন, তারা কি হাত তুলতে পারেন?
ঠিক আছে।
কিছু বিরল তাস।
আসুন, একসাথে শিখি।
আপনি যদি আগে মেমরি লিমিটার কিল না দেখে থাকেন, তাহলে তার কারণ হল এটি Android 17-এ একদম নতুন।
এবং জাভা OOM শুধুমাত্র তখনই ট্রিগার করে যখন আপনি ম্যানেজ করা জাভা হিপ সীমা অতিক্রম করেন, কিন্তু যখন আপনার অ্যাপের মোট মেমরি ফুটপ্রিন্টে গ্রাফিক বাফার এবং C++ অ্যালোকেশনের মতো নেটিভ RAM-ও অন্তর্ভুক্ত থাকে, তখন আগে অ্যাপ প্রতি কোনও সীমা ছিল না।
তাই, আমাদের কাছে লো মেমরি কিলার থাকত, যা গ্লোবাল ছিল।
কোনও অ্যাপ সিস্টেম RAM-এর সবটুকু ব্যবহার করে ফোনকে হ্যাং করে দিলে, এটি কাজ শুরু করে।
এখন, Android 17 থেকে শুরু করে, মেমরি লিমিটার প্রতিটি অ্যাপের মেমরি ফুটপ্রিন্টকে সীমিত করে।
তাই, প্রসেসটি যখন অত্যধিক পরিমাণে RAM ব্যবহার করে, তখন OS সেই নির্দিষ্ট অ্যাপটি বন্ধ করে দেয়।
এবং Crashlytics সেইসব বিবরণ ক্যাপচার করে এবং ড্যাশবোর্ডে একসাথে টার্মিনেশন গ্রুপ করে।
তাই, Crashlytics-এ মেমরি লিমিটার কিল-এ ক্লিক করলে, OOM-এর মতোই, আপনি একটি ব্যাখ্যা পাবেন যে কেন OS হস্তক্ষেপ করেছে, তাই আমি যা বলেছি তা আপনাকে মুখস্থ করতে হবে না।
এছাড়াও, আপনি পরবর্তীকালে কীভাবে এটি সমাধান করবেন সেই বিষয়ে পরামর্শ এবং Android মেমরি এনফোর্সমেন্ট ডকুমেন্টের সরাসরি লিঙ্ক পাবেন।
OOM-এর মতোই, OS প্লাগ টেনে নিলে স্ট্যাক ট্রেস একটি নির্দোষ ফ্রেমকে দোষারোপ করে এবং Crashlytics ড্যাশবোর্ড এটিকে হাইলাইট করে।
এখন, মেমরি লিমিটার কিল মূলত নেটিভ কোডে ঘটে কারণ নেটিভ এনভায়রনমেন্টে সেই লেয়ারটি নেই যা মেমরি নিরাপত্তা এবং অ্যালোকেশন ম্যানেজ করে।
অন্যথায়, এটি OOM হয়ে যেত।
তাই, আমাদের প্রথম প্রশ্ন হল, আমাদের Kotlin অবজেক্ট কি বিশাল নেটিভ-ব্যাকড অ্যালোকেশন ধরে রাখছে?
নাকি এটি একটি কাঁচা C++ কোড যা নিজে থেকেই লিক হচ্ছে?
ঠিক আছে, Perfetto UI-তে 'বিশ্লেষণ করুন' বিকল্পে ক্লিক করে দেখা যাক।
Perfetto হিপ ডাম্প এক্সপ্লোরারের ওভারভিউ ট্যাব দেখুন।
আমরা দেখতে পাচ্ছি যে সাধারণ তথ্যের অধীনে, anon RSS প্লাস সোয়াপ আমাদের দেখায় যে মোট প্রসেস মেমরি ৪ জিবিতে পৌঁছেছে।
ঠিক আছে।
তবুও, তার নিচে, আমরা দেখতে পাচ্ছি যে হিপ দ্বারা ধরে রাখা বাইটের অধীনে, বিটম্যাপের মতো Kotlin অবজেক্টের সাথে যুক্ত নেটিভ মেমরি সহ আমাদের সম্পূর্ণ ম্যানেজ করা হিপ মাত্র ৪২ এমবি।
ঠিক আছে।
তাই, ১০ সেকেন্ডের মধ্যে আমরা সব 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 যোগ করি, আমাদের ভার্সন বাম্প করি এবং আপডেট শিপ করি।
মেমরি লিমিটার কিল এবং ANR-এর সমস্যা অবশেষে সমাধান হয়ে গেছে, তাই আমরা 1.0 ভার্সন রিলিজ করতে পারি।
রোমাঞ্চকর।
ChromaClip এখন কতটা দ্রুত ও স্টেবল, তার জন্য ধন্যবাদ, আমাদের অ্যাপ এখন দারুণ চলছে।
প্রতিদিন হাজার হাজার মানুষ ভিডিও আপলোড ও রিমিক্স করছেন।
কিন্তু সব ট্রাফিকের সাথে, ড্যাশবোর্ডের নিচে আগে দেখা সেই রহস্যময় নেটিভ ক্র্যাশের কথা মনে আছে?
হ্যাঁ, তারাও বন্য হয়ে উঠেছে।
হঠাৎ করে, সেখানে আরও অনেক বেশি লোক চলে এসেছে।
আর আমাদের কাজ এখনও শেষ হয়নি।
Polina Govorkova
তাই, Crashlytics-এ সেই নেটিভ ক্র্যাশ খুলে, স্ট্যাক ট্রেস দেখুন।
আমাদের নিজস্ব অ্যাপ থেকে কোডের একটি লাইনও নেই।
মেমরি খালি করার সময় libc.so-এর মধ্যে RenderThread ক্র্যাশ করছে।
এখানে আপনার জন্য আরেকটি পরামর্শ হল, যখন কোনও সিস্টেম থ্রেড মেমরি অ্যালোকেটরের মধ্যে ক্র্যাশ করে এবং আপনার কোনও অ্যাপ কোড ভিতরে থাকে না, তখন সেটি নেটিভ মেমরি করাপশনের একটি টেক্সটবুক চিহ্ন।
তাই, আমাদের C++ কোড আগেই বাফারকে করাপ্ট বা ফ্রি করে দেয় এবং রেন্ডার থ্রেড পরে এটির উপর ট্রিপ করে।
তাহলে, দুর্নীতি হওয়ার ঠিক মুহূর্তে আমরা কীভাবে আসল অপরাধীকে ধরব?
Android আমাদের দুটি শক্তিশালী টুল দেয় এবং Crashlytics এই দুটির সাথেই কাজ করে।
প্রথমত, GWP-ASan, যা একটি লাইটওয়েট নিরাপত্তা নেট যা বিশেষভাবে প্রোডাকশনের জন্য তৈরি করা হয়েছে।
এটি আপনার নেটিভ হিপ অ্যালোকেশনের একটি ক্ষুদ্র অংশ স্যাম্পেল করে এবং সেগুলিকে গার্ড পেজ দিয়ে ঘিরে রাখে যাতে সিপিইউ এবং মেমরি ওভারহেড প্রায় শূন্য হয়।
হাজার হাজার ব্যবহারকারীর সেশন জুড়ে, যখন কোনও বাগ স্যাম্পেল করা অ্যালোকেশনে ফ্রি করা বা সীমার বাইরে থাকা মেমরিকে স্পর্শ করে, তখন GWP-ASan তা সাথে সাথেই ধরে ফেলে।
তবে, GWP-ASan-এর প্রকৃতি সম্ভাব্যতা-ভিত্তিক হওয়ার কারণে, আপনি যখন লোকাল ডিভাইসে কোনও কিছু পরীক্ষা করেন, তখন র্যান্ডম স্যাম্পেল পাওয়ার জন্য আপনার টেস্ট ফোন ১৬ বা তার বেশি ঘণ্টা ধরে চালাতে চান না।
তাহলে, আপনি কী পাবেন?
নির্ধারক স্থানীয় ডিবাগিংয়ের জন্য, আপনার কাছে ARM MTE বা মেমরি ট্যাগিং এক্সটেনশন আছে।
MTE প্রতিটি অ্যালোকেশন চেক করার জন্য হার্ডওয়্যার পয়েন্টার ও মেমরি ট্যাগ ব্যবহার করে এবং এটি Pixel 8 ও তার পরবর্তী ভার্সনের মতো ARM v9 ডিভাইসে উপলভ্য।
তাই, প্রোডাকশনে GWP-ASan-এর সাথে এটি পেয়ার করুন এবং দুটিরই সেরা সুবিধা পান।
এবং আপনি যেমনটি আশা করেছিলেন, যেকোনও টুল বাগটি ধরলে, Crashlytics অটোমেটিক সমস্যাটি ব্যাজ করে এবং আপনাকে GWP-ASan বা MTE দ্বারা ফিল্টার করতে দেয় এবং ড্যাশবোর্ডে সঠিক দুর্নীতির কারণ সহ তথ্য কার্ড পিন করে।
তাই, GWP-ASan ক্র্যাশ শনাক্ত করলে Crashlytics কনসোলে কী হয় তা দেখুন।
রেগুলার স্ট্যাক ট্রেস ট্যাবের পাশে, আমরা মেমরি স্ট্যাক ট্রেস পেয়েছি।
এবং একদম উপরে, আমরা ঠিক কী কারণে ডেটা নষ্ট হয়েছে তা দেখতে পাচ্ছি, Use After Free।
এবং libc.so-তে রেন্ডার থ্রেড ক্র্যাশ করার পরিবর্তে, GWP-ASan এখন আমাদের নিজস্ব effect_pipeline.cc-এর মধ্যে সেই বাফারের সম্পূর্ণ ইতিহাস দেয়।
তাই, 'এর দ্বারা বরাদ্দ করা হয়েছে' বিকল্পের অধীনে, আমরা দেখতে পাই যে বাফারটি কোথায় তৈরি করা হয়েছে।
আর 'এর দ্বারা ডিলিট করা হয়েছে' বিকল্পের ঠিক নিচে, আমরা সেই অপরাধীকে দেখতে পাই যা রেন্ডার থ্রেড থেকে এটিকে মুক্ত করেছে।
আর সেটি আমাদের এফেক্ট পাইপলাইনের ২০৮ নম্বর লাইনে লুকিয়ে আছে।
তাই, আমরা 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 রিপোর্ট এবং সেইসব রহস্যজনক ক্র্যাশ প্রায় রাতারাতি অদৃশ্য হয়ে যেতে দেখি।
এবং আমাদের ক্র্যাশ-ফ্রি ব্যবহারকারীর মেট্রিক ১০০% পর্যন্ত বেড়ে গেছে।
দারুণ!
মনে রাখবেন, আলোচনার শুরুতে আমরা নিজেদের জিজ্ঞাসা করেছিলাম: "আমার কাজ কি শেষ?"
ঠিক তার পরেই, রেসপন্সিভনেস ও মেমরি সংক্রান্ত সব ধরনের সমস্যার সম্মুখীন হতে হয়।
ANR, আউট অফ মেমরি এরর, মেমরি লিমিট শেষ হয়ে যাওয়া এবং নেটিভ মেমরি করাপ্ট হওয়ার কারণে ক্র্যাশ হওয়া সংক্রান্ত সমস্যার সমাধান করা হয়েছে।
এমনকি স্ট্যাক ট্রেস যদি ভুল হয় এবং অপরাধী যদি C++, Android এবং Firebase Crashlytics-এর থ্রেড জুড়ে কোথাও লুকিয়ে থাকে, তাহলে আপনাকে ট্র্যাক ডাউন করার এবং আত্মবিশ্বাসের সাথে শিপ করার টুল দেয়।
Polina Govorkova
সুতরাং, আপনি ল্যাপটপে ফিরে আসার সাথে সাথেই কী কী ব্যবহার করতে পারবেন তা সংক্ষেপে দেখে নেওয়া যাক: ApplicationExitInfo-এর মাধ্যমে ANR থ্রেড ইনসাইট এবং অন্যান্য প্রাসঙ্গিক মেটাডেটা; ProfilingManager-এর মাধ্যমে OOM এবং মেমরি লিমিটার কিল ট্রিগারের অটোমেটিক কালেকশন; এবং নেটিভ মেমরি করাপশনের জন্য সম্পূর্ণ GWP-ASan এবং Arm MTE সাপোর্ট।
এছাড়াও, Android 17 বা তার পরবর্তী ভার্সনে ফিল্ড হিপ ডাম্পের ব্যক্তিগত প্রিভিউ লঞ্চ করা হচ্ছে।
আপনি যদি Firebase Crashlytics-এ অটোমেটিক হিপ ডাম্প কালেকশন ব্যবহার করে দেখা প্রথম ব্যক্তিদের মধ্যে একজন হতে চান এবং Perfetto ও Android CLI-এর মাধ্যমে সেগুলি বিশ্লেষণ করতে চান, তাহলে স্লাইডের একটি দ্রুত ফটো তুলে নিন এবং লিঙ্কে সাইন-আপ করুন।
তাহলে, আমরা এখন বেশিরভাগ আলোচনা শেষ করেছি, আমি আজ আমার সাথে যোগ দেওয়ার জন্য এবং আপনার সময় দেওয়ার জন্য আপনাকে ধন্যবাদ জানাতে চাই।
আপনার মূল থ্রেড যেন আনব্লক থাকে, হিপ যেন পরিষ্কার থাকে এবং আপনার পরবর্তী রিলিজ যেন এখনও পর্যন্ত সবচেয়ে স্টেবল হয়।
তাই, ডিবাগিং উপভোগ করুন।
আপনাকে আবারও ধন্যবাদ।
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
}