I have an AlertDialog created using the AlertDialog Builder:
private void setProgressDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.alertDialogTheme);
builder.setView(R.layout.alert_dialog_login_progress);
mAlertDialog = builder.create();
mAlertDialog.setCancelable(false);
mAlertDialog.setCanceledOnTouchOutside(false);
mAlertDialog.show();
}
I'm using FirebaseAuth's sign in method:
private void signIn() {
mAuth.signInWithEmailAndPassword(mEmail, mPassword)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (!task.isSuccessful()) {
mAlertDialog.dismiss();
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
}
});
}
I have reduced the code all the way down to this and it still leaks. I am NOT changing activities.
The leak is caused when I type an incorrect password, and the .isSuccessful() method is called.
I have tried running it on the UI thread too, but it still leaks:
runOnUiThread(new Runnable() {
public void run() {
mAlertDialog.dismiss();
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
});
Here is the full code:
https://pastebin.com/bp4Xa6jx
Here is the leak:
┬───
│ GC Root: Local variable in native code
│
├─ android.os.HandlerThread instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'GoogleApiHandler'
│ ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never
│ leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (InternalLeakCanary↓ is not leaking)
│ ↓ Object[].[218]
├─ leakcanary.internal.InternalLeakCanary class
│ Leaking: NO (LoginActivity↓ is not leaking and a class is never leaking)
│ ↓ static InternalLeakCanary.resumedActivity
├─ instance
│ Leaking: NO (Activity#mDestroyed is false)
│ mApplication instance of android.app.Application
│ mBase instance of android.app.ContextImpl
│ ↓ LoginActivity.mAlertDialog
│ ~~~~~~~~~~~~
├─ androidx.appcompat.app.AlertDialog instance
│ Leaking: UNKNOWN
│ Retaining 157.5 kB in 2114 objects
│ mContext instance of android.view.ContextThemeWrapper, wrapping activity
│ with mDestroyed = false
│ Dialog#mDecor is null
│ ↓ Dialog.mWindow
│ ~~~~~~~
├─ com.android.internal.policy.PhoneWindow instance
│ Leaking: UNKNOWN
│ Retaining 15.3 kB in 300 objects
│ mContext instance of android.view.ContextThemeWrapper, wrapping activity
│ with mDestroyed = false
│ Window#mDestroyed is false
│ ↓ PhoneWindow.mDecor
│ ~~~~~~
╰→ com.android.internal.policy.DecorView instance
Leaking: YES (ObjectWatcher was watching this because com.android.
internal.policy.DecorView received View#onDetachedFromWindow() callback)
Retaining 4.7 kB in 42 objects
key = f381c0b5-587b-4d68-b453-be1c851ce257
watchDurationMillis = 31367
retainedDurationMillis = 26366
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
mContext instance of android.view.ContextThemeWrapper, wrapping activity
with mDestroyed = false
Why is it leaking and what can I do to fix it?
After a few hours of trying to figure out the issue, the memory leak no longer happens when I set my mAlertDialog to null after calling mAlertDialog.dismiss();
mAlertDialog.dismiss();
mAlertDialog = null;
Toast.makeText(LoginActivity.this, "Authentication failed.",Toast.LENGTH_SHORT).show();
I am don't know why this works...but it stops the leak from happening.
Related
Do you guys have any suggestion what can cause this memory leak that I got from the Leak Canary?
Below is the description I am getting from the report.
Steps to reproduce are, In the Settings menu, click on the switch which will perform the theme change, after this action, leaking happening. Below this I will provide the code piece from the SettingsFragmentClass. Any help would be most welcomed guys.
Basically issue is happening when this line is being called.
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
┬───
│ GC Root: Global variable in native code
│
├─ android.graphics.animation.RenderNodeAnimator instance
│ Leaking: UNKNOWN
│ Retaining 1.1 MB in 16344 objects
│ ↓ RenderNodeAnimator.mTarget
│ ~~~~~~~
├─ android.graphics.RenderNode instance
│ Leaking: UNKNOWN
│ Retaining 1.1 MB in 16274 objects
│ ↓ RenderNode.mHostView
│ ~~~~~~~~~
├─ com.google.android.material.switchmaterial.SwitchMaterial instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 1.1 MB in 16272 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.null
│ View.mWindowAttachCount = 1
│ mContext instance of com.flixeron.my_passkeeper.main.MainActivity with
│ mDestroyed = true
│ ↓ View.mContext
╰→ com.flixeron.my_passkeeper.main.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.flixeron.
my_passkeeper.main.MainActivity received Activity#onDestroy() callback
and Activity#mDestroyed is true)
Retaining 251.2 kB in 5956 objects
key = 3c074833-b927-4971-b2b5-0a77b9e68bde
watchDurationMillis = 5216
retainedDurationMillis = 215
mApplication instance of com.flixeron.my_passkeeper.main.MainApplication
mBase instance of androidx.appcompat.view.ContextThemeWrapper
METADATA
Build.VERSION.SDK_INT: 31
Build.MANUFACTURER: Xiaomi
LeakCanary version: 2.7
App process name: com.flixeron.my_passkeeper
Stats: LruCache[maxSize=3000,hits=6345,misses=185335,hitRate=3%]
RandomAccess[bytes=9694284,reads=185335,travel=115234430872,range=38720254,size=
46574102]
Heap dump reason: 7 retained objects, app is visible
Analysis duration: 15049 ms
Fragment Class
class SettingsFragment : Fragment() {
private val cardViewModel: CardViewModel by viewModel()
private var bindingProp: FragmentSettingsBinding? = null
private val binding get() = bindingProp!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
bindingProp = FragmentSettingsBinding.inflate(inflater, container, false)
setTextLockTime()
configureScreenShoots()
themeSettings()
// changing dark/light mode
binding.switchTheme.setOnClickListener {
if (!PreferenceProvider.darkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
PreferenceProvider.darkMode = true
binding.imageViewTheme.setImageResource(R.drawable.ic_dark_mode)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
PreferenceProvider.darkMode = false
binding.imageViewTheme.setImageResource(R.drawable.ic_light_mode)
}
}
override fun onDestroyView() {
super.onDestroyView()
bindingProp = null
}
}
Xml file
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/switchTheme"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
app:layout_constraintBottom_toBottomOf="#+id/textViewChangeTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/textViewChangeTheme">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="#+id/switchTheme1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="#+id/switchTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/switchTheme"
android:scaleX="1.3"
android:scaleY="1.3"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Updated leak canary
┬───
│ GC Root: System class
│
├─ android.view.inputmethod.InputMethodManager class
│ Leaking: NO (InputMethodManager↓ is not leaking and a class is never
│ leaking)
│ ↓ static InputMethodManager.sInstance
├─ android.view.inputmethod.InputMethodManager instance
│ Leaking: NO (DecorView↓ is not leaking and InputMethodManager is a
│ singleton)
│ ↓ InputMethodManager.mCurRootView
├─ android.view.ViewRootImpl instance
│ Leaking: NO (DecorView↓ is not leaking)
│ mContext instance of com.android.internal.policy.DecorContext, wrapping
│ activity com.flixeron.my_passkeeper.main.MainActivity with mDestroyed =
│ false
│ ViewRootImpl#mView is not null
│ mWindowAttributes.mTitle = "com.flixeron.my_passkeeper/com.flixeron.
│ my_passkeeper.main.MainActivity"
│ mWindowAttributes.type = 1
│ ↓ ViewRootImpl.mView
├─ com.android.internal.policy.DecorView instance
│ Leaking: NO (View attached)
│ View is part of a window view hierarchy
│ View.mAttachInfo is not null (view attached)
│ View.mWindowAttachCount = 1
│ mContext instance of com.android.internal.policy.DecorContext, wrapping
│ activity com.flixeron.my_passkeeper.main.MainActivity with mDestroyed =
│ false
│ ↓ View.mResources
│ ~~~~~~~~~~
├─ dev.b3nedikt.app_locale.AppLocaleResources instance
│ Leaking: UNKNOWN
│ Retaining 1.9 kB in 55 objects
│ context instance of androidx.appcompat.view.ContextThemeWrapper
│ ↓ AppLocaleResources.context
│ ~~~~~~~
├─ androidx.appcompat.view.ContextThemeWrapper instance
│ Leaking: UNKNOWN
│ Retaining 274.9 kB in 6390 objects
│ mBase instance of io.github.inflationx.viewpump.ViewPumpContextWrapper
│ ContextThemeWrapper does not wrap a known Android context
│ ↓ ContextWrapper.mBase
│ ~~~~~
├─ io.github.inflationx.viewpump.ViewPumpContextWrapper instance
│ Leaking: UNKNOWN
│ Retaining 270.3 kB in 6242 objects
│ mBase instance of dev.b3nedikt.app_locale.AppLocaleContextWrapper
│ ViewPumpContextWrapper does not wrap a known Android context
│ ↓ ContextWrapper.mBase
│ ~~~~~
├─ dev.b3nedikt.app_locale.AppLocaleContextWrapper instance
│ Leaking: UNKNOWN
│ Retaining 270.2 kB in 6236 objects
│ mBase instance of android.app.ContextImpl
│ AppLocaleContextWrapper does not wrap a known Android context
│ ↓ ContextWrapper.mBase
│ ~~~~~
├─ android.app.ContextImpl instance
│ Leaking: YES (ContextImpl.mOuterContext is an instance of com.flixeron.
│ my_passkeeper.main.MainActivity with Activity.mDestroyed true)
│ Retaining 268.1 kB in 6175 objects
│ mAutofillClient instance of com.flixeron.my_passkeeper.main.MainActivity
│ with mDestroyed = true
│ mOuterContext instance of com.flixeron.my_passkeeper.main.MainActivity
│ with mDestroyed = true
│ ↓ ContextImpl.mAutofillClient
╰→ com.flixeron.my_passkeeper.main.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.flixeron.
my_passkeeper.main.MainActivity received Activity#onDestroy() callback
and Activity#mDestroyed is true)
Retaining 264.2 kB in 6091 objects
key = 44a30066-31c8-4abb-8660-907035d18457
watchDurationMillis = 5229
retainedDurationMillis = 224
mApplication instance of com.flixeron.my_passkeeper.main.MainApplication
mBase instance of androidx.appcompat.view.ContextThemeWrapper
METADATA
Build.VERSION.SDK_INT: 31
Build.MANUFACTURER: Xiaomi
LeakCanary version: 2.7
App process name: com.flixeron.my_passkeeper
Count of retained yet cleared: 1 KeyedWeakReference instances
Stats: LruCache[maxSize=3000,hits=3959,misses=160418,hitRate=2%]
RandomAccess[bytes=8403631,reads=160418,travel=86543130456,range=36387679,size=4
4147138]
Heap dump reason: 6 retained objects, app is visible
Analysis duration: 11869 ms```
your leak is strictly related to GUI, which gets restared when user change theme with your option
conider releasing GUI-related objects by releasing binded Views when Fragment gets destroyed (and further/soon will be recreated with new theme)
override fun onDestroyView() {
bindingProp = null
super.onDestroyView()
}
When initiating network communication through the Executor class, I use the showProgress function. When the result is received after communication ends, hideProgess function is used. The code is as follows and I am using it in BaseActivity.
private val networkIO = Executors.newFixedThreadPool(3)
private var progressBar: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
progressBar = AlertDialog.Builder(this)
.setView(R.layout.dialog_loading)
.setCancelable(false)
.create()
.apply { window?.setBackgroundDrawableResource(android.R.color.transparent) }
doOnCreate()
}
override fun onDestroy() {
progressBar = null
super.onDestroy()
}
fun showProgress() {
runOnUiThread {
try {
if (progressBar?.isShowing == false) {
progressBar?.show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun hideProgress() {
runOnUiThread { progressBar?.dismiss() }
}
A memory leak occurs when moving to another activity after performing the next network communication in MainActivity.
private fun fetchCard() {
networkIO.execute {
showProgress()
networkManager.getCardInfo { _, code, message ->
Timber.d("## $code")
Timber.d("## $message")
hideProgress()
}
}
}
Leak causes provided by LeakCanary
2021-03-08 05:11:42.552 8678-8678/com.tagless D/LeakCanary:
┬───
│ GC Root: System class
│
├─ android.app.ActivityThread class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static ActivityThread.sCurrentActivityThread
├─ android.app.ActivityThread instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ mInitialApplication instance of com.tagless.TaglessApp
│ mSystemContext instance of android.app.ContextImpl
│ mSystemUiContext instance of android.app.ContextImpl
│ ↓ ActivityThread.mActivities
├─ android.util.ArrayMap instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ArrayMap.mArray
├─ java.lang.Object[] array
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ Object[].[3]
├─ android.app.ActivityThread$ActivityClientRecord instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ activity instance of com.tagless.ui.main.MainActivity with mDestroyed = false
│ ↓ ActivityThread$ActivityClientRecord.activity
├─ com.tagless.ui.main.MainActivity instance
│ Leaking: NO (Activity#mDestroyed is false)
│ mApplication instance of com.tagless.TaglessApp
│ mBase instance of androidx.appcompat.view.ContextThemeWrapper
│ ↓ BaseActivity.progressBar
│ ~~~~~~~~~~~
├─ androidx.appcompat.app.AlertDialog instance
│ Leaking: UNKNOWN
│ Retaining 106.7 kB in 1647 objects
│ mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
│ mDestroyed = false
│ Dialog#mDecor is null
│ ↓ Dialog.mWindow
│ ~~~~~~~
├─ com.android.internal.policy.PhoneWindow instance
│ Leaking: UNKNOWN
│ Retaining 23.3 kB in 289 objects
│ mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
│ mDestroyed = false
│ Window#mDestroyed is false
│ ↓ PhoneWindow.mDecor
│ ~~~~~~
╰→ com.android.internal.policy.DecorView instance
Leaking: YES (ObjectWatcher was watching this because com.android.internal.policy.DecorView received
View#onDetachedFromWindow() callback)
Retaining 3.4 kB in 59 objects
key = 5f2f819a-9640-429f-afff-4489f7b039d2
watchDurationMillis = 5703
retainedDurationMillis = 702
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
mContext instance of android.view.ContextThemeWrapper, wrapping activity com.tagless.ui.main.MainActivity with
mDestroyed = false
METADATA
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: samsung
LeakCanary version: 2.6
App process name: com.tagless
Stats: LruCache[maxSize=3000,hits=5668,misses=70891,hitRate=7%]
RandomAccess[bytes=3887192,reads=70891,travel=27244024610,range=20775300,size=25990100]
Heap dump reason: user request
Analysis duration: 3864 ms
Thanks in advance Anyone please help!!
Add progressBar = null should solve your issue. The progressBar is referenced by the activity and it can not be recycled by GC.
fun showProgress() {
runOnUiThread {
try {
if (progressBar == null) {
progressBar = AlertDialog.Builder(this)
.setView(R.layout.dialog_loading)
.setCancelable(false)
.create()
.apply { window?.setBackgroundDrawableResource(android.R.color.transparent) }
}
if (progressBar?.isShowing == false) {
progressBar?.show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun hideProgress() {
runOnUiThread {
progressBar?.dismiss()
progressBar = null
}
}
Just started getting to know memory leaks with Leak Canary so forgive me if I conflate or misunderstand anything.
Leak canary tells me there is a memory leak and I've narrowed it down to this line
NavigationUI.setupActionBarWithNavController(
it,
navController
)
nav controller is called and set to the variable like this
val navController = findNavController(this#PokemonDetailFragment)
and 'it' is my activity cast to app compat activity, full snippet looks like this:
mainActivity = (activity as AppCompatActivity)
mainActivity?.let {
it.setSupportActionBar(toolbar)
val navController = findNavController(this#PokemonDetailFragment)
NavigationUI.setupActionBarWithNavController(
it,
navController
)
}
I've tried three things, 1. Injecting the activity instead, 2. Setting activity as a global nullable variable and setting it to null in onDestroyView, and 3. tried using the NavigationUI.setupWithNavController instead of the setupActionBarWithNavController which takes a toolbar and the nav controller
NavigationUI.setupWithNavController(
binding.toolbar,
findNavController(this#PokemonDetailFragment)
)
but none of these fix the issue.
Removing the first code block definitely removes the leak, however Leak Canary doesn't show the leak as being with a third party library and does say it's a variable in my code below is the heap dump
```
┬───
│ GC Root: Local variable in native code
│
├─ android.os.HandlerThread instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'LeakCanary-Heap-Dump'
│ ↓ HandlerThread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
│ ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (InternalLeakCanary↓ is not leaking)
│ ↓ Object[].[502]
├─ leakcanary.internal.InternalLeakCanary class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static InternalLeakCanary.resumedActivity
├─ com.sealstudios.pokemonApp.MainActivity instance
│ Leaking: NO (FragmentContainerView↓ is not leaking and Activity#mDestroyed is false)
│ ↓ MainActivity._binding
├─ com.sealstudios.pokemonApp.databinding.ActivityMainBinding instance
│ Leaking: NO (FragmentContainerView↓ is not leaking)
│ ↓ ActivityMainBinding.navHostFragment
├─ androidx.fragment.app.FragmentContainerView instance
│ Leaking: NO (View attached)
│ mContext instance of com.sealstudios.pokemonApp.MainActivity with mDestroyed = false
│ View.parent androidx.constraintlayout.widget.ConstraintLayout attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mID = R.id.nav_host_fragment
│ View.mWindowAttachCount = 1
│ ↓ FragmentContainerView.mKeyedTags
│ ~~~~~~~~~~
├─ android.util.SparseArray instance
│ Leaking: UNKNOWN
│ ↓ SparseArray.mValues
│ ~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[0]
│ ~~~
├─ androidx.navigation.NavHostController instance
│ Leaking: UNKNOWN
│ ↓ NavHostController.mOnDestinationChangedListeners
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.concurrent.CopyOnWriteArrayList instance
│ Leaking: UNKNOWN
│ ↓ CopyOnWriteArrayList.array
│ ~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[1]
│ ~~~
├─ androidx.navigation.ui.ActionBarOnDestinationChangedListener instance
│ Leaking: UNKNOWN
│ ↓ ActionBarOnDestinationChangedListener.mContext
│ ~~~~~~~~
├─ android.view.ContextThemeWrapper instance
│ Leaking: UNKNOWN
│ ContextThemeWrapper wraps an Activity with Activity.mDestroyed false
│ ↓ ContextThemeWrapper.mBase
│ ~~~~~
├─ dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper instance
│ Leaking: UNKNOWN
│ ViewComponentManager$FragmentContextWrapper wraps an Activity with Activity.mDestroyed false
│ ↓ ViewComponentManager$FragmentContextWrapper.fragment
│ ~~~~~~~~
╰→ com.sealstudios.pokemonApp.ui.PokemonDetailFragment instance
Leaking: YES (ObjectWatcher was watching this because com.sealstudios.pokemonApp.ui.PokemonDetailFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
key = 724affdf-d1ac-47ff-82b8-6907ced5b666
watchDurationMillis = 9052
retainedDurationMillis = 4051
METADATA
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: Google
LeakCanary version: 2.4
App process name: com.sealstudios.pokemonApp
Analysis duration: 14474 ms```
any help appreciated I just want to set the toolbar which needs an AppCompatActivity and then dispose of it properly or allow the system to do it
I'm using Hilt and found this not sure if its related although my heap does mention the ContextThemeWrapper - https://github.com/google/dagger/issues/2070
Didn't get anywhere with this and setting it manually removes the memory leak
#SuppressLint("DefaultLocale")
private fun setActionBar() {
binding.toolbar.outlineProvider = null
binding.appBarLayout.outlineProvider = null
(activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
(activity as AppCompatActivity).supportActionBar.apply {
this?.setHomeButtonEnabled(true)
this?.setDisplayHomeAsUpEnabled(true)
}
}
I think the problem is with the NavigationUI.setupWithNavController() function.
Let's have a look at the code snippet below:
public static void setupWithNavController(#NonNull Toolbar toolbar,
#NonNull final NavController navController,
#NonNull final AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ToolbarOnDestinationChangedListener(toolbar, configuration));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
navigateUp(navController, configuration);
}
});
}
As we can see, you are adding a listener to your navController which is a new instance of ToolbarOnDestinationChangedListener().
Because you do not remove the listener of the navController when the activity / fragment life cycle enters the Destroy state, this is most likely causing your problem.
I strongly recommend to unset(nulling) every used object in the fragment, especially the bitmaps, in onDestroy():
#Override
public void onDestroy() {
super.onDestroy();
// Here is my example:
myBigBitmap = null;
adapter = new MyAdapter(requireContext(), new String[0], new String[0], new Bitmap[0]);
listView.setAdapter(adapter);
}
This is the only correct method. Working 100%.
I am trying the new version of GoogleMap
com.google.android.libraries.maps:maps:3.1.0-beta
But it seems to leak memory when I come back to my previous Activity
I'm using Fragment inside my Activity
Here is the code:
XML
<androidx.fragment.app.FragmentContainerView
android:id="#+id/map"
android:name="com.google.android.libraries.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="220dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:useViewLifecycle="true"
tools:context=".OutputScreen"/>
Java Code
public class OutputScreen extends FragmentActivity implements OnMapReadyCallback{
private GoogleMap map;
private SupportMapFragment mapFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setting layout
mapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
map = googleMap;
}
#Override
protected void onDestroy() {
if (map != null){
map.clear();
map.setMyLocationEnabled(false)
map = null;
}
}
}
LeakCanary Report
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (Application↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ android.app.Application instance
│ Leaking: NO (Application is a singleton)
│ Application does not wrap an activity context
│ ↓ Application.mActivityLifecycleCallbacks
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[5]
│ ~~~
├─ com.google.android.libraries.maps.dj.zzf instance
│ Leaking: UNKNOWN
│ ↓ zzf.zza
│ ~~~
├─ com.google.android.libraries.maps.dj.zzd instance
│ Leaking: UNKNOWN
│ ↓ zzd.zzd
│ ~~~
├─ com.google.android.libraries.maps.bm.zzu instance
│ Leaking: UNKNOWN
│ ↓ zzu.zzd
│ ~~~
├─ com.google.android.libraries.maps.kf.zzab instance
│ Leaking: UNKNOWN
│ ↓ zzab.zzh
│ ~~~
├─ com.google.android.libraries.maps.kf.zzd instance
│ Leaking: UNKNOWN
│ ↓ zzd.zzi
│ ~~~
├─ com.google.android.libraries.maps.ed.zzau instance
│ Leaking: YES (View detached and has parent)
│ mContext instance of android.app.Application, not wrapping activity
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ ↓ zzau.mParent
├─ android.widget.FrameLayout instance
│ Leaking: YES (zzau↑ is leaking and View detached and has parent)
│ mContext instance of android.app.Application, not wrapping activity
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ ↓ FrameLayout.mParent
╰→ android.widget.FrameLayout instance
Leaking: YES (ObjectWatcher was watching this because com.google.android.libraries.maps.SupportMapFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks) and View.mContext references a destroyed activity)
I looked other stackoverflow answers but didn't found any proper solution.
The issue is that the SupportMapFragment is incorporated into your Fragment container view, but the ondestroy is not being called. You need to manually invoke this.
In your OutputScreen class under the onDestroy() method, you need to call the following:
mapFragment.onDestroy() // call this in your activity's onDestroy
I was working on some issues related to memory leaks. When I was removing a fragment using a fragment transaction(fragmentManager.beginTransaction().remove(fragment).commit()), I noticed memory leaks. But when I use a pop back stack (fragmentManager.popBackStack()) I didn't saw any memory leak. This scenario made me curious to find which way is better to remove a fragment. Using Fragment Transaction or using a pop back stack.
EDIT 1
I am adding a fragment dynamically.
fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int, tag: String) {
supportFragmentManager.inTransaction {
setPrimaryNavigationFragment(fragment)
addToBackStack(tag)
add(frameId, fragment, tag)
}
}
EDIT 2 Memory leak output
┬───
│ GC Root: Local variable in native code
│
├─ com.esri.arcgisruntime.mapping.view.GeoView$RenderingThread instance
│ Leaking: NO (MapView↓ is not leaking)
│ Thread name: 'Rendering thread'
│ ↓ GeoView$RenderingThread.mGeoView
├─ com.esri.arcgisruntime.mapping.view.MapView instance
│ Leaking: NO (MainActivity↓ is not leaking and View attached)
│ mContext instance of main.MainActivity with mDestroyed = false
│ View.parent android.widget.LinearLayout attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mID = R.id.mapView
│ View.mWindowAttachCount = 1
│ ↓ MapView.mContext
├─ MainActivity instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking and Activity#mDestroyed is false)
│ ↓ MainActivity.navigationHandler
├─ BottomNavigationHandler instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ BottomNavigationHandler.fragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ FragmentManagerImpl.mFragmentStore
├─ androidx.fragment.app.FragmentStore instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ FragmentStore.mActive
├─ java.util.HashMap instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ HashMap$Node[].[0]
├─ java.util.HashMap$Node instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ HashMap$Node.value
├─ androidx.fragment.app.FragmentStateManager instance
│ Leaking: NO (SearchContainerFragment↓ is not leaking)
│ ↓ FragmentStateManager.mFragment
├─ SearchContainerFragment instance
│ Leaking: NO (Fragment#mFragmentManager is not null)
│ Fragment.mTag=SearchContainerFragment
│ ↓ SearchContainerFragment.adapter
│ ~~~~~~~
├─ SearchContainerAdapter instance
│ Leaking: UNKNOWN
│ ↓ SearchContainerAdapter.mViewPagerObserver
│ ~~~~~~~~~~~~~~~~~~
├─ androidx.viewpager.widget.ViewPager$PagerObserver instance
│ Leaking: UNKNOWN
│ ↓ ViewPager$PagerObserver.this$0
│ ~~~~~~
├─ NonSwipeableViewPager instance
│ Leaking: YES (View detached and has parent)
│ mContext instance of MainActivity with mDestroyed = false
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mID = R.id.viewPager
│ View.mWindowAttachCount = 1
│ ↓ NonSwipeableViewPager.mParent
╰→ android.widget.RelativeLayout instance
Leaking: YES (ObjectWatcher was watching this because SearchContainerFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
key = 6941f63f-0f30-45b3-b62e-958477398b5b
watchDurationMillis = 13855
retainedDurationMillis = 8854
key = 3b8b4ecb-d3fe-4db0-b46b-2e4ab6e64bb8
watchDurationMillis = 13856
retainedDurationMillis = 8856
mContext instance of MainActivity with mDestroyed = false
View#mParent is null
View#mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
METADATA
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: OnePlus
LeakCanary version: 2.2
App process name: com.dev
Analysis duration: 18920 ms```