Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is causing Google AdMob to leak ServiceConnection?

I am using AdMob in a fragment. Sometimes I see the following stack

10-23 14:27:38.916: E/ActivityThread(21250): Activity com.applegrew.app.skywifiremote.MainActivity has leaked ServiceConnection com.google.android.gms.common.b@420e82e8 that was originally bound here
10-23 14:27:38.916: E/ActivityThread(21250): android.app.ServiceConnectionLeaked: Activity com.applegrew.app.skywifiremote.MainActivity has leaked ServiceConnection com.google.android.gms.common.b@420e82e8 that was originally bound here
10-23 14:27:38.916: E/ActivityThread(21250):    at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:979)
10-23 14:27:38.916: E/ActivityThread(21250):    at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:873)
10-23 14:27:38.916: E/ActivityThread(21250):    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1690)
10-23 14:27:38.916: E/ActivityThread(21250):    at android.app.ContextImpl.bindService(ContextImpl.java:1673)
10-23 14:27:38.916: E/ActivityThread(21250):    at android.content.ContextWrapper.bindService(ContextWrapper.java:517)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.identifier.a.b(SourceFile:179)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.identifier.a.a(SourceFile:207)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.a.t.d(SourceFile:83)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.a.t.b(SourceFile:131)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.a.q.a(SourceFile:258)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.a.q.a(SourceFile:195)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.internal.k.a(SourceFile:76)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.internal.request.c.f_(SourceFile:99)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.internal.util.b.run(SourceFile:17)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.internal.util.d.call(SourceFile:29)
10-23 14:27:38.916: E/ActivityThread(21250):    at com.google.android.gms.ads.internal.util.e.call(SourceFile:49)
10-23 14:27:38.916: E/ActivityThread(21250):    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
10-23 14:27:38.916: E/ActivityThread(21250):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
10-23 14:27:38.916: E/ActivityThread(21250):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
10-23 14:27:38.916: E/ActivityThread(21250):    at java.lang.Thread.run(Thread.java:841)

From stack trace it looks like the source of leak is AdMob code. However, in my fragment I have code to destroy the AdMob view when the fragment is destroyed.

Snippet from my fragment.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    initAd();
}

private void initAd() {
    mAdView = (AdView) getView().findViewById(R.id.remote_pager_ad);
    if (mAdView != null) {
        AdRequest adRequest = new AdRequest.Builder().addTestDevice(
                AdRequest.DEVICE_ID_EMULATOR).build();
        mAdView.loadAd(adRequest);
    }
}

@Override
public void onPause() {
    mAdView.pause();
    super.onPause();
}

@Override
public void onResume() {
    super.onResume();
    mAdView.resume();
}

@Override
public void onDestroy() {
    mAdView.destroy();
    super.onDestroy();
}
like image 445
AppleGrew Avatar asked Oct 23 '14 09:10

AppleGrew


People also ask

What is Google AdMob and how does it work?

Google AdMob makes it easy for developers to earn money from their mobile apps with high-quality ads. AdMob maximizes the value of every impression by combining global advertiser demand, innovative ad formats, and advanced app monetization technology.

What happens when an ad fails to load?

When an ad fails to load, a failure callback is called which provides a LoadAdError object. For any ad format, if the ad fails to load, the following callback is called: The following code snippet retrieves error information when an ad fails to load:

Why AdMob for Firebase?

Get smarter insights: By offering seamless integration with Google Analytics for Firebase, AdMob offers smarter analytics and reporting features so you can better understand how users interact with ads and optimize their lifetime value.


2 Answers

I was having a similar issue with AdMob (Google Play Services) ads leaking huge amounts of memory. Used MAT to find that the problem was every gms ad was being retained by my Application instance, in the sComponentCallbacks array. So I override registerComponentCallbacks() and unregisterComponentCallbacks() to keep track of instances that are registering themselves but never unregistering (when I assume they should have done so). This code example assumes only the .gms.ads package is problematic, but if you find others that are causing a similar leak, you can add those packages to the list as well:

public class MyApplication extends Application {
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void registerComponentCallbacks(ComponentCallbacks callback) {
        super.registerComponentCallbacks(callback);
        ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksRegistered(callback);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
        ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksUnregistered(callback);
        super.unregisterComponentCallbacks(callback);
    }

    public void forceUnregisterComponentCallbacks() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.unregisterAll(this);
        }
    }


    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private static class ComponentCallbacksBehavioralAdjustmentToolIcs {
        static ComponentCallbacksBehavioralAdjustmentToolIcs INSTANCE = new ComponentCallbacksBehavioralAdjustmentToolIcs();

        private WeakHashMap<ComponentCallbacks, ApplicationErrorReport.CrashInfo> mCallbacks = new WeakHashMap<>();
        private boolean mSuspended = false;

        public void onComponentCallbacksRegistered(ComponentCallbacks callback) {
            Throwable thr = new Throwable("Callback registered here.");
            ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(thr);

            if (BuildConfig.DEBUG) Log.w(TAG, "registerComponentCallbacks: " + callback, thr);

            if (!mSuspended) {
                if (callback.getClass().getName().startsWith("com.google.android.gms.ads")) {
                    mCallbacks.put(callback, ci);
                }
                // TODO: other classes may still prove to be problematic?  For now, only watch for .gms.ads, since we know those are misbehaving
            } else {
                if (BuildConfig.DEBUG) Log.e(TAG, "ComponentCallbacks was registered while tracking is suspended!");
            }
        }

        public void onComponentCallbacksUnregistered(ComponentCallbacks callback) {
            if (!mSuspended) {
                if (BuildConfig.DEBUG) {
                    Log.i(TAG, "unregisterComponentCallbacks: " + callback, new Throwable());
                }

                mCallbacks.remove(callback);
            }
        }

        public void unregisterAll(Context context) {
            mSuspended = true;

            for (Map.Entry<ComponentCallbacks, ApplicationErrorReport.CrashInfo> entry : mCallbacks.entrySet()) {
                ComponentCallbacks callback = entry.getKey();
                if (callback == null) continue;

                if (BuildConfig.DEBUG) {
                    Log.w(TAG, "Forcibly unregistering a misbehaving ComponentCallbacks: " + entry.getKey());
                    Log.w(TAG, entry.getValue().stackTrace);
                }

                try {
                    context.unregisterComponentCallbacks(entry.getKey());
                } catch (Exception exc) {
                    if (BuildConfig.DEBUG) Log.e(TAG, "Unable to unregister ComponentCallbacks", exc);
                }
            }

            mCallbacks.clear();
            mSuspended = false;
        }
    }
}

Then in my BaseActivity's onPause() (or onDestroy()) method, I call my forceUnregisterComponentCallbacks() method:

@Override
public void onPause() {
    ((MyApplication) getApplicationContext()).forceUnregisterComponentCallbacks()
    super.onPause();
}

Note that ComponentCallbacks was introduced in ICS, so if you're seeing problems on versions before ICS, then this is not the problem.

(I also realize that this does not address the exact problem identified in the OP, as that has to do with bindService(), not ComponentCallbacks. But it saved us from a pretty major memory leak that forced us to disable AdMob entirely until a hotfix could be released.)

like image 135
Joe Avatar answered Oct 08 '22 05:10

Joe


It's seen like you need to unregister the Service before your Activity lost the context! This's the same issue for Dialog's when you call dismiss after Activity has lost. :-/

like image 40
Felipe Bonezi Avatar answered Oct 08 '22 04:10

Felipe Bonezi