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.
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.
Most helpful comment
Given hack fixes the problem for us:
In Application:
Remove reference to activity from static field