Reputation: 2481
I'm using AdView and LeakCanary. Fragment that hosts adView call adView.destroy() in onDestroy, but LeakCanary shows that Activity that hosts this fragment is leaked by com.google.android.gms.common.api.a.a.a.i . Heap dump also shows that there are memory leak. When I remove AdView.loadAd() and not loading Ad in fragment - there is no leak. Any thoughts or suggestions ? Thanks.
Upvotes: 19
Views: 4451
Reputation: 2465
I tried all solutions discussed in stackoverflow forums and all other Google forums. The memory leaks appear to be erratic: Looks like the chances of experiencing memory leaks are higher if user leaves the activity BEFORE showing the ad.
The only thing that is working for me is to contain the memory leak in one single activity...
First recommendation: Do NOT add the adview directly to the XML layout file. If you follow the instructions from the official documentation (https://developers.google.com/admob/android/banner) that will lead to memory leak FOR SURE. Instead, add the adview programatically:
Remove the adview from the XML file:
<RelativeLayout
xmlns:ads="http://schemas.android.com/apk/res-auto"
android:id="@+id/RLadViewContainer"
android:layout_width="match_parent"
android:layout_height="50dp"
>
<com.google.android.gms.ads.AdView <<< REMOVE IT
android:id="@+id/adView" <<< REMOVE IT
android:layout_width="wrap_content" <<< REMOVE IT
android:layout_height="wrap_content" <<< REMOVE IT
android:layout_centerHorizontal="true" <<< REMOVE IT
android:layout_alignParentBottom="true" <<< REMOVE IT
ads:adSize="BANNER" <<< REMOVE IT
ads:adUnitId="@string/banner_ad_unit_id"> <<< REMOVE IT
</com.google.android.gms.ads.AdView> <<< REMOVE IT
</RelativeLayout>
Then define a RelativeLayout variable (adscontainer) in your activity next to the mAdView:
private AdView mAdView;
private RelativeLayout adscontainer;
In OnCreate, remove your old mAdView assignment and replace it with the following:
adscontainer = findViewById(R.id.RLadViewContainer);
mAdView = new AdView(MainActivity.MemoryLeakContainerActivity);//THIS IS THE TRICK ;)
mAdView.setAdSize(AdSize.BANNER);
mAdView.setAdUnitId(getResources().getString(R.string.banner_ad_unit_id));
adscontainer.addView(mAdView);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mAdView.getLayoutParams();
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mAdView.setLayoutParams(lp);
RequestConfiguration requestConfiguration = new RequestConfiguration.Builder()
.setTestDeviceIds(Constants.testDevices)
.build();
MobileAds.setRequestConfiguration(requestConfiguration);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest); //Move this line to the right place, wherever you need.
Note: This is supposed to display the banner at the bottom with CENTER_HORIZONTAL and ALIGN_PARENT_BOTTOM options.
In onDestroy(), add the following:
if (mAdView != null){
mAdView.setAdListener(null);
adscontainer.removeAllViews();
adscontainer = null;
mAdView.destroy();
mAdView = null;
}
Now, lets talk about this "MainActivity.MemoryLeakContainerActivity": YES, you have to sacrifice one of your activities to contain the memory leak. I did not find any other way. I tried "getApplicationContext()" here and it did not work. So I have chosen my MainActivity for two reasons:
a- In my app the MainActivity works as a simple menu with three buttons ("Play", "Review Decks" and "Create Decks"). It is not consuming too much memory, there is no need to destroy it as most of the onBackPressed() tasks in my app lead the flow to the menu and it will be in the memory forever anyways.
b- It is the very first Activity to be loaded. Hence, the adview will be always ready and available for the other activities.
In the MainActivity (or the activity that you choose to contain the memory leak), add the following at the bottom:
public static MainActivity MemoryLeakContainerActivity;
public MainActivity() {
super();
if (MemoryLeakContainerActivity != null) {
throw new IllegalStateException("MemoryLeakContainerActivity is already created");
}
MemoryLeakContainerActivity = this;
}
Thats it! No more memory leaks in my app! LeakCanary reports are clean.
NOTE: This is a WORKAROUND, not a SOLUTION. The SOLUTION should come from GOOGLE only: One simple command like "mAdView.destroy()" should do the work, right?
This issue is being discussed in Google Forums: https://groups.google.com/forum/#!topic/google-admob-ads-sdk/9IyjqdmeumM
Upvotes: 1
Reputation: 908
In my case, it was caused by using MobileAds initialization code with this
in a lambda scope. After changing this
to applicationContext
, it was fixed.
Before:
MobileAds.initialize(this, "ca-app-pub-0000000000000000~0000000000")
After:
MobileAds.initialize(applicationContext, "ca-app-pub-0000000000000000~0000000000")
Upvotes: 1
Reputation: 58934
I think passing App Context to AdView is not a solution really. Because issue is that AdView is not freeing up Context Object. So it will not free up App Context if you pass it.
So below can be a workaround to prevent leak truely.
@Override
protected void onDestroyView() {
super.onDestroy();
if (adview != null && adview.getParent() != null) // inflated by XML and remove here from parent
((ViewGroup) adview.getParent()).removeView(adview);
adview.destroy();
adview = null;
}
onDestroyView
onDestroyView
, that is called when view is destroyed, so you should exactly destroy AdView at this place.onDestroy
(After onDestroyView) so it is a leak. Because AdView is still there after Fragment View is destroyed.onDestroyView
method, view is destroyed in onDestroy
in Activity. So there we clear objects in onDestroy
.But if we are using adView in XML then this cant be done.
Because you want inflate AdView from XML so removing View in onDestroy
will do the job for you.
onDestroyView
Make AdView null
in onDestroy
. Thus AdView object will not be referred anymore. and it will be cleaned by Garbage collector.
I hope this information is useful for you. :)
Upvotes: 4
Reputation: 1470
Same problem, AdView have an internal variable (strong reference) holding onto the context, which is my Activity, causing a leak of the Activity instance.
My dependency is com.google.android.gms:play-services-ads:8.3.0
A workaround is to supply the Application Context when creating the AdView instance.
AdView adview = new AdView(getApplicationContext());
Upvotes: 15
Reputation: 2066
Can you try the following:
first remove your adView
from its container and then call destroy(), i.e.
ViewParent parent = adView.getParent();
if (parent != null && parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(adView);
}
adView.destroy();
adView = null;
Upvotes: 2