Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
May 3, 2022

Conversation

handstandsam
Copy link
Contributor

@handstandsam handstandsam commented Apr 26, 2022

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

@@ -136,12 +136,11 @@ internal class MainActivity :
}

private fun exportTransactions(fileName: String, block: suspend (List<HttpTransaction>) -> Sharable) {
val applicationContext = this.applicationContext
Copy link
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
Copy link
Collaborator

vbuberen commented Apr 27, 2022

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)

Copy link
Member

@cortinico cortinico left a comment

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
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
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
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.

Copy link
Collaborator

@vbuberen vbuberen left a comment

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.

None yet

3 participants