Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Admob Memory Leak - avoiding by using empty activity

Our app is getting hit pretty hard by a memory leak. I've found that the root cause is the AdMob AdView keeping references to old activities. The problem is pretty well documented in question Android AdMob causes memory leak? and the sublinks in the comments/answers. I have noticed that the problem is not apparent in ICS as the GC eventually cleans up the WebViews with references to activities. However, my HTC EVO 3D running stock gingerbread never collects the activities and considering the number of force close reports due to OOM errors, the problem is very widespread for our app.

I would like to follow the solution provided by TacB0sS, https://stackoverflow.com/a/8364820/684893. He has suggested to create an empty activity and use that same activity for each AdMob AdView. The leak would be contained since AdView will only keep alive that one empty activity. He provided the code for the activity itself and how to reference it but I'm at a loss of how to actually integrate it into our app. His code never calls anything from AdMob SDK as far as I can tell.

We are currently using AdView in the XML layouts so we don't dynamically do anything with the ads in code such as call loadAd(). All of our layouts with ads rely on the ad being in the XML since they are laid out relative to it. My two questions are thus, how do I implement TacB0sS code and how can I retain my XML layout relationships if we have to switch to creating the XML layouts in code?

Update 3/6:

Thanks Adam (TacB0sS) for responding! I have no problem switching to creating the Ad in code but I am still having difficulty using your dummy activity when creating Ads. My code currently is:

AdMobActivity adActivity = new AdMobActivity();
adActivity.startAdMobActivity(this);

// Create an ad with the activity reference pointing to dummy activity
AdView adView = new AdView(adActivity.AdMobMemoryLeakWorkAroundActivity, AdSize.IAB_BANNER, "myAdUnitID");

// Create an ad request.
AdRequest adRequest = new AdRequest();

// add the ad to the layout and request it to be filled
RelativeLayout root_main = (RelativeLayout) findViewById(R.id.root_main);
root_main.addView(adView);
adView.loadAd(adRequest);

I have placed this code in the onCreate method of my initial activity. I get a force close on the line where I create the AdView, "AdView adView = new AdView(...)". Stacktrace snippet:

03-06 00:34:28.098 E/AndroidRuntime(16602): java.lang.RuntimeException: Unable to start activity ComponentInfo{org.udroid.wordgame/org.udroid.wordgame.MainMenu}: java.lang.NullPointerException
03-06 00:34:28.098 E/AndroidRuntime(16602):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1830)
(...)
03-06 00:34:28.098 E/AndroidRuntime(16602): Caused by: java.lang.NullPointerException
03-06 00:34:28.098 E/AndroidRuntime(16602):     at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:100)
03-06 00:34:28.098 E/AndroidRuntime(16602):     at com.google.ads.AdView.<init>(SourceFile:78)
03-06 00:34:28.098 E/AndroidRuntime(16602):     at org.udroid.wordgame.MainMenu.onCreate**(MainMenu.java:71)**  <- Line that creates the new AdView

What is the proper way to initialize your AdMobActivity and reference it when creating the AdView? Thanks again!

Update 2 3/6:

I figured out my problems creating the activity. I have your solution fully implemented and the best part is that it actually solves my memory leak. After spending two weeks on this problem, I am so happy that it's resolved. Here are the full steps I used:

Create a new activity called AdMobActivity:

public final class AdMobActivity extends Activity {

    public static AdMobActivity AdMobMemoryLeakWorkAroundActivity;

    public AdMobActivity() {
        super();
        if (AdMobMemoryLeakWorkAroundActivity != null) {
            throw new IllegalStateException("This activity should be created only once during the entire application life");
        }
        AdMobMemoryLeakWorkAroundActivity = this;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("CHAT", "in onCreate - AdMobActivity");
        finish();
    }

    public static final void startAdMobActivity(Activity activity) {
        Log.i("CHAT", "in startAdMobActivity");
        Intent i = new Intent();
        i.setComponent(new ComponentName(activity.getApplicationContext(), AdMobActivity.class));
        activity.startActivity(i);
    }
}

Add the following to your AndroidManifest.xml

<activity android:name="org.udroid.wordgame.AdMobActivity"
    android:launchMode="singleInstance" />

You need to initialize the dummy AdMobActivity before trying to load any ads. This activity won't contain anything. It will be displayed for a split second and then close, returning back to the activity you called it in. You can not create it in the same activity you want to load Ads since it must be fully initialized in time before using. I initialize it in a splash load screen activity's onCreate before the main activity that contains an ad starts:

