MoPub Natives holding onto memory?


#1

Hi,

Hopefully someone can help me. I have integrated MoPub natives using their adapter and a listview. The natives load ok and display correctly. My problem is that MoPub seems to keep hold of the cache on each native that is loaded, and never releases it.

Even when I destroy the adapter onPause, it still keeps the cache in memory. It builds up to well over 100mb after 30-40 natives shown, and eventually just causes a crash because it never releases the memory.

Here is my dominator tree http://i.imgur.com/ZGdhY3q.png

As you can see at the top the mopub.network.Networking is taking up nearly 30mb alone. This is after I have called destroy on the adapter.

Am I missing something? Why is it holding onto it?

Thanks


#2

I thought I would just test the moPub SDK sample app and it does the exact same thing? Surely this is not correct? Does mopub never release the cache? This is always going to cause problems if it does not.


#3

I played around with the SDK this morning. If anyone else is having the same problem. You can call Networking.clearForTesting(); when destroying the adapter. It will clear the cache.


#4

Hello Scott, I am facing this issue now. I am destroying the nativeAd and removing other listeners. But i finally found out that the issue is with the LruCache mCache from Networking.java class holding on to the Bitmap. Even if garbage collected it is not cleared. The worst is my app is already live and this issue killed me. My question is does Networking.clearForTesting() works? Can I use if for live app?


#5

One thing I completely forgot about was that MoPub will use a 30mb cache in memory by default. So for me my app was running at 50mb memory and went up to 80-90mb but did not go further.

(The following code is for creating a native ad without using an adapter). If you want to use the adapter, following the official instructions as they work ok, and memory only goes up by 30-40mb max.

I can show you my code, hopefully it may help you :slight_smile:

First I made sure my fragment implemented MoPubNative.MoPubNativeNetworkListener:

public class myFragment extends Fragment implements MoPubNative.MoPubNativeNetworkListener {

Then this following code creates the native and adds it to a container in my layout after I call nativeInit() . The nativeAdContainer is a LinearLayout inside my fragment layout that I use to hold the native ad.

public void nativeInit() {
    if (null != getActivity()) {
        MoPubNative mMoPubNative = new MoPubNative(getActivity(), "yourPlacementID", this);

        ViewBinder viewBinder = new ViewBinder.Builder(R.layout.i_native_banner_ad)
                    .mainImageId(R.id.native_banner_image)
                    .iconImageId(R.id.native_banner_logo)
                    .titleId(R.id.native_banner_title)
                    .textId(R.id.native_banner_description)
                    .privacyInformationIconImageId(R.id.native_banner_ad_privacy_information_icon_image)
                    .callToActionId(R.id.native_banner_cta)
                    .build();
       
        MoPubStaticNativeAdRenderer adRenderer = new MoPubStaticNativeAdRenderer(viewBinder);
        mMoPubNative.registerAdRenderer(adRenderer);

        final EnumSet<RequestParameters.NativeAdAsset> desiredAssets = EnumSet.of(
                RequestParameters.NativeAdAsset.TITLE,
                RequestParameters.NativeAdAsset.TEXT,
                RequestParameters.NativeAdAsset.ICON_IMAGE,
                RequestParameters.NativeAdAsset.CALL_TO_ACTION_TEXT);

        RequestParameters requestParameters = new RequestParameters.Builder()
                .desiredAssets(desiredAssets)
                .build();

        mMoPubNative.makeRequest(requestParameters);

    }
}

@Override
public void onNativeLoad(NativeAd nativeAd) {
    LLog.e(TAG, "Native Loaded");
    if (null != getActivity()) {
        View mView = nativeAd.createAdView(getActivity(), null);
        if (null != mView) {
            nativeAd.renderAdView(mView);
            nativeAd.prepare(mView);
            if (null != nativeAdContainer) {
                nativeAdContainer.removeAllViews();
                nativeAdContainer.addView(mView);
                nativeAdContainer.setVisibility(View.VISIBLE);
            }
        }
    }
}

@Override
public void onNativeFail(NativeErrorCode errorCode) {
    if (null != nativeAdContainer) {
        nativeAdContainer.removeAllViews();
        nativeAdContainer.setVisibility(View.GONE);
    }
}

With this code my memory usage does not go above 80mb from 50mb because MoPub only uses 30mb cache by default.

I went a step further because I found onPause the memory was not released. So I made the following code in my OnPause method of the fragment.

        if (null != nativeAdContainer) {
            nativeAdContainer.removeAllViews();
            Networking.clearForTesting();
        }

So on pause the objects are forced to be released and on the next garbage collection the memory is released.

And yes Networking.clearForTesting() does work in a live version, however not sure if they will always have this method in the SDK in the future. It’s only optional if you want to keep memory usage low onPause.

Hope it helps!


#6

Hello Scott, Thank you for your reply. This is really helpful. The Networking.clearForTesting() works. But now the DiskBasedCache is filled keeping the reference to the mainImageView of the native ad view. This is taking up much of the heap memory, same as the LruCache. Below are some references from MAT. Actually my service runs in a background. When the users navigates to Running Services in Settings, they may panic seeing much unreleased memory. This is a big issue for me. I don’t know how to acquire a reference to diskbasedCache to clear it when not needed.

Could you help me with this?


#7

Are you using the MoPub adapter for your code? I am actually having some trouble myself with the adapter. I tested their sample and it works good, however when I use my adapter on it, things start to go bad and it holds onto memory.

I will be working on it tomorrow, maybe I can find something.

I’m pretty sure MoPub has a default value of 10mb for the disk cache. There must be a leak somewhere else. It seems like in your case, it is creating a new diskcache each time.

One thing that may help you if you haven’t done so already is to check that you are destroying the MoPub adapter onDestroy, like mopubadapter.destroy();


#8

I am not using the adapter. I am just creating the adview on onNativeLoad and add it to my parent view. Similar approach to yours.

Also I destroy the mopubNative by calling destroy it it. Also I checked the SDK and found that calling the MoPubNative.destroy() clears the MoPubNativeNetworkListener and destroy the corresponding nativeAd by calling NativeAd.destroy() as well.

But using the Networking.clearForTesting() actually cleared the LruCache. But the disk cache started to fill. My devices has 4GB of RAM probably that’s the reason the disk cache may be high. But I am still finding a way to clear the disk cache.

I have written a mail with the link to this thread to mopub.

EDIT: I still haven’t found why there are too many disk cache instances. Calling MoPubNative.destroy() is the issue?


#9

The issue seems to be with the line MoPubNative.clearForTesting(). First I tried MoPubNative.clearForTesting() without adding the code MoPubNative.destroy(). I call the clearForTesting method when the view is not going to be displayed to user. (I am not using Activity, but adding a custom overlay view into a window). So by doing this. there are multiple instances of DiskBasedCache.
But if instead i try only the MoPubNative.destroy without the clearForTesting method. There is a single instance of network.Networking class which holds the imageCache. Actualy myheap is big so the heap is not exceeded beyond 115-120 MB.
But when there are multiple instances of the DiskBasedCache the heap goes beyond 240MB and I am sure its a leak. I have attached screens for both scenarios. I suggest Not to use the clearForTesting() method and let the api handle the cache.

DiskBasedCache:

Single instance of imageCache:


#10

Yeah I mean you shouldn’t have to use the clearfortesting method at all. The cache shouldn’t go higher than 30-40mb. In my code it doesn’t. I also don’t use the native.destroy method. It seems once I remove the native ad from the container, it removes any references left.

Are you sure you are not leaving any references to the window you are creating somewhere, even after closing it, something may be stopping everything from getting garbage collected. Maybe re-creating a service each time, but not first stopping the original service?

Are you using all local variables when creating the native ad? For example:

MoPubNative mMoPubNative = new MoPubNative(getActivity(), "yourPlacementID", this);

instead of:

private MoPubNative mMoPubNative;

mMoPubNative = new MoPubNative(getActivity(), "yourPlacementID", this);

I had problems when using the second code.


#11

Yes. I am clearing everything when the view is removed from window. Listeners and context. I don’t hold explicit reference to any of this once the view is removed. Also I verified the same with the heap Viewer and MAT. there are no references point to my context or Activity. Even with LeakCanary.
But the only thing i find is that clearForTesting() does create new instances of DiskBasedCache. I call this while freeing the resources of the view I am adding to the window. So every time the view is removed from Window the method is called. I tried several times and I am able to catch 183 instances of the DiskBasedCache which increases the heap size but never GC’ed. It went beyond 240MB in my heap which is pathetic. But without using this call my heap stays at 115-120MB.
Also I am using the second approach for creating MoPubNative. This is because i clearly need to remove the Listener. By calling destroy on moPubNative instance the listener is freed. As you reference ‘this’ in you listener, i probably doubt that it keeps the reference until the MoPubNative.destroy is called by the API. which may happen when an entry associated with an ad is cleared from the Lru.
I hope you understand. In my case it kept the reference to the parent View until the Lru is Filled and the particular ad associated with the parent view is removed from Lru. So eveytime a view is displayed and destroyed 4-5MB of heap is filled and never reduced until it reaches say about 150 or 170MB.

below is the link to the MoPubNative Class. You can find the destroy() method and doc comments on it. I hope it helps.

MoPubNative.java

Calling destroy on MoPubNative calls the destroy method of the NativeAd as well. Below is the code for NativeAd.destroy().

