Alex Perevozchykov
Alex Perevozchykov

Reputation: 2481

AdView causes memory leak

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

Answers (5)

Pablo Alfonso
Pablo Alfonso

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:

  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

Upvotes: 1

Joonsoo
Joonsoo

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

Khemraj Sharma
Khemraj Sharma

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

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. :)

Upvotes: 4

dvd
dvd

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

Dimitar Genov
Dimitar Genov

Reputation: 2066

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;
    

Upvotes: 2

Related Questions