googleads-mobile-android-examples icon indicating copy to clipboard operation
googleads-mobile-android-examples copied to clipboard

Memory leak when Activity hosting ad is destroyed

Open bubbleguuum opened this issue 1 year ago • 2 comments

For this report, I will use the java/admob/NativeAdvancedExample project, but this issue is not specific to that project. It also happens (at least) with WebView banner ads.

In the project above, when the MainActivity is destroyed (as in onDestroy() being called), Leak Canary reports this leak:

  D   58:40.489  1 APPLICATION LEAKS
                         D   58:40.489  References underlined with "~~~" are likely causes.
                         D   58:40.489  Learn more at https://squ.re/leaks.
                         D   58:40.489  52701 bytes retained by leaking objects
                         D   58:40.489  Signature: 91178d655ffcb34658bf9e70d4e86805f1f5cad8
                         D   58:40.489  ┬───
                         D   58:40.489  │ GC Root: Global variable in native code
                         D   58:40.489  │
                         D   58:40.489  ├─ java.util.HashMap instance
                         D   58:40.489  │    Leaking: UNKNOWN
                         D   58:40.489  │    Retaining 1.7 MB in 8478 objects
                         D   58:40.489  │    ↓ HashMap["ctx"]
                         D   58:40.489  │             ~~~~~~~
                         D   58:40.489  ╰→ com.google.example.gms.nativeadvancedexample.MainActivity instance
                         D   58:40.489  ​     Leaking: YES (ObjectWatcher was watching this because com.google.example.gms.nativeadvancedexample.MainActivity
                         D   58:40.489  ​     received Activity#onDestroy() callback and Activity#mDestroyed is true)
                         D   58:40.489  ​     Retaining 52.7 kB in 998 objects
                         D   58:40.489  ​     key = b5758ba5-b0a0-416f-9e85-9bd32b0afb12
                         D   58:40.489  ​     watchDurationMillis = 5266
                         D   58:40.489  ​     retainedDurationMillis = 263
                         D   58:40.489  ​     mApplication instance of android.app.Application
                         D   58:40.489  ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

I have made slight modification to the NativeAdvancedExample to make it easily reproducible:

  • it adds the Leak Canary dependency to build.gradle
  • it disables the ad inspector in Manifest so it does not get in the way
  • it adds a 'Destroy Activity' button to the MainActivity that just calls finish() so the MainActivity is destroyed and onDestroyed() is called (which is necessary to trigger this leak). Adding this button is necessary because otherwise just doing the back action to close the MainActivity will only trigger its onPause().

To reproduce the leak trace above:

  • download and unzip the modified NativeAdvancedExample project
  • run ./gradlew assembleDebug
  • install app: adb install app/build/outputs/apk/debug/app-debug.apk
  • launch the Admob Native Advanced app
  • display an ad
  • tap the Destroy Activity button
  • wait for a bit for Leak Canary to do its job (up to 1 or 2 minutes) and after a while the logcat for package com.google.example.gms.nativeadvancedexample will displayed the trace above.

Interestingly, if in MainActivity.java you change this line:

AdLoader.Builder builder = new AdLoader.Builder(this, ADMOB_AD_UNIT_ID);

to:

AdLoader.Builder builder = new AdLoader.Builder(getApplicationContext(), ADMOB_AD_UNIT_ID);

(that is, using the Application Context rather than the Activity Context for the AdLoader)

The leak trace will be slightly different with a reference to the NativeAdView:

   D   54:06.559  1 APPLICATION LEAKS
                         D   54:06.559  References underlined with "~~~" are likely causes.
                         D   54:06.559  Learn more at https://squ.re/leaks.
                         D   54:06.559  1664381 bytes retained by leaking objects
                         D   54:06.559  Signature: 91178d655ffcb34658bf9e70d4e86805f1f5cad8
                         D   54:06.559  ┬───
                         D   54:06.559  │ GC Root: Global variable in native code
                         D   54:06.559  │
                         D   54:06.559  ├─ java.util.HashMap instance
                         D   54:06.559  │    Leaking: UNKNOWN
                         D   54:06.559  │    Retaining 1.7 MB in 8405 objects
                         D   54:06.559  │    ↓ HashMap["view"]
                         D   54:06.559  │             ~~~~~~~~
                         D   54:06.559  ├─ com.google.android.gms.ads.nativead.NativeAdView instance
                         D   54:06.559  │    Leaking: YES (View.mContext references a destroyed activity)
                         D   54:06.559  │    Retaining 1.7 MB in 8271 objects
                         D   54:06.559  │    View is part of a window view hierarchy
                         D   54:06.559  │    View.mAttachInfo is null (view detached)
                         D   54:06.559  │    View.mWindowAttachCount = 1
                         D   54:06.559  │    mContext instance of com.google.example.gms.nativeadvancedexample.MainActivity with mDestroyed = true
                         D   54:06.559  │    ↓ View.mContext
                         D   54:06.559  ╰→ com.google.example.gms.nativeadvancedexample.MainActivity instance
                         D   54:06.559  ​     Leaking: YES (ObjectWatcher was watching this because com.google.example.gms.nativeadvancedexample.MainActivity
                         D   54:06.559  ​     received Activity#onDestroy() callback and Activity#mDestroyed is true)
                         D   54:06.559  ​     Retaining 49.4 kB in 934 objects
                         D   54:06.559  ​     key = 806db37f-9aad-481d-9c0a-96b1f2566cc8
                         D   54:06.559  ​     watchDurationMillis = 5412
                         D   54:06.559  ​     retainedDurationMillis = 412
                         D   54:06.559  ​     mApplication instance of android.app.Application
                         D   54:06.559  ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

So the big question, is where does this HashMap comes from and why does it retain an instance of NativeAdView (or directly the MainActivity context in the first trace) after the Activity is destroyed ? There is no fix or workaround I can think of, the leak has to be in the AdMob SDK...

bubbleguuum avatar Jun 05 '24 11:06 bubbleguuum

Please Report this issue to SDK Support :

https://groups.google.com/g/google-admob-ads-sdk

NVentimiglia avatar Jul 15 '24 17:07 NVentimiglia

Already did a while ago:

https://groups.google.com/g/google-admob-ads-sdk/c/2_xl6PpJhXg/m/KxItALS0AAAJ

bubbleguuum avatar Jul 15 '24 23:07 bubbleguuum

@bubbleguuum were you able to fix it?? https://github.com/square/leakcanary/issues/2688

This is real leak right?

swiggy-saurabh avatar Sep 29 '25 06:09 swiggy-saurabh

Yes AFAICT it is a real leak. Though I did not check again since the date of this report and it may have been fixed in the AdMob SDK (though I highly doubt it).

bubbleguuum avatar Sep 29 '25 09:09 bubbleguuum