Reputation: 19622
I'm using LeakCanary and it found a memory leak in ConnectivityManager. I've never used ConnectivityManager before and it's not in my project from anything that I wrote. I'm assuming maybe a 3rd party library is using it.
How can I fix this?
┬───
│ GC Root: System class
│
├─ android.net.ConnectivityManager class
│ Leaking: NO (a class is never leaking)
│ ↓ static ConnectivityManager.sCallbackHandler
│ ~~~~~~~~~~~~~~~~
├─ android.net.ConnectivityManager$CallbackHandler instance
│ Leaking: UNKNOWN
│ Retaining 32 B in 1 objects
│ ↓ ConnectivityManager$CallbackHandler.this$0
│ ~~~~~~
├─ android.net.ConnectivityManager instance
│ Leaking: UNKNOWN
│ Retaining 516.0 kB in 8048 objects
│ mContext instance of com.company.appname.MainActivity with mDestroyed =
│ true
│ ↓ ConnectivityManager.mContext
│ ~~~~~~~~
╰→ com.company.appname.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.company.
appname.MainActivity received Activity#onDestroy() callback and
Activity#mDestroyed is true)
Retaining 515.9 kB in 8043 objects
key = fb405ad1-a78b-4e8f-8d09-c1b937e1462c
watchDurationMillis = 8341
retainedDurationMillis = 3230
mApplication instance of android.app.Application
mBase instance of androidx.appcompat.view.ContextThemeWrapper
Upvotes: 1
Views: 908
Reputation: 19622
@KevinCoppock sent me in the right direction
The memory leak culprit was AdMob
, specifically MobileAds.initialize(requireContext())
for banner ads which I used inside one of my fragments.
Once I changed it to use MobileAds.initialize(MyApplication.myAppContext)
, the leak no longer appeared. Here's an example:
1- AdMob initialization in Fragment example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// MobileAds.initialize(requireContext()) <---- Causes Memory Leak
MobileAds.initialize(MyApplication.myAppContext) // <---- No Memory Leak
}
2- MyApplication class:
class MyApplication : Application() {
companion object {
lateinit var myAppContext: Context
}
override fun onCreate() {
super.onCreate()
myAppContext = applicationContext
}
}
Side note for beginners, make sure to add MyApplication to your Manifest as in android:name=".MyApplication"
Upvotes: 0
Reputation: 134714
There is some discussion on the LeakCanary repo here. In short I believe the issue is that the first initialization of the ConnectivityManager
holds a strong reference to the Context
it was retrieved by.
So if something (in a 3rd party library for example) initializes it when your MainActivity
starts (via getSystemService()
) that reference will be leaked.
You could try within your Application
class to resolve a ConnectivityManager
up front with the application context:
class MyApplication : Application() {
override fun onCreate() {
// Eagerly initialize ConnectivityManager with the application context
// before the Activity is started.
getSystemService(ConnectivityManager::class.java)
}
}
Upvotes: 0
Reputation: 1823
ConnectivityManager in Android is usually used to observe network state changes. To do this, the user needs to register a callback, which will be called by ConnectivityManager when something changes. However, you must unregister this callback when you no longer need the updates. According to the docs:
To avoid performance issues due to apps leaking callbacks, the system will limit the number of outstanding requests to 100 per app (identified by their UID), shared with all variants of this method, of requestNetwork(NetworkRequest, PendingIntent) as well as ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback. Requesting a network with this method will count toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, make sure to unregister the callbacks with unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback). Requires Manifest.permission.ACCESS_NETWORK_STATE.
I am not sure by this is something that can lead to the leak.
Upvotes: 1