Skip to content

Memory Leak with Toasts: Use applicationContext instead of Activity#810

Merged
vbuberen merged 2 commits into
ChuckerTeam:developfrom
handstandsam:toast-memory-leak
May 3, 2022
Merged

Memory Leak with Toasts: Use applicationContext instead of Activity#810
vbuberen merged 2 commits into
ChuckerTeam:developfrom
handstandsam:toast-memory-leak

Conversation

@handstandsam

@handstandsam handstandsam commented Apr 26, 2022

Copy link
Copy Markdown
Contributor

Saw a memory leak pop up in LeakCanary. May be related to Toasts using Activity Context.

Android Docs suggest using ApplicationContext for Toasts: https://developer.android.com/guide/topics/ui/notifiers/toasts

📷 Screenshots

Screen Shot 2022-04-26 at 8 23 44 AM

📄 Context

Issue found in Leak Canary.

📝 Changes

Found all instances of .makeText( and ensured it's being passed applicationContext.

📎 Related PR

🚫 Breaking

No.

🛠️ How to test

Use these features of the library and ensure that the toasts are still properly shown.

⏱️ Next steps

This is it, just merging.


Leak Trace from Leak Canary:

┬───
│ GC Root: Thread object
│
├─ ...RealLocalThumbManager$Thumbnailer instance
│    Leaking: NO (...Application↓ is not leaking)
│    Thread name: 'LocalThumbManager$Thumbnailer'
│    mContext instance of ...Application
│    ↓ RealLocalThumbManager$Thumbnailer.mContext
├─ ...Application instance
│    Leaking: NO (Application is a singleton)
│    mBase instance of android.app.ContextImpl
│    ↓ Application.mLoadedApk
│                  ~~~~~~~~~~
├─ android.app.LoadedApk instance
│    Leaking: UNKNOWN
│    Retaining 1.0 MB in 14258 objects
│    mApplication instance of ...Application
│    Receivers
│    ..HyperionService@333835072
│    ....HyperionService$OpenMenuReceiver@333837384
│    .....Application@325642976
│    ....LockReceiver@330442336
│    ....SystemBroadcastReceiver@329566192
│    ....K60@333852472
│    ....AppStateInitializerKt$registerReceivers$1@333852536
│    ....ak@333852600
│    ....f@333852656
│    ....ProxyChangeListener$ProxyReceiver@333852720
│    ....CancelUploadsIntentReceiver@333852784
│    ....ny0@327172256
│    ....w50@333852880
│    ....z6@333852936
│    ....VisibilityTracker@333853000
│    ....dl@333853072
│    ....Dispatcher$NetworkBroadcastReceiver@330534912
│    ....eo0@333853136
│    ....D6@333853200
│    ↓ LoadedApk.mServices
│                ~~~~~~~~~
├─ android.util.ArrayMap instance
│    Leaking: UNKNOWN
│    Retaining 999.3 kB in 14190 objects
│    ↓ ArrayMap.mArray
│               ~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    Retaining 999.3 kB in 14188 objects
│    ↓ Object[4]
│            ~~~
╰→ com.chuckerteam.chucker.internal.ui.MainActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.chuckerteam.chucker.internal.ui.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 997.1 kB in 14153 objects
​     key = 0f6874e8-79a2-493d-b176-ab5db9ebdff6
​     watchDurationMillis = 6171
​     retainedDurationMillis = 1171
​     mApplication instance of ...Application
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

}

private fun exportTransactions(fileName: String, block: suspend (List<HttpTransaction>) -> Sharable) {
val applicationContext = this.applicationContext

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because a coroutine is being launched below that tries to access the Activity, we would be holding an instance. By pulling out applicationContext into a local shadow val, we don't need to retain an instance of the Activity.

@vbuberen

vbuberen commented Apr 27, 2022

Copy link
Copy Markdown
Collaborator

The PR looks good, but could you share the LeakCanary log/screenshot with this leak?

Also, the link you shared mentions application context, but if we open docs for makeText it mentions that both application and activity are both valid options: https://developer.android.com/reference/android/widget/Toast#makeText(android.content.Context,%20java.lang.CharSequence,%20int)

@cortinico cortinico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the link you shared mentions application context, but if we open docs for makeText it mentions that both application and activity are both valid options: developer.android.com/reference/android/widget/Toast#makeText(android.content.Context,%20java.lang.CharSequence,%20int)

+1 on this.
I think this is safe to land, and I'm fine merging it regardless of this being the root cause of the leak. Curious to see the leakcanary output if we have it though 👍

@handstandsam

Copy link
Copy Markdown
Contributor Author

@vbuberen @cortinico Sorry for the delay. Here was the leak that was caught (with some information redacted):

┬───
│ GC Root: Thread object
│
├─ ...RealLocalThumbManager$Thumbnailer instance
│    Leaking: NO (...Application↓ is not leaking)
│    Thread name: 'LocalThumbManager$Thumbnailer'
│    mContext instance of ...Application
│    ↓ RealLocalThumbManager$Thumbnailer.mContext
├─ ...Application instance
│    Leaking: NO (Application is a singleton)
│    mBase instance of android.app.ContextImpl
│    ↓ Application.mLoadedApk
│                  ~~~~~~~~~~
├─ android.app.LoadedApk instance
│    Leaking: UNKNOWN
│    Retaining 1.0 MB in 14258 objects
│    mApplication instance of ...Application
│    Receivers
│    ..HyperionService@333835072
│    ....HyperionService$OpenMenuReceiver@333837384
│    .....Application@325642976
│    ....LockReceiver@330442336
│    ....SystemBroadcastReceiver@329566192
│    ....K60@333852472
│    ....AppStateInitializerKt$registerReceivers$1@333852536
│    ....ak@333852600
│    ....f@333852656
│    ....ProxyChangeListener$ProxyReceiver@333852720
│    ....CancelUploadsIntentReceiver@333852784
│    ....ny0@327172256
│    ....w50@333852880
│    ....z6@333852936
│    ....VisibilityTracker@333853000
│    ....dl@333853072
│    ....Dispatcher$NetworkBroadcastReceiver@330534912
│    ....eo0@333853136
│    ....D6@333853200
│    ↓ LoadedApk.mServices
│                ~~~~~~~~~
├─ android.util.ArrayMap instance
│    Leaking: UNKNOWN
│    Retaining 999.3 kB in 14190 objects
│    ↓ ArrayMap.mArray
│               ~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    Retaining 999.3 kB in 14188 objects
│    ↓ Object[4]
│            ~~~
╰→ com.chuckerteam.chucker.internal.ui.MainActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.chuckerteam.chucker.internal.ui.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 997.1 kB in 14153 objects
​     key = 0f6874e8-79a2-493d-b176-ab5db9ebdff6
​     watchDurationMillis = 6171
​     retainedDurationMillis = 1171
​     mApplication instance of ...Application
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

Comment on lines 140 to 145
lifecycleScope.launch {
val transactions = viewModel.getAllTransactions()
if (transactions.isNullOrEmpty()) {
Toast
.makeText(this@MainActivity, R.string.chucker_export_empty_text, Toast.LENGTH_SHORT)
.show()
showToast(applicationContext.getString(R.string.chucker_export_empty_text))
return@launch
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling it was this which caused the leak since it references the activity after launching a coroutine.

The other updates aren't required, and activity can be fine as you mentioned. I like to always try and use application Context just as a best practice to avoid these sorts of leaks.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feeling that this is the place.

@vbuberen vbuberen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, let's land this one.

@vbuberen vbuberen merged commit 2046d6c into ChuckerTeam:develop May 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants