I am having a memory leak in my MainActivity.java which was detected by LeakCanary. This is my Leak Trace.
┬───
│ GC Root: Input or output parameters in native code
│
├─ android.os.MessageQueue instance
│ Leaking: NO (MessageQueue#mQuitting is false)
│ HandlerThread: "main"
│ ↓ MessageQueue.mMessages
│ ~~~~~~~~~
├─ android.os.Message instance
│ Leaking: UNKNOWN
│ Retaining 14.2 kB in 348 objects
│ Message.what = 0
│ Message.when = 37524601 (681 ms after heap dump)
│ Message.obj = null
│ Message.callback = instance @319985112 of com.application.app.
│ MainActivity$$ExternalSyntheticLambda2
│ ↓ Message.callback
│ ~~~~~~~~
├─ com.application.app.MainActivity$$ExternalSyntheticLambda2 instance
│ Leaking: UNKNOWN
│ Retaining 12 B in 1 objects
│ f$0 instance of com.application.app.MainActivity with mDestroyed =
│ true
│ ↓ MainActivity$$ExternalSyntheticLambda2.f$0
│ ~~~
╰→ com.application.app.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.defenderstudio.
geeksjob.MainActivity received Activity#onDestroy() callback and
Activity#mDestroyed is true)
Retaining 11.2 MB in 5622 objects
key = e98df529-afa0-4e0c-b0f0-51a5d3eaf67c
watchDurationMillis = 5249
retainedDurationMillis = 248
mApplication instance of android.app.Application
mBase instance of androidx.appcompat.view.ContextThemeWrapper
METADATA
Build.VERSION.SDK_INT: 30
Build.MANUFACTURER: samsung
LeakCanary version: 2.7
App process name: com.application.app
Count of retained yet cleared: 6 KeyedWeakReference instances
Stats: LruCache[maxSize=3000,hits=6544,misses=134885,hitRate=4%]
RandomAccess[bytes=5904498,reads=134885,travel=75990168059,range=41137566,size=5
3483782]
Heap dump reason: 7 retained objects, app is visible
Analysis duration: 31639 ms
I can't understand what is the problem here. I closed all the postdelayed()
method when ondestroy()
is called. This is the code :
@Override
protected void onDestroy() {
dialog = new Dialog(MainActivity.this, R.style.dialog);
if (dialog.isShowing()) {
dialog.dismiss();
}
if (handler != null && statusChecker != null) {
handler.removeCallbacks(statusChecker);
}
if (databaseReference != null && userSignInInfoReference != null && eventListener != null) {
databaseReference.removeEventListener(eventListener);
userSignInInfoReference.removeEventListener(eventListener);
}
progressDialog = new ProgressDialog(MainActivity.this, R.style.ProgressDialogStyle);
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
headerView = null;
super.onDestroy();
}
Please help me out here!
NOTE : Also Please tell me what is MessageQueue and how close all leaks of it. Thanks in advance!
CodePudding user response:
What is a MessageQueue?
There are 3 key Android classes tied together: Handler, Looper and MessageQueue. When a Looper instance is created, it creates its own MessageQueue instance. Then you can create a Handler et give it the Looper instance. When you call Handler.post() (or postDelayed), under the hood you're actually calling Handler.sendMessage which enqueues a Message instance on the Message queue associated to the Looper associated to that Handler.
What happens to those enqueued messages? Somewhere else in the code (i.e. dedicated HandlerThread), something calls Looper.loop() which loops forever, removing one entry at a time from the associated Message queue and running that message. If the queue is empty, the Looper waits for the next message (that's done via native code). Some more context: https://developer.squareup.com/blog/a-journey-on-the-android-main-thread-psvm/
What can we read from the LeakTrace?
We see that the MessageQueue at the top is for the following HandlerThread: "main". That's the main thread. So if you're doing a postDelayed on the main thread, a message gets enqueued into the message queue.
Messages are stored as a linked list: the MessageQueue holds on to the first message (via its mMessages field) and then each message holds on to the next.
We can see that the head of the queue is a Message and we can see its content.
Message.when = 37524601 (681 ms after heap dump)
This tells us the message was enqueued with a delay and will execute in 681ms (after the heap dump was taken)
Message.callback = instance @319985112 of com.application.app.MainActivity$$ExternalSyntheticLambda2
This tells us the callback enqueued is an inner lambda defined in MainActivity. It's hard to figure out which one, but if you decompile the bytecode (e.g. class files or dex) you should be able to tell which lambda has that name.
Fix
Most likely, you have a piece a code that keeps rescheduling itself as a postDelayed on the main thread, even after the activity is destroyed. That callback needs to be canceled in onDestroy()
Edit: note on the advice to using a WeakReference in the other answer: that's not good general advice. From the doc: https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak
Memory leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.
CodePudding user response:
Check all the data members of your Activity there is some data member which is outliving your activity's lifecycle.
Also check in what places you are passing the activity context and MainActivity.this instance.
Lastly check what callbacks / lambda's are associated with this activity there could be a case that one of your class's member is being shared with some other class like a recycler view adapter which could lead to a leak.
As a thumb rule when working on memory leak issues I encapsulate most if not all data passing with WeakReference that way you are both safe from NPE plus you get benefit of a decoupled class.
Edit - As shared in the comment below using weak reference is a bad practice and there are better ways to resolve memory leaks. Kindly check the answer from @Pierre or link to the comment posted below.