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!