AdActivity leak on AdMob (SDK 7.0) for Android
Asked Answered
B

0

7

I have memory leaks regarding Interstitial ads of AdMob with AdActivity object. Whenever an ad is shown, AdActivity object count in the memory increments by 1. I inspect all changes via MAT after explicit GC calls. I use the latest versions of everything.

At first, I thought that this is related to how I implemented my UI or project, but creating a fresh and empty project shows the same leak.

This leak has also existed in the previous Admob SDK (Google Play Services) and now it exists in version 7.0 too.

I see that people try to solve these kinds of issues by creating a SingleInstance empty activity just to show and set as the context of the interstitial ads. I tried them all and they did not work for my case. Some did help but I even couldn't use it because of the flow of my app. launchMode in Android has limitations and it does not help me on my case.

I already notified the AdMob team but even they fix it, it doesn't seem to happen in a short time as they have just released the version 7.0 SDK.

I do not understand how others do not report leaks like this. It cannot be a special case just for me as it happens on even samples or default templates. If anyone somehow solved this issue (including ugly reflection hacks) please share your experience. I have been working on this for months! Really!

AndroidManifest:

<uses-sdk
    android:minSdkVersion="9"
    android:targetSdkVersion="22" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
    android:name="MyApplication"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name="com.google.android.gms.ads.AdActivity"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
        android:theme="@android:style/Theme.Translucent" />
</application>

MainActivity:

package com.example.leaktest1;

import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.InterstitialAd;

import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {

    private InterstitialAd interstitial=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        interstitial= new InterstitialAd(getApplicationContext());
        interstitial.setAdUnitId("YOUR-ADD-ID");

        AdRequest adRequest2 = new AdRequest.Builder()
        // .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                .build();

        interstitial.setAdListener(new AdListener() {
            @Override
            public void onAdLoaded() {          
                super.onAdLoaded();

                if(interstitial.isLoaded()){
                    interstitial.show();
                }
            }
        });
        interstitial.loadAd(adRequest2);
    }


    @Override
    protected void onDestroy() {
        if(interstitial!=null){
            interstitial.setAdListener(null);
            interstitial=null;
        }
        super.onDestroy();
    }     
}

Layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.leaktest1.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

</RelativeLayout>
  • Start the app. When the ad is shown, close it with backpress and hit back button again to exit app.
  • App will still be in memory, but the activity is gone. Now touch the app icon to start the activity again, it will show the ad again, exit like you did before.
  • Cause GC multiple times and get heap dump. You will see that there are 2 AdActivity objects (and also many other related objects). It will continue to grow according to the number of shown ads. enter image description here

The following did not work too (it still leaks):

/*
interstitial.setAdListener(new AdListener() {
    @Override
    public void onAdLoaded() {          
        super.onAdLoaded();
    }
});*/
interstitial.loadAd(adRequest2);

Runnable r=new Runnable() {
    @Override
    public void run() {
                if(interstitial.isLoaded()){
                    interstitial.show();
                }
}};
new Handler().postDelayed(r,10000);

And putting code inside a button did not work too (it still leaks):

Button b = new Button(this);
b.setText("Touch me");
b.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        if(interstitial.isLoaded()){
            interstitial.show();
        }
    }
});
ViewGroup v = (ViewGroup) this.findViewById(android.R.id.content);
v.addView(b);

Leak platform and exceptions: This leak happens on various devices from Samsung and Asus with various un-modded (original firmware) Androis systems from 2.3 to 4.4. It also happens on any setup of android simulator.

(This leak does not show up on rooted Cyanogenmod (Galaxy S3) Android 4.4.4)

UPDATE

The leak does not vanish if I use Activity context instead of Application context. It also causes Activity leak too. enter image description here

enter image description here

enter image description here

Bank answered 25/3, 2015 at 10:1 Comment(12)
I have been using Admob interstitials across several projects for years and I have never seen this issue. I don't use any hacks. Suggest you create a Github project that clearly shows the fault.Lobelia
@Lobelia Thank you. I have added related codes and explanations to the question. Would you take a look?Bank
Your problem may well be that you are calling interstitial.show() from onAdLoaded(). DON'T DO THIS Call showInterstitial() from a natural break point in your app.Lobelia
@Lobelia Thanks; I tried what you said and called the add at another time and not from a listener. (I updated the question and added that code to the bottom of the question) Unfortunately it still leaks.Bank
@Frankish What would happen if you passed in Activity as opposed to ApplicationContext() to the constructor? I believe since you're passing in the ApplicationContext(), the ad may live for the lifetime of the application. Just a thought. What I mean is to replace interstitial= new InterstitialAd(getApplicationContext()); with interstitial= new InterstitialAd(this);Driven
@Driven giving Activity context instead of ApplicationContext is always my favorite but it just causes additional leaks by also keeping my Activity. As long as AdActivity lives, some how it doesn't leave my Activity. After your message, I retried it and I can confirm my reply; so I update the question to reflect this situation too.Bank
@Frankish fair enough. was just a thought. I have the same problem with admob (albeit on normal banner ads) and I tried many things, including playing around with the webView which seems to be the main source of the leak, and it worked but it caused more severe problems with the app later on because of the fundamental changes I had to make. I finally gave up on it and accepted it as is since the leak was sufficiently small in my case. I know it's not ideal but there wasn't much I could do about it.Driven
Have you tried to call interstitial.destroy() on your onDestroy() ?Handcraft
@Handcraft Unfortunately there is no destroy() method for InterstitialAd object in the SDK.Bank
@Bank Oops my bad :)Handcraft
@Bank did you find any fix for this problem?Soothfast
No, Google does not care memory leaks and unfortunately I believe they accept it on Android. The same library does general not leak or leak very little on iOS but Android... I had ended up applying custom fixes which iterate through the hierarchy to clean leaking stuff, but as every new version I had to repeat similar stuff. I stopped doing it for most of the leaks. I just accept the fact that Google libs leak on Android.Bank

© 2022 - 2024 — McMap. All rights reserved.