Leakcanary: Platform ActivityManager.mContext leaking my activity?

Created on 9 Jun 2015  路  9Comments  路  Source: square/leakcanary

Seems more like a false positive than a true leak. When my activity is destroyed, LeakCanary raise a flag that my activity got leaked.

When I check who's holding a reference to it, for my surprise, is AOSP static property ActivityManager.mContext. I can tell that such field is outside the scope of my app, at least directly. Unless I'm doing something that, ultimately, results in an assignment of a reference to my activity from that field.

Any clue that you guys can give me would be awesome.

help wanted leak

Most helpful comment

Given hack fixes the problem for us:

In Application:

@Override
public void onCreate() {
    registerActivityLifecycleCallbacks(new ActivityManagerDetacher());
}

Remove reference to activity from static field

class ActivityManagerDetacher implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityDestroyed(Activity activity) {
        try {
            detachActivityFromActivityManager(activity);
        } catch (NoSuchFieldException e) {
            Assert.fail("Samsung activity leak fix has to be removed as ActivityManager field has changed", e);
        } catch (IllegalAccessException e) {
            Assert.fail("Samsung activity leak fix did not work, probably activity has leaked", e);
        }
    }

    /**
     * On Samsung, Activity reference is stored into static mContext field of ActivityManager
     * resulting in activity leak.
     */
    private void detachActivityFromActivityManager(Activity activity) throws
            NoSuchFieldException, IllegalAccessException {
        ActivityManager activityManager = (ActivityManager) activity.
                getSystemService(Context.ACTIVITY_SERVICE);

        Field contextField = activityManager.getClass().getDeclaredField("mContext");

        int modifiers = contextField.getModifiers();
        if ((modifiers | Modifier.STATIC) == modifiers) {
            // field is static on Samsung devices only
            contextField.setAccessible(true);

            if (contextField.get(null) == activity) {
                contextField.set(null, null);
            }
        }
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityStarted(Activity activity) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    @Override
    public void onActivityPaused(Activity activity) {}

    @Override
    public void onActivityStopped(Activity activity) {}

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
}

All 9 comments

I've seen this on the SGS6 with 5.0, it seems Samsung keeps a static reference to the context. You can work around it by calling:

        ActivityManager am = (ActivityManager) app.getSystemService(Context.ACTIVITY_SERVICE);

