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();
}
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.
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:
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.
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.)
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. :-/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With