  /**
 * Cleans up all {@link NativeAd} state. Call this method when the {@link NativeAd} will never be shown to a
 * user again.
 */
public void destroy() {
    if (mIsDestroyed) {
        return;
    }

    mBaseNativeAd.destroy();
    mIsDestroyed = true;
}

I hope this helps.


#12

Just writing this in case anybody else has trouble with the adapter, in my case, even though I was calling popBackStack() on the fragment, the memory was still not released. However calling popBackStackImmediate() fixed it.

To raaja090, I’m really not sure what could be cause it to be honest. It could be some strange bug with the window you are adding it to maybe, but if there is no references to it like you said, then the bug must be in the MoPub SDK somewhere. But you have used all the methods they have given you to destroy the ad.


#13

Yes Scott and I am sure the cache is working fine and is released at certain stage. Yet I destroy the nativeAd for extra benefit of releasing the MoPubNativenetworkListener.
I use Glide for image caching and loading. There seems to be a handy method to Skip Memory Cache and Trim Memory. I really hope this available for moPub as well, in future. For now there is no issues. Thanks for much needed info’s from your side.


#14

There is definitely a memory leak somewhere. Did you hear back from MoPub?


#15

I have been working on a workaround for this and so far it seems good. Only one DiskBasedCache was shown in memory and memory usage seems stable.

The idea is the use an adapter like the documentation however, only use a dummy item for it.

Here is my example:

private MoPubAdAdapter moPubAdAdapter;
private RequestParameters moPubRequestParameters;

        moPubListView = (ListView) rootView.findViewById(R.id.native_ad_list);
        moPubListView.setDivider(null);
        moPubListView.setVisibility(View.VISIBLE);

        MoPubNativeAdapter originalAdapter = new MoPubNativeAdapter(getActivity(), R.layout.dummy_native_list_item);
        originalAdapter.add("Dummy Item");

        moPubAdAdapter = new MoPubAdAdapter(getActivity(), originalAdapter, new MoPubNativeAdPositioning.MoPubServerPositioning());

        moPubAdAdapter.registerAdRenderer(new MoPubStaticNativeAdRenderer(
                new ViewBinder.Builder(R.layout.i_native_banner_ad)
                        .mainImageId(R.id.native_banner_image)
                        .iconImageId(R.id.native_banner_logo)
                        .titleId(R.id.native_banner_title)
                        .textId(R.id.native_banner_description)
                        .privacyInformationIconImageId(R.id.native_banner_ad_privacy_information_icon_image)
                        .callToActionId(R.id.native_cta)
                        .build()));

        moPubListView.setAdapter(moPubAdAdapter);

                if (moPubRequestParameters == null) {
                    final EnumSet<RequestParameters.NativeAdAsset> desiredAssets = EnumSet.of(
                            RequestParameters.NativeAdAsset.TITLE,
                            RequestParameters.NativeAdAsset.TEXT,
                            RequestParameters.NativeAdAsset.ICON_IMAGE,
                            RequestParameters.NativeAdAsset.CALL_TO_ACTION_TEXT);

                    moPubRequestParameters = new RequestParameters.Builder()
                            .desiredAssets(desiredAssets)
                            .build();
                }

                moPubAdAdapter.loadAds("PLACEMENT ID", moPubRequestParameters);

The adapter class is:

public class MoPubNativeAdapter extends ArrayAdapter {

private Context context;
private int layoutResourceId;
private View dummyView;

public MoPubNativeAdapter(Context context, int layoutResourceId) {
    super(context, layoutResourceId);
    this.layoutResourceId = layoutResourceId;
    this.context = context;
}

@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {

    if (dummyView == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        dummyView = inflater.inflate(layoutResourceId, parent, false);
    }

    return dummyView;
}
}

The dummy_native_list_item layout is:

<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1dp"/>

Just make sure in MoPub, you set the native ad placement start position to 0, and then turn off the ‘After last ad position…’ checkbox.

Try it out, hope it helps!


#16

Nice try Scott. I will try it from my end and will let you know. Also i just have a question. In my view i need to display the ads as soon as the view is visible to the user. by following the documentation and guide, the request is made only after the view is displayed. Which greatly reduces the Ad Impression except when on a high speed connection.
So my question is it good practice to make a request in background and get the view form the request and hold it on the memory? But when the user is about to see the view i will add the ad view to my parent view. I know that it may eat some memory, But i don’t know the best way to do this. Can you guide with this?
FYI i have a service running in background when my App is working. So I will utilize the Service to get the AdView. OnTrimMemory(), I will release the adView from reference.


#17

I don’t see why you couldn’t do that. As long as you don’t call .destroy on the native or the view then they should be ok to use. So long as the ad network you are using does not set a timeout limit of the click url.

You could maybe do the creation of the request and native ad view in your main activity and put the final native ad view in a list or map of some kind.

The only thing I would be worried about is if the click url’s had a timeout on them, but I doubt they do.


#18

Nice Point Scott. I haven’t verified it. I should do the same. I use Facebook Audience Network in MoPub mediation. Facebook allows 26 ads to be fetched an hour. SO I should check the refresh interval of the Facebook ad and Mopub d at the same time and should request ads accordingly Thank you for the musch important point.


#19

Hi @Scott and @raaja090,

If you have further questions about this, please reach out to our Support Team directly at support@mopub.com with a summary of your issue and they’d be glad to provide more details on this case.

Thanks!