from your ApplicationContext very early on (such as from your Application object's onCreate), only the first context is held onto, and holding the application context won't cause a leak.

I'm seeing this on the S6 Edge (SAMSUNG-SM-G925A zerolteuc) running 5.0.2 as well. Unfortunately, the workaround that @teslacoil provided does not work for me.

We can't do anything without leak traces.

I'm consistently getting this on a Galaxy S6 running 5.1.1

Definitely a Samsung leak, since looking at AOSP source, mContext is not static but on the trace it is
static mContext = com.weheartit.app.MainActivity [id=0x12d7db00]

Still uploading a stacktrace to dropbox...

In com.weheartit:4.5.0-DEBUG:16441.
* com.weheartit.app.MainActivity has leaked:
* GC ROOT static android.app.ActivityManager.mContext
* leaks com.weheartit.app.MainActivity instance

* Reference Key: 9edbc1d1-8e87-42da-ab09-1b76a52a8f7e
* Device: samsung samsung SM-G920I zerofltedv
* Android Version: 5.1.1 API: 22 LeakCanary: 1.3.1
* Durations: watch=7251ms, gc=217ms, heap dump=3843ms, analysis=21289ms

* Details:
* Class android.app.ActivityManager
|   static $staticOverhead = byte[] [id=0x70f7c391;length=496;size=512]
|   static AMS_POLICY_ENFORCING = java.lang.String [id=0x6f62adb8]
|   static BROADCAST_FAILED_USER_STOPPED = -2
|   static BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1
|   static BROADCAST_SUCCESS = 0
|   static COMPAT_MODE_ALWAYS = -1
|   static COMPAT_MODE_DISABLED = 0
|   static COMPAT_MODE_ENABLED = 1
|   static COMPAT_MODE_NEVER = -2
|   static COMPAT_MODE_TOGGLE = 2
|   static COMPAT_MODE_UNKNOWN = -3
|   static INTENT_SENDER_ACTIVITY = 2
|   static INTENT_SENDER_ACTIVITY_RESULT = 3
|   static INTENT_SENDER_BROADCAST = 1
|   static INTENT_SENDER_SERVICE = 4
|   static META_HOME_ALTERNATE = java.lang.String [id=0x6f722be8]
|   static MOVE_TASK_NO_USER_ACTION = 2
|   static MOVE_TASK_WITH_HOME = 1
|   static PROCESS_STATE_BACKUP = 5
|   static PROCESS_STATE_CACHED_ACTIVITY = 11
|   static PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12
|   static PROCESS_STATE_CACHED_EMPTY = 13
|   static PROCESS_STATE_HEAVY_WEIGHT = 6
|   static PROCESS_STATE_HOME = 9
|   static PROCESS_STATE_IMPORTANT_BACKGROUND = 4
|   static PROCESS_STATE_IMPORTANT_FOREGROUND = 3
|   static PROCESS_STATE_LAST_ACTIVITY = 10
|   static PROCESS_STATE_PERSISTENT = 0
|   static PROCESS_STATE_PERSISTENT_UI = 1
|   static PROCESS_STATE_RECEIVER = 8
|   static PROCESS_STATE_SERVICE = 7
|   static PROCESS_STATE_TOP = 2
|   static RECENT_IGNORE_HOME_STACK_TASKS = 8
|   static RECENT_IGNORE_UNAVAILABLE = 2
|   static RECENT_INCLUDE_PROFILES = 4
|   static RECENT_WITH_EXCLUDED = 1
|   static REMOVE_ALL_RECENT_TASKS = 8
|   static REMOVE_TASK_EXCEPT_RECENTS = 16
|   static REMOVE_TASK_IMMEDIATELY = 4
|   static START_CANCELED = -6
|   static START_CANCELED_BY_TEMPERATURE = -8
|   static START_CLASS_NOT_FOUND = -2
|   static START_DELIVERED_TO_TOP = 3
|   static START_FLAG_DEBUG = 2
|   static START_FLAG_ONLY_IF_NEEDED = 1
|   static START_FLAG_OPENGL_TRACES = 4
|   static START_FORWARD_AND_REQUEST_CONFLICT = -3
|   static START_INTENT_NOT_RESOLVED = -1
|   static START_NOT_ACTIVITY = -5
|   static START_NOT_VOICE_COMPATIBLE = -7
|   static START_PERMISSION_DENIED = -4
|   static START_RETURN_INTENT_TO_CALLER = 1
|   static START_RETURN_LOCK_TASK_MODE_VIOLATION = 5
|   static START_SUCCESS = 0
|   static START_SWITCHES_CANCELED = 4
|   static START_TASK_TO_FRONT = 2
|   static TAG = java.lang.String [id=0x6f6e9660]
|   static USER_OP_IS_CURRENT = -2
|   static USER_OP_SUCCESS = 0
|   static USER_OP_UNKNOWN_USER = -1
|   static gMaxRecentTasks = -1
|   static localLOGV = false
|   static mContext = com.weheartit.app.MainActivity [id=0x12d7db00]
* Instance of com.weheartit.app.MainActivity
|   static $staticOverhead = byte[] [id=0x12fb7001;length=8;size=24]
|   static TAG = java.lang.String [id=0x132cd4a0]
|   accountManager = com.weheartit.accounts.WhiAccountManager2 [id=0x13306910]
|   branch = com.weheartit.analytics.BranchManager [id=0x1330c4c0]
|   debugUtils = com.weheartit.util.debug.DebugUtilsImpl [id=0x13301700]
|   deepLinkManager = com.weheartit.util.DeepLinkManager [id=0x13301ba0]
|   deviceUtils = com.weheartit.push.WhiDeviceUtils [id=0x13301720]
|   gcmHelper = com.weheartit.push.GCMHelper [id=0x13307ae0]
|   havePlayServices = true
|   preferences = com.weheartit.accounts.UserToggles [id=0x13301840]
|   session = com.weheartit.accounts.WhiSession [id=0x13025560]
|   DEBUG_ELASTIC = true
|   isElasticEnabled = true
|   mActionBar = null
|   mActivityInfo = android.content.pm.ActivityInfo [id=0x12c4e4f0]
|   mActivityTransitionState = android.app.ActivityTransitionState [id=0x132cc400]
|   mAllLoaderManagers = android.util.ArrayMap [id=0x1332dc20]
|   mApplication = com.weheartit.WeHeartItApplication [id=0x12c95be0]
|   mCalled = true
|   mChangeCanvasToTranslucent = false
|   mChangingConfigurations = false
|   mCheckedForLoaderManager = true
|   mComponent = android.content.ComponentName [id=0x12c73670]
|   mConfigChangeFlags = 0
|   mContainer = android.app.Activity$1 [id=0x132b2990]
|   mCurrentConfig = android.content.res.Configuration [id=0x13246780]
|   mDecor = null
|   mDefaultKeyMode = 0
|   mDefaultKeySsb = null
|   mDestroyed = true
|   mDoReportFullyDrawn = false
|   mEmbeddedID = null
|   mEnableDefaultActionBarUp = false
|   mEnterTransitionListener = android.app.SharedElementCallback$1 [id=0x70ad7620]
|   mExitTransitionListener = android.app.SharedElementCallback$1 [id=0x70ad7620]
|   mFeatureContextMenuListener = android.app.Activity$FeatureContextMenuListener [id=0x132b2960]
|   mFinished = true
|   mFlipfont = 0
|   mFragments = android.app.FragmentManagerImpl [id=0x132b0900]
|   mHandler = android.os.Handler [id=0x132cd540]
|   mIdent = 763020128
|   mInjectionManager = android.app.im.InjectionManager [id=0x132be730]
|   mInstanceTracker = android.os.StrictMode$InstanceTracker [id=0x132b29a0]
|   mInstrumentation = android.app.Instrumentation [id=0x12c63a10]
|   mIntent = android.content.Intent [id=0x12c91060]
|   mLastNonConfigurationInstances = null
|   mLauncherBooster = null
|   mLoaderManager = null
|   mLoadersStarted = false
|   mMainThread = android.app.ActivityThread [id=0x12c6b100]
|   mManagedCursors = java.util.ArrayList [id=0x132cd4e0]
|   mManagedDialogs = null
|   mMenuInflater = null
|   mMultiWindowStyle = com.samsung.android.multiwindow.MultiWindowStyle [id=0x132be940]
|   mParent = null
|   mPerfActivityList = java.lang.String[] [id=0x132b2b20;length=0]
|   mPreferredOrientation = -2
|   mPreventEmbeddedTabs = false
|   mReferrer = null
|   mResultCode = 0
|   mResultData = null
|   mResumed = false
|   mScreenChangeListener = null
|   mSearchManager = null
|   mShrinkRequestListener = null
|   mStackedHeight = -1
|   mStartedActivity = false
|   mStopped = true
|   mSubDecor = null
|   mSubWindow = null
|   mSubWindowAdded = false
|   mSubWindowDummpy = null
|   mTemporaryPause = false
|   mTitle = java.lang.String [id=0x12e69700]
|   mTitleColor = 0
|   mTitleReady = true
|   mToken = android.os.BinderProxy [id=0x12c8b1c0]
|   mTranslucentCallback = null
|   mUiThread = java.lang.Thread [id=0x87b04e20]
|   mVisibleBehind = false
|   mVisibleFromClient = true
|   mVisibleFromServer = true
|   mVoiceInteractor = null
|   mWindow = com.android.internal.policy.impl.PhoneWindow [id=0x12f4fad0]
|   mWindowAdded = true
|   mWindowManager = android.view.WindowManagerImpl [id=0x132eb440]
|   myName = java.lang.String [id=0x132cd4c0]
|   mInflater = com.android.internal.policy.impl.PhoneLayoutInflater [id=0x132e2ac0]
|   mOverrideConfiguration = null
|   mResources = android.content.res.Resources [id=0x12c4f030]
|   mTheme = android.content.res.Resources$Theme [id=0x132eb460]
|   mThemeResource = 2131362068
|   mBase = android.app.ContextImpl [id=0x13246660]

PR welcome to ignore leak, with comment that explains how to go around it.

Given hack fixes the problem for us:

In Application:

@Override
public void onCreate() {
    registerActivityLifecycleCallbacks(new ActivityManagerDetacher());
}

Remove reference to activity from static field

class ActivityManagerDetacher implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityDestroyed(Activity activity) {
        try {
            detachActivityFromActivityManager(activity);
        } catch (NoSuchFieldException e) {
            Assert.fail("Samsung activity leak fix has to be removed as ActivityManager field has changed", e);
        } catch (IllegalAccessException e) {
            Assert.fail("Samsung activity leak fix did not work, probably activity has leaked", e);
        }
    }

    /**
     * On Samsung, Activity reference is stored into static mContext field of ActivityManager
     * resulting in activity leak.
     */
    private void detachActivityFromActivityManager(Activity activity) throws
            NoSuchFieldException, IllegalAccessException {
        ActivityManager activityManager = (ActivityManager) activity.
                getSystemService(Context.ACTIVITY_SERVICE);

        Field contextField = activityManager.getClass().getDeclaredField("mContext");

        int modifiers = contextField.getModifiers();
        if ((modifiers | Modifier.STATIC) == modifiers) {
            // field is static on Samsung devices only
            contextField.setAccessible(true);

            if (contextField.get(null) == activity) {
                contextField.set(null, null);
            }
        }
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityStarted(Activity activity) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    @Override
    public void onActivityPaused(Activity activity) {}

    @Override
    public void onActivityStopped(Activity activity) {}

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
}

@kzaikin This solution seems work for me. Thanks

Got the same issue on Samsung S5 (SM-G900F) running Android 6.0.1. The solution works perfectly.

Was this page helpful?
0 / 5 - 0 ratings