// Start the dummy admob activity.  Don't try to start it twice or an exception will be thrown
if (AdMobActivity.AdMobMemoryLeakWorkAroundActivity == null) {
    Log.i("CHAT", "starting the AdMobActivity");
    AdMobActivity.startAdMobActivity(this);
}

Now you are ready to create ads in code. Add the following LinearLayout to your XML activity layout. Align all other views necessary around this layout. The AdView we create in code will be placed inside this view.

<LinearLayout
android:id="@+id/adviewLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />

In the activity you want to load an ad, create a global variable for the AdView:

AdView adView;

In our app, we load different layouts when the phone rotates. Therfore, I call the following code on every rotate. It creates the adView if necessary and adds it to the adviewLayout.

    // DYNAMICALLY CREATE AD START
    LinearLayout adviewLayout = (LinearLayout) findViewById(R.id.adviewLayout);
    // Create an ad.
    if (adView == null) {
        adView = new AdView(AdMobActivity.AdMobMemoryLeakWorkAroundActivity, AdSize.BANNER, "<ADUNITID>");
        // Create an ad request.
        AdRequest adRequest = new AdRequest();
        // Start loading the ad in the background.
        adView.loadAd(adRequest);
        // Add the AdView to the view hierarchy. The view will have no size until the ad is loaded.
        adviewLayout.addView(adView);
    }
    else {
        ((LinearLayout) adView.getParent()).removeAllViews();
        adviewLayout.addView(adView);
        // Reload Ad if necessary.  Loaded ads are lost when the activity is paused.
        if (!adView.isReady() || !adView.isRefreshing()) {
            AdRequest adRequest = new AdRequest();
            // Start loading the ad in the background.
            adView.loadAd(adRequest);
        }
    }
    // DYNAMICALLY CREATE AD END

Lastly, make sure you call adView.destroy() in the activities onDestroy() method:

@Override
protected void onDestroy() {
    adView.destroy();
super.onDestroy();
}

Anyone else that reads this, please remember that this is Adam's solution (TacB0sS), not mine. I just wanted to provide the full implementation details to make it easier for others to implement. This AdMob bug is a huge problem for apps running pre-honeycomb and Adam's solution is the best thing I could find to circumvent it. And it works!

like image 550
ravishi Avatar asked Mar 04 '12 20:03

ravishi


Video Answer


3 Answers

Ravishi,

Your question is to the point, and I have failed to address it in my solution. As far as I can tell the solution I've found works only dynamically, where you can choose your activity while calling the sdk...

The reason my code does not have a use example, is because my solution is a bit more complicated then the one I presented, involving an entire wrapping framework I've build around the Android framework, where the AdMob relation to the application is via an intermediate module, which puts the ad dynamically using the single activity instance.

I really doubt you can avoid the memory leak simply using the Android XML.

In any case, if you are into the memory leak business, you might as well check out your AsyncTask usage... it also has its own memory leak behavior... so here is my solution

best of luck...

-- UPDATE -- 07/10/14

Someone just upvoted my answer, its preposterous that this issue still exists, it has been almost three years since my original answer, and people still have memory leaks in their apps because of AdMob... from Google... that made Android....

Anyway, I just wanted to add that you should probably set the theme of the AdMobActivity to transparent, it would prevent the flickering.

-- UPDATE -- 28/02/16

Four years...

-- UPDATE -- 09/03/17

Five years... Someone at Google please wake up, and hire real monkey to do the job :)

Adam.

like image 55
TacB0sS Avatar answered Oct 22 '22 23:10

TacB0sS


I am using "play-services-ads:7.5.0" and it was not necesary to create de AdMobActivity. It worked by:

  • Creating adView dinamically

    mAdView = new AdView(getApplicationContext(), AdSize.BANNER, banner_ad_unit_id); mAdsContainer.addView(mAdView);

  • Removing all views from linearLayout on destroy and destroying adView

                mAdView.setAdListener(null);
                mAdsContainer.removeAllViews();
                mAdView.destroy();
    

Unfortunatelly Interstitial still leaks

like image 39
Julian Avatar answered Oct 23 '22 00:10

Julian


I was seeing this same leak with the 6.1.0 SDK, but was able to resolve it by calling destroy() on the AdView in question in my Activity's onDestroy. I think they have fixed it. The destroy() seems to get rid of the PhantomReferences that the AdView's WebView had to my Activity that was keeping the Activity from being GC.

like image 1
Steven Avatar answered Oct 23 '22 00:10

Steven