19

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.

Alex Perevozchykov
  • 2,211
  • 22
  • 19

5 Answers5

15

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());
Rahul Sainani
  • 3,437
  • 1
  • 34
  • 48
dvd
  • 1,470
  • 1
  • 16
  • 24
  • 3
    But if we are using adView in XML then this cant be done. I tried destroying the adView onDestroy but didnt help. – Kshitij Aggarwal Feb 12 '16 at 05:15
  • @Funkyidol ya layout inflater is likely supplying the activity as the context to AdView in layout xml. Adding a view to viewgroup programatically isn't that terrible. – dvd Feb 13 '16 at 20:30
  • This is the only thing that fixed it for me. Shame that I can't add it in the XML layout now... – eliasbagley Apr 05 '16 at 05:07
  • I add the AdView programmatically and remove it from the parent view in the onStop() method of my fragment, as here it's mentioned: https://groups.google.com/forum/#!topic/google-admob-ads-sdk/DnICRC67ASI And I use the the application context as well to create the instance of AdView – Camino2007 Apr 14 '16 at 15:26
  • This may not work for mediating some ad networks because some mediation adapter will still try to cast it to Activity context. And you will see this in logcat - W/Ads: Could not request banner ad from adapter. java.lang.ClassCastException – Alan May 30 '16 at 05:13
  • @Alan fair point. The wording in my answer was wrong, it shouldn't be solution, but a workaround. Google need to fix their memory leak. – dvd Jun 01 '16 at 05:16
  • If we pass the applicationContext correctly, the leaks disappear. When passing the context we must observe the lifecycle passing to the context from there, otherwise the applicationContext is destroyed before causing the leak. No need to handle AdView – AllanRibas Mar 24 '22 at 09:58
4

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;
}

1. Destroy Adview in onDestroyView

  • Fragment lifecycle has method onDestroyView, that is called when view is destroyed, so you should exactly destroy AdView at this place.
  • In your case you are destroying AdView in onDestroy (After onDestroyView) so it is a leak. Because AdView is still there after Fragment View is destroyed.
  • An Activity has NO onDestroyView method, view is destroyed in onDestroy in Activity. So there we clear objects in onDestroy.

2. Remove AdView from View programmatically.

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.

3. Make AdView NULL in 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. :)

Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
  • 1
    Thanks for you answer. I tried with latest ads SDK from google and apply your code but it's still leak. The LeakCanary show the activity which contains adView has been hold by `com.google.android.gms.internal.ads.zzml`. Shame on google, 9 years from first ads SDK version was release but they do not fix this issue. – Son Truong Oct 26 '18 at 10:13
  • So did you try all three options I suggested. – Khemraj Sharma Oct 27 '18 at 10:40
  • I thought all I could, now only Google can do the solution :), perhaps they are holding some values at app level. – Khemraj Sharma Oct 27 '18 at 10:44
  • 1
    Update from June 2020: AdView is still generating memory leaks everytime I use it. I can see it even in the most simplest activities (after executing adview.destroy() in Activity OnDestroy()). My last testcase was performed in a BluView1 with Android 9 (Pie). – Pablo Alfonso Jun 16 '20 at 20:10
  • Update from 2023.. This solution is good, I tried it and first few times I didnt get any leaks , but after that again leaks.. wether ad loaded or not, always leaked – China fox Feb 10 '23 at 10:04
2

Can you try the following:

  • move your logic in onDestroyView()
  • 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;
    
Dimitar Genov
  • 2,056
  • 13
  • 11
  • 2
    Unfortunately still there is a memory leak. Looks like adView holds reference on activity. – Alex Perevozchykov Nov 03 '15 at 21:52
  • I've never encountered any memory leaks with AdView and we are using DFP all over. Can you ensure you are using latest Play services. I am positive it is something else but cannot say without seeing some source code. – Dimitar Genov Nov 03 '15 at 21:55
  • Did you test apps with LeakCanary or by heap dump ? I'm using latest 8.1.0 play services – Alex Perevozchykov Nov 03 '15 at 21:59
  • 1
    Yes, I use LeakCanary. Several (potential) differences here though: 1) We use DFP, i.e. `com.google.android.gms.ads.doubleclick.PublisherAdView` 2) I am not using fragments for the ads but adding and dealing directly in the activity view tree – Dimitar Genov Nov 03 '15 at 22:06
  • I have the same issue, Have you fixed it ? – Omar Hassan Jan 04 '16 at 13:26
1

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")
Joonsoo
  • 788
  • 1
  • 13
  • 15
0

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:

  1. 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>
    
  2. Then define a RelativeLayout variable (adscontainer) in your activity next to the mAdView:

       private AdView mAdView;
       private RelativeLayout adscontainer; 
    
  3. 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.

  4. In onDestroy(), add the following:

       if (mAdView != null){
            mAdView.setAdListener(null);
            adscontainer.removeAllViews();
            adscontainer = null;
            mAdView.destroy(); 
            mAdView = null;               
           }
    
  5. 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.

  6. 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;
     }
    
  7. 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

Pablo Alfonso
  • 2,317
  • 1
  • 18
  • 24