1

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

enter image description here

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

3 Answers3

1

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.

Atick Faisal
  • 1,086
  • 1
  • 6
  • 13
  • Thanks for the help :). The thing is I'm not using `ConnectivityManager` so it's not me who needs to unregister. How do I get around finding where it's being used or somehow overriding it so that I can unregister? – Lance Samaria Jun 23 '23 at 16:02
  • Unfortunately, I don't know how to fix this issue without knowing how it is used in the code. – Atick Faisal Jun 23 '23 at 16:17
  • Thanks. Same for me. I found this but it's not working for me: https://stackoverflow.com/q/41431409/4833705 – Lance Samaria Jun 23 '23 at 17:12
0

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)
  }
}
Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
  • I just tried your code, it didn't work, the leak is still there. This is a very frustrating lbug, smh. I'm going to check out the link to the discussion and see if I can find anything there. Thanks for the help, really appreciated :) Cheers!!! – Lance Samaria Jun 24 '23 at 16:54
  • I just tried the code from the link, the leak still exists. I tried `getSystemService(ConnectivityManager::class.java)`, `applicationContext.getSystemService(ConnectivityManager::class.java)`, `getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager`, and `applicationContext.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager`. I did this all inside `class MyApplication : Application() { onCreate{} }` and `MyApplication`'s name is registered in Manifest. – Lance Samaria Jun 24 '23 at 17:03
  • You might try removing other library dependencies as you can, to isolate. If you have any sort of network-reliant libraries (think Glide for image loading, that sort of thing) those are your likely culprits. Perhaps they're doing something else that is initializing it with the activity context. – Kevin Coppock Jun 24 '23 at 21:44
  • That makes sense. I use Glide, CircleImageView, and several Firebase libraries. I'll see what happens and if I find a culprit I'll get back to you. Thanks again :) – Lance Samaria Jun 24 '23 at 21:47
  • 1
    The memory leak culprit is `AdMob`, specifically `MobileAds.initialize(requireContext())` for banner ads. Once I changed it to `MobileAds.initialize(MyApplication.myAppContext)`, the leak no longer appeared. Thanks for sending me in the right direction! – Lance Samaria Jul 02 '23 at 23:58
0

@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"

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256