29

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!

Community
  • 1
  • 1
ravishi
  • 3,349
  • 5
  • 31
  • 40
  • Works like a charm!! Thank you to both you @ravishi and @TacB0sS!! – Regis Nov 18 '12 at 11:56
  • Hi ravishi... I am having trouble with this solution. Can you please help me. This is my question http://stackoverflow.com/questions/15583994/activity-does-not-launch-from-the-recent-activities-android – Swati Rawat Mar 23 '13 at 09:54
  • 1
    Is there link to the full code @ravishi? – Mahmoud Hanafy May 27 '14 at 10:49
  • Another way to address this issue, even though it is a pain to do so, is to unpersist all data that's needed from sharedPreferences or any other types of on storage persistence, WIPE ALL OF YOUR APP DATA, then re-persist everything. You could start a service as your app is closing to do this. This would prevent data from getting really big due to the memory leak. – SavageKing Oct 03 '14 at 23:39
  • 2015: I had to resort to this to solve memory issue cause in my apps due to admob. My apps Works like a charm now. Thanks a ton – Sunny Kumar Aditya Apr 03 '15 at 17:12
  • ads become unclickable after using this solution – Ravi Jul 28 '15 at 08:02
  • 2017: shame on google I have to use this as workaround – q126y Jan 08 '17 at 11:09
  • Hi @ravishi... this is ridicules... they didn't fix it yet... :) – TacB0sS May 18 '17 at 19:04
  • Does it create a memory leak on the empty activity? (ie. AdMobActivity) – Pablo Alfonso Jun 16 '20 at 21:11
  • Is there any way to avoid the white screen appearing between the Splash and the Main Activity? – Pablo Alfonso Jun 16 '20 at 21:26

3 Answers3

17

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.

TheCodeDestroyer
  • 763
  • 9
  • 29
TacB0sS
  • 10,106
  • 12
  • 75
  • 118
  • Thanks Adam! Please see my edited post for a more direct question. Thanks for also pointing out the AsyncTask issue. I have thankfully not run into it yet or if I have, it hasn't been a huge leak like AdMob. – ravishi Mar 06 '12 at 08:51
  • Which AdMob version you use? Did you call it before or after the super.onCreate(...)? In which activity do you instantiate the AdView, the Dummy Activity, or the real UI Activity. – TacB0sS Mar 06 '12 at 21:36
  • 1
    I got it working. See edits in original question for the details. Thanks so much! – ravishi Mar 07 '12 at 06:52
  • The problem is still there with Google Play Services 7.0 AdMob SDK and this solution does not work for me. If you found any other solution recently, please share it :| http://stackoverflow.com/questions/29252545/adactivity-leak-on-admob-sdk-7-0-for-android – frankish Mar 25 '15 at 11:29
  • Have you followed up on @ravishi question and answer? Again, this solution is not to eliminate the admob activity... we are using it to enaure only one instance of it! – TacB0sS Mar 30 '15 at 05:35
  • HAHAHAHAHAHAHa... just got an up vote... it has been 5 years since... Google you are such a pain in the BUTT, fix this bug it is so simple to fix it!! – TacB0sS Mar 09 '17 at 21:08
6

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

Julian
  • 2,490
  • 24
  • 20
  • 1
    Thanks a lot man. I had a `LeakCanary` leak referencing `MainActivity -> references ht.a -> static hk.o`. This helped me solve it, which was a `NativeExpressAdView` leak on my `GridLayoutManager`. I was not removing all views from my `GridLayoutManager` or setting my `setAdListener` to null. Google completely missed this in their `NativeExpressAdView` tutorial. – Drew Szurko May 02 '17 at 00:04
1

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.

Steven
  • 905
  • 1
  • 8
  • 17