My interstitial Ad is causing a memory leak?
Asked Answered
R

5

9

I also tried using the ApplicationContext but it still leaks for some reason.

Found a similar post about the issue here AdActivity leak on AdMob (SDK 7.0) for Android but without an answer.

Also tried to set the adlistener and the ad to null in onDestroy() but without any luck and still leaks the activity.

My code called in onCreate()

private void refreshInterstitial(){
        mInterstitialAd = new InterstitialAd(this);
        mInterstitialAd.setAdUnitId("AD_ID");
        mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).addTestDevice("877BCC97E130A0DC62B2E5770D854496").build());

        mInterstitialAd.setAdListener(new AdListener() {
            @Override
            public void onAdLoaded() {
                mInterstitialAd.show();
            }
            @Override
            public void onAdClosed() {
            }
        });
}

Leakcanary Leak Trace

 ┬───
    │ GC Root: Global variable in native code
    │
    ├─ mx instance
    │    Leaking: UNKNOWN
    │    ↓ mx.a
    │         ~
    ├─ com.google.android.gms.ads.internal.webview.w instance
    │    Leaking: UNKNOWN
    │    mContext instance of com.google.android.gms.ads.internal.webview.ay, not wrapping activity
    │    View#mParent is null
    │    View#mAttachInfo is null (view detached)
    │    View.mWindowAttachCount = 1
    │    ↓ w.a
    │        ~
    ├─ com.google.android.gms.ads.internal.webview.aa instance
    │    Leaking: YES (View detached and has parent)
    │    mContext instance of com.google.android.gms.ads.internal.webview.ay, not wrapping activity
    │    View#mParent is set
    │    View#mAttachInfo is null (view detached)
    │    View.mWindowAttachCount = 1
    │    ↓ aa.mListenerInfo
    ├─ android.view.View$ListenerInfo instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ View$ListenerInfo.mOnClickListener
    ├─ com.google.android.gms.ads.nonagon.ad.webview.f instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ f.a
    ├─ com.google.android.gms.ads.nonagon.ad.webview.l instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ l.e
    ├─ com.google.android.gms.ads.nonagon.ad.event.bs instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ bs.a
    ├─ java.util.HashMap instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ HashMap.table
    ├─ java.util.HashMap$Node[] array
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ HashMap$Node[].[1]
    ├─ java.util.HashMap$Node instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ HashMap$Node.key
    ├─ com.google.android.gms.ads.nonagon.shim.k instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ k.a
    ├─ com.google.android.gms.ads.internal.client.ae instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ ae.a
    ├─ com.google.android.gms.internal.ads.zzuc instance
    │    Leaking: YES (aa↑ is leaking)
    │    ↓ zzuc.zzcbw
    ├─ com.test.Activity$1 instance
    │    Leaking: YES (aa↑ is leaking)
    │    Anonymous subclass of com.google.android.gms.ads.AdListener
    │    ↓ EqualizerActivity$1.this$0
    ╰→ com.test.Activity instance
    ​     Leaking: YES (ObjectWatcher was watching this because Activity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     key = 40a1eb8e-c9e6-4062-b5f7-053e642e812f
    ​     watchDurationMillis = 5288
    ​     retainedDurationMillis = 258
Rheingold answered 23/2, 2020 at 5:38 Comment(10)
Have you tried making mInterstitialAd a local variable instead?Shop
@harrytmthy Why would that solve the problem?Rheingold
Because in most cases memory leak happened when we keep a reference of a mutable object, where this reference is NOT lifecycle aware. When AdListener callback is called, somehow your mInterstitialAd is being leaked. That's why I am asking you to turn it into a local variable instead, and make it final to ensure its immutability. That way, you can prevent the memory leak.Shop
And if that's the case, please let me know so I can write it as an answer and claim the bounty :)Shop
@harrytmthy just tried it and it's still leakingRheingold
See my answer :)Shop
happened with me too. Seems like bug from their sideMilord
@SahilManchanda That's what i thought, but why is no one reporting this. I can barely find anything about this here on stackoverflow and on their community page they don't answer my questions.Rheingold
Have you browsed some of the issues on github.com/googleads/googleads-mobile-android-examples? Does issue #83 or issue #84 look similar?Johnnyjumpup
@Johnnyjumpup yes except i'm just using a basic interstitial ad and not a rewarded video but i just looked at the comments and someone has the exact same problem on #84.Rheingold
S
3

According to InterstitialAd Docs:

A single InterstitialAd object can be used to request and display multiple interstitial ads over the course of an activity's lifespan, so you only need to construct it once.

After looking at your code again, I noticed you re-construct mInterstitialAd every time refreshInterstitial() method is called. But according to the docs above, you should only construct mInterstitialAd once during onCreate().

In your case, the main cause of the memory leak: you still have an active listener (which is bound to Activity lifespan), yet you reconstruct a new InterstitialAd instance with another listener.

So, the solution is to reuse InterstitialAd instance and its listener without re-assigning. I suggest to simplify your refreshInterstitial() method to this:

private void refreshInterstitial() {
    mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).addTestDevice("877BCC97E130A0DC62B2E5770D854496").build());
}

Then put mInterstitialAd assignment to onCreate(). This solution is similar to the one you can find here.

Shop answered 27/2, 2020 at 2:10 Comment(6)
I don't understand this? My method refreshInterstitial() is the code that is called in onCreate() when i launch my activity so it is called once. Also without the adlistener the interstitial won't show up because i have to call mInterstitialAd.show() when the ad is loaded.Rheingold
Now let me ask you: do you only call refreshInterstitial() only once in the whole Activity lifespan? Because if you call it more than once, with your code, memory leak will happen.Shop
Once. I just put it inside a method because i have alot of code in my onCreateRheingold
Then can you update your question, show us what's inside your onCreate() so we can figure out the solution? Because the Leak Trace logged Leaking: YES (ObjectWatcher was watching this because Activity received Activity#onDestroy() callback and Activity#mDestroyed is true). Which means there is a part in your code which triggers onDestroy() then re-create the Activity, making your InterstitialAd constructed more than once.Shop
Thats because i go back to my previous activity, Activity is a second activity and not my mainactivity. So when i press the back button ObjectWatcher is destroyed and that's normal.Rheingold
I think this is a bug on their side unfortunately. Even their github example which is from google itself has this leak.Rheingold
T
2

This is working for me: (in ActivityMain()):

MobileAds.initialize(WeakReference(applicationContext).get()){}

in fragments:

adView= AdView(WeakReference(requireActivity().application).get())

LeakCanary shows:

==================================== HEAP ANALYSIS RESULT ==================================== 0 APPLICATION LEAKS.

Trypanosomiasis answered 15/2, 2021 at 21:7 Comment(1)
I'm the 2nd person who upvoted this answer but upon further testing this answer doesn't work. Don't use itLassalle
L
1

I was facing the same issue in interstitial ad and it is resolved by setting the FullScreenContentCallback to null in all of its overridden methods.

 interstitial.setFullScreenContentCallback(new FullScreenContentCallback() {
        @Override
        public void onAdDismissedFullScreenContent() {
            super.onAdDismissedFullScreenContent();
            
            interstitial.setFullScreenContentCallback(null);}}
Lamere answered 4/8, 2022 at 6:25 Comment(1)
I know my memory leak is related to this specific setting with the FullScreenContentCallback. However, I'm still getting memory leaks even after setting this to null.Khalif
B
0
// Singleton pattern using Kotlin's object declaration.
object AdInterstitialCacheManager {
    private var interstitialAd: InterstitialAd? = null


    // Loads an interstitial ad using Google's AdManager with custom targeting.
    private fun loadGoogleInterstitialAd(context: Context, adUnitId: String) {
        if (interstitialAd != null) return

        val pref = Preferences().apply { getPreference(context) }
        val adRequest = AdManagerAdRequest.Builder()
            .addCustomTargeting(Constants.Ads.CUSTOM_TARGETING_CATEGORY, listOf("बड़ी ख़बरें", "ListingPage"))
            .addCustomTargeting(Constants.Ads.CUSTOM_TARGETING_PRICE_TAG, pref.getAdsPriceCategory().orEmpty())
            .setPublisherProvidedId(pref.getPPID())
            .build()
        loadAd(context, adUnitId, adRequest)
    }

    // Common method to load an interstitial ad, with a fallback to Google's AdManager on failure.
    private fun loadAd(context: Context, adUnitId: String, adRequest: AdRequest) {
        InterstitialAd.load(context, adUnitId, adRequest, object : InterstitialAdLoadCallback() {
            override fun onAdLoaded(ad: InterstitialAd) {
                interstitialAd = ad
            }

            override fun onAdFailedToLoad(adError: LoadAdError) {
                if (adRequest is AdManagerAdRequest) {
                    interstitialAd = null // If already using Google AdManager, do not retry.
                } else {
                    loadGoogleInterstitialAd(context, adUnitId)
                }
            }
        })
    }

    // Displays the loaded interstitial ad and clears it from cache after being shown.
    fun showInterstitialAd(activity: Activity) {
        interstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
            override fun onAdDismissedFullScreenContent() {
                interstitialAd = null
            }

            override fun onAdShowedFullScreenContent() {
                interstitialAd = null
            }
        }
        interstitialAd?.show(activity)
    }

    // Clears the interstitial ad from cache, allowing a new ad to be loaded.
    fun clearAdViewCache() {
        interstitialAd = null
    }
}
Belford answered 17/7 at 7:7 Comment(0)
M
0

I faced the same issue when testing my app with AdMob interstitial ads on an Android 5.1 device. It has 2 GB of RAM, and it threw an OutOfMemoryError after I used my app for not more than 20 minutes. I followed AdMob's official guide to implement interstitial ads on my app. When I tested it, Android Profiler detected a memory leak after closing an activity with interstitial ads. I was using the latest version of the AdMob SDK (23.3.0).

Apparently, Google hasn't done a pretty good job in addressing memory leaks in their SDK. Fortunately, there's a fix, but it is not as simple as passing an applicationContext to InterstitialAd.load() within the activity class and setting interstitialAd and its FullScreenContentCallback to null in onDestroy(). Doing these is not enough to eliminate memory leaks.

Instead, you must ensure you load and show interstitial ads within a single instance: a singleton object. Singh has provided a good solution to this problem, but I will elaborate further and share what I did to eliminate memory leaks due to the AdMob SDK.

Here's a basic structure of the custom AdFactory singleton (import statements are excluded):

interface AdFactoryInterface {
    fun onInterstitialAdDismissedFullScreenContent()
    fun onInterstitialAdFailedToShowFullScreenContent()
}

object AdFactory {
    var interstitialAd: InterstitialAd? = null
    private var mContextReference: WeakReference<Context>? = null
    private var adFactoryInterface: AdFactoryInterface? = null

    fun initialize(context: Context, afi: AdFactoryInterface) {
        mContextReference = WeakReference(context)
        adFactoryInterface = afi
    }

    fun loadInterstitialAds() {
        val context = mContextReference?.get()
            ?: throw IllegalStateException("AdFactory has not been initialized")
        if (adFactoryInterface == null) {
            throw IllegalStateException("adFactoryInterface cannot be null")
        }
        val adRequest = AdRequest.Builder().build()

        InterstitialAd.load(context, "your_ad_id", adRequest,
            object: InterstitialAdLoadCallback() {
                override fun onAdLoaded(p0: InterstitialAd) {
                    super.onAdLoaded(p0)
                    interstitialAd = p0

                    interstitialAd?.fullScreenContentCallback =
                        object: FullScreenContentCallback() {

                            override fun onAdDismissedFullScreenContent() {
                                super.onAdDismissedFullScreenContent()
                                interstitialAd = null
                                adFactoryInterface?.onInterstitialAdDismissedFullScreenContent()
                            }

                            override fun onAdFailedToShowFullScreenContent(p0: AdError) {
                                super.onAdFailedToShowFullScreenContent(p0)
                                interstitialAd = null
                                adFactoryInterface?.onInterstitialAdFailedToShowFullScreenContent()
                            }

                        }
                }

                override fun onAdFailedToLoad(p0: LoadAdError) {
                    super.onAdFailedToLoad(p0)
                    interstitialAd = null
                }

            }
        )
    }

    fun showInterstitialAd(activity: Activity) {
        if (interstitialAd != null) {
            interstitialAd?.show(activity)
        }
    }

    fun clear() {
        interstitialAd?.fullScreenContentCallback = null
        interstitialAd = null
        mContextReference = null
        adFactoryInterface = null
    }
}

Here's a description of each method in the singleton object:

  • initialize(context: Context, afi: AdFactoryInterface): Initializes the singleton. You can call this method in your activity class as follows: AdFactory.initialize(applicationContext, this). Your activity class must implement AdFactoryInterface.
  • loadInterstitialAds(): Loads an interstitial ad. This method must be called after initialize(context: Context, afi: AdFactoryInterface).
  • showInterstitialAd(activity: Activity): Shows the interstitial ad.
  • clear(): Removes all references to the activity and the interstitial ad. This must be called in onDestroy(), or the activity to be destroyed may leak memory.

If you need to perform actions (e.g. starting another activity) after the user dismisses the interstitial ad or the ad fails to show, override onInterstitialAdDismissedFullScreenContent() and onInterstitialAdFailedToShowFullScreenContent() in your activity class. I have tried this approach and tested it on Android 5.1 and Android 12 platforms, and Android Profiler no longer reports any memory leaks. It's complicated, but that's the way to do it.

Mclane answered 9/9 at 11:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.