Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leak in AccountManager$AmsTask #97

Closed
andkulikov opened this issue May 15, 2015 · 17 comments · Fixed by #103
Closed

Leak in AccountManager$AmsTask #97

andkulikov opened this issue May 15, 2015 · 17 comments · Fixed by #103

Comments

@andkulikov
Copy link

  • com.example.MyActivity has leaked:
  • GC ROOT android.accounts.AccountManager$AmsTask$Response.this$1
  • references android.accounts.AccountManager$7.mActivity (anonymous class extends android.accounts.AccountManager$AmsTask)
  • leaks com.example.MyActivity instance
  • Reference Key: 09311979-aa31-4567-97c8-86d28f6bba02
  • Device: HTC htc HTC One_M8 htc_europe
  • Android Version: 5.0.1 API: 21
  • Durations: watch=6901ms, gc=165ms, heap dump=2347ms, analysis=37277ms

and on all other tested devices with 4.4 or 5.x

AmsTask required and keeps reference to Activity.

@pyricau
Copy link
Member

pyricau commented May 15, 2015

Hi,

Can you provide the output of the same leak report with 1.3.1-SNAPSHOT? https://github.com/square/leakcanary/blob/master/CHANGELOG.md#version-131-snapshot

@andkulikov
Copy link
Author

    * com.example.Activity has leaked:
    * GC ROOT android.accounts.AccountManager$AmsTask$Response.this$1
    * references android.accounts.AccountManager$9.val$activity (anonymous class extends android.accounts.AccountManager$AmsTask)
    * leaks com.example.Activity instance

    * Reference Key: 56a89102-db5e-4572-be6d-3207171b9aec
    * Device: htc google Nexus 9 volantis
    * Android Version: 5.0.1 API: 21 LeakCanary: 1.3.1-SNAPSHOT
    * Durations: watch=5016ms, gc=197ms, heap dump=1494ms, analysis=8186ms

    * Details:
    * Instance of android.accounts.AccountManager$AmsTask$Response
    |   this$1 = android.accounts.AccountManager$9 [id=0x134cafb0]
    |   mDescriptor = java.lang.String [id=0x70f6ad78]
    |   mOwner = android.accounts.AccountManager$AmsTask$Response [id=0x1320b740]
    |   mObject = -1420858192
    * Instance of android.accounts.AccountManager$9
    |   this$0 = android.accounts.AccountManager [id=0x1320b500]
    |   val$accountType = java.lang.String [id=0x1320b260]
    |   val$activity = com.example.Activity [id=0x13003000]
    |   val$authTokenType = java.lang.String [id=0x1320b280]
    |   val$optionsIn = android.os.Bundle [id=0x1320b620]
    |   val$requiredFeatures = null
    |   mActivity = com.example.Activity [id=0x13003000]
    |   mCallback = com.example.AuthManager$2 [id=0x13286190]
    |   mHandler = null
    |   mResponse = android.accounts.AccountManager$AmsTask$Response [id=0x1320b740]
    |   this$0 = android.accounts.AccountManager [id=0x1320b500]
    |   callable = null
    |   outcome = null
    |   runner = null
    |   waiters = null
    |   state = 6
    * Instance of com.example.Activity
    |   handler_ = android.os.Handler [id=0x1307a480]
    |   onViewChangedNotifier_ = org.androidannotations.api.view.OnViewChangedNotifier [id=0x12f007a0]
    |   actionBarDrawerToggle = android.support.v7.app.ActionBarDrawerToggle [id=0x131b7580]
    |   addPopup = null
    |   avatarUrl = null
    |   breadcrumbsView = com.example.widget.breadcrumbs.BreadcrumbsView [id=0x136a4c00]
    |   clickedRootFolder = null
    |   contentFrame = android.widget.RelativeLayout [id=0x136a3400]
    |   currentFolder = null
    |   currentToast = null
    |   customFolderIdList = null
    |   dialogHelper = com.example.dialog.DialogHelper [id=0x13238660]
    |   drawerLayout = com.example.ExtendedDrawerLayout [id=0x136a2800]
    |   drawerLayoutContent = android.widget.FrameLayout [id=0x136a3000]
    |   drawerRightShadow = android.widget.FrameLayout [id=0x136c3c00]
    |   fileListHeaders = android.widget.LinearLayout [id=0x136a6c00]
    |   firstSortingController = com.example.sorting.LabelSortingControllerView [id=0x136a7000]
    |   flaggedRootFile = null
    |   floatingActionButton = com.example.fab.FloatingActionButton [id=0x136aa800]
    |   folderStack = java.util.Stack [id=0x1307a120]
    |   fragmentContainer = android.widget.FrameLayout [id=0x136aa000]
    |   leftDrawerList = com.example.ScrimInsetsFrameLayout [id=0x136c4c00]
    |   mainToolbarContent = android.widget.LinearLayout [id=0x136a4800]
    |   navDrawerPhoneContainer = android.widget.ScrollView [id=0x136c5000]
    |   navDrawerTabContainer = com.example.widget.NavDrawerTabContainer [id=0x136c4400]
    |   navDrawerView = com.example.widget.NavDrawerView_ [id=0x1373b000]
    |   offlineRootFile = null
    |   openFileBtn = android.widget.LinearLayout [id=0x136a5000]
    |   openFileBtnText = com.example.widget.font.RobotoMediumTextView [id=0x136a5800]
    |   personalRootFile = null
    |   progressBar = com.pnikosis.materialishprogress.ProgressWheel [id=0x136aa400]
    |   recentRootFile = null
    |   rightDrawer = com.example.info.FileInfoLayout_ [id=0x136c5400]
    |   rootFolder = null
    |   searchFragment = com.example.search.SearchFragment_ [id=0x12fb15b0]
    |   searchMenuItem = android.support.v7.internal.view.menu.MenuItemImpl [id=0x1377b580]
    |   searchToolbarContent = android.widget.LinearLayout [id=0x136a6000]
    |   secondSortingController = com.example.sorting.LabelSortingControllerView [id=0x136a8800]
    |   sharedRootFile = null
    |   sizeDelimiterView = com.example.widget.DynamicWidthView [id=0x13522940]
    |   smSortingImageView = android.widget.ImageView [id=0x136a5c00]
    |   snackBar = com.github.mrengineer13.snackbar.SnackBar [id=0x1371d430]
    |   snackBarContainer = android.widget.FrameLayout [id=0x136c4000]
    |   sortingManager = com.example.sorting.SortingManager [id=0x1320b8a0]
    |   thirdSortingController = com.example.sorting.LabelSortingControllerView [id=0x136a9400]
    |   toolbar = android.support.v7.widget.Toolbar [id=0x1369e400]
    |   toolbarContainer = android.widget.LinearLayout [id=0x136a3800]
    |   trashRootFile = null
    |   uploadHelper = com.example.UploadHelper [id=0x13238780]
    |   uploadedFileName = null
    |   changeFolderInProgress = false
    |   isUpdateDialogShown = false
    |   searchOpeningInProgress = false
    |   mDelegate = android.support.v7.app.ActionBarActivityDelegateHC [id=0x134edae0]
    |   mAllLoaderManagers = android.support.v4.util.SimpleArrayMap [id=0x13257820]
    |   mContainer = android.support.v4.app.FragmentActivity$2 [id=0x12f00760]
    |   mFragments = android.support.v4.app.FragmentManagerImpl [id=0x13447040]
    |   mHandler = android.support.v4.app.FragmentActivity$1 [id=0x13069bc0]
    |   mLoaderManager = null
    |   mCheckedForLoaderManager = true
    |   mCreated = true
    |   mLoadersStarted = false
    |   mOptionsMenuInvalidated = false
    |   mReallyStopped = true
    |   mResumed = false
    |   mRetaining = false
    |   mStopped = true
    |   mActionBar = null
    |   mActivityInfo = android.content.pm.ActivityInfo [id=0x12c71080]
    |   mActivityTransitionState = android.app.ActivityTransitionState [id=0x12e42ac0]
    |   mAllLoaderManagers = android.util.ArrayMap [id=0x13257700]
    |   mApplication = com.example.CODebugApplication [id=0x12c81820]
    |   mComponent = android.content.ComponentName [id=0x12c55080]
    |   mContainer = android.app.Activity$1 [id=0x12f00660]
    |   mCurrentConfig = android.content.res.Configuration [id=0x13698320]
    |   mDecor = null
    |   mDefaultKeySsb = null
    |   mEmbeddedID = null
    |   mEnterTransitionListener = android.app.SharedElementCallback$1 [id=0x715ae4d8]
    |   mExitTransitionListener = android.app.SharedElementCallback$1 [id=0x715ae4d8]
    |   mFragments = android.app.FragmentManagerImpl [id=0x13446e80]
    |   mHandler = android.os.Handler [id=0x13069b80]
    |   mInstanceTracker = android.os.StrictMode$InstanceTracker [id=0x12f00670]
    |   mInstrumentation = android.app.Instrumentation [id=0x12c33010]
    |   mIntent = android.content.Intent [id=0x12c6d040]
    |   mLastNonConfigurationInstances = null
    |   mLoaderManager = null
    |   mMainThread = android.app.ActivityThread [id=0x12c24100]
    |   mManagedCursors = java.util.ArrayList [id=0x13069aa0]
    |   mManagedDialogs = null
    |   mMenuInflater = null
    |   mParent = null
    |   mResultData = null
    |   mSearchManager = null
    |   mTitle = java.lang.String [id=0x70acff58]
    |   mToken = android.os.BinderProxy [id=0x12c521e0]
    |   mTranslucentCallback = null
    |   mUiThread = java.lang.Thread [id=0x745302e0]
    |   mVoiceInteractor = null
    |   mWindow = com.android.internal.policy.impl.PhoneWindow [id=0x12f75500]
    |   mWindowManager = android.view.WindowManagerImpl [id=0x1307d380]
    |   mCalled = true
    |   mChangeCanvasToTranslucent = false
    |   mChangingConfigurations = false
    |   mCheckedForLoaderManager = true
    |   mConfigChangeFlags = 0
    |   mDefaultKeyMode = 0
    |   mDestroyed = true
    |   mDoReportFullyDrawn = false
    |   mEnableDefaultActionBarUp = false
    |   mFinished = true
    |   mIdent = 477293538
    |   mLoadersStarted = false
    |   mResultCode = 0
    |   mResumed = false
    |   mStartedActivity = false
    |   mStopped = true
    |   mTemporaryPause = false
    |   mTitleColor = 0
    |   mTitleReady = true
    |   mVisibleBehind = false
    |   mVisibleFromClient = true
    |   mVisibleFromServer = false
    |   mWindowAdded = true
    |   mInflater = com.android.internal.policy.impl.PhoneLayoutInflater [id=0x12e1d430]
    |   mOverrideConfiguration = null
    |   mResources = android.content.res.Resources [id=0x12c32f20]
    |   mTheme = android.content.res.Resources$Theme [id=0x1307d3a0]
    |   mThemeResource = 2131558505
    |   mBase = android.app.ContextImpl [id=0x12e96b00]

@pyricau
Copy link
Member

pyricau commented May 15, 2015

Looks like another case of native code holding on to a binder object (here android.accounts.AccountManager$AmsTask$Response

@andkulikov
Copy link
Author

and what does it mean? is it problem in framework or in my project?

@pyricau
Copy link
Member

pyricau commented May 16, 2015

Likely AOSP. We'd need to figure out what causes that bug.

@iNoles
Copy link

iNoles commented May 16, 2015

Anonymous inner class uses the STRONG reference of parent class.

@andkulikov
Copy link
Author

in my case anonymous class extends android.accounts.AccountManager$AmsTask located not in activity but in singletone utils class AuthManager. and AuthManager doesnt saves reference to activity in any place

@pyricau
Copy link
Member

pyricau commented May 16, 2015

The problem here is that there's another process that seems to hold AccountManager$AmsTask$Response through the binder . And AccountManager$AmsTask$Response is an inner class of AccountManager$AmsTask which itself has a reference to the activity.

So the question here becomes: why is the other process not done yet with AccountManager$AmsTask$Response

@pyricau
Copy link
Member

pyricau commented May 16, 2015

@andkulikov
Copy link
Author

i found a duplicate of this issue
forcedotcom/SalesforceMobileSDK-Android#882

@pyricau
Copy link
Member

pyricau commented May 16, 2015

Filed in AOSP: https://code.google.com/p/android/issues/detail?id=173689

You can prevent this leak from happening by passing a null activity reference to the AccountManager methods, and then dealing with the returned future to to get the result and correctly start an activity when it's available.

pyricau added a commit that referenced this issue May 16, 2015
pyricau added a commit that referenced this issue May 16, 2015
Ignore AccountManager leak. Fixes #97
Yky pushed a commit to Yky/leakcanary that referenced this issue Feb 21, 2016
Yky pushed a commit to Yky/leakcanary that referenced this issue Feb 21, 2016
Ignore AccountManager leak. Fixes square#97
@francoispluchino
Copy link

@pyricau I tried your solution to avoid memory leak, namely, passed a null activity reference to the AccountManager::getAuthToken method, but I still get an alert with NO LEAK FOUND message.

Your fix is for SDK <= LOLLIPOP_MR1, but the problem is always present in SDK MARSHMALLOW.

In co.fxp.android.test.debug:1.0.0-debug:1.
* NO LEAK FOUND.

* Reference Key: 8dbbcc70-7a9b-4133-9b62-6c1ba90fcd04
* Device: LGE google Nexus 5 hammerhead
* Android Version: 6.0.1 API: 23 LeakCanary: 1.4-beta2 3799172
* Durations: watch=5021ms, gc=163ms, heap dump=4399ms, analysis=13770ms
* Excluded Refs:
| Field: android.view.inputmethod.InputMethodManager.mNextServedView
| Field: android.view.inputmethod.InputMethodManager.mServedView
| Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
| Field: android.view.inputmethod.InputMethodManager.mCurRootView
| Field: android.widget.Editor$Blink.this$0
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)
| Root Class:android.os.Binder (always)

This alert is displayed only that the activity is closed after retrieving the auth token.

@nanjingdaqi
Copy link

The problem still exists in N.

@masonTool
Copy link

have a try with this method. use AccountLeakHandler.safeGetAuthToken to wrap your original call.

package com.meizu.compaign.sdkcommon.utils;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;

import com.mason.meizu.reflect.RInstance;

import java.util.concurrent.FutureTask;

/**
 * Handle the getAuthToken method's memory leak
 * <p/>PS: the RInstance should be refrenced by:  compile 'com.github.masontool:reflect:2.2.0'
 * <p/>Or you can reflect the param state in the future instance by yourself
 *
 * @author 马培羽 <mason.mpy@gmail.com>  
 * www.mapeiyu.com
 */
public class AccountLeakHandler {

    /**
     * handle memory leak, use the to wrap your code
     * @param accountManager
     * @param account
     * @param authTokenType
     * @param options
     * @param activity
     * @param callback
     * @param handler
     * @return
     */
    public static AccountManagerFuture<Bundle> safeGetAuthToken(AccountManager accountManager,
                                                                Account account,
                                                                String authTokenType,
                                                                Bundle options,
                                                                Activity activity,
                                                                AccountManagerCallback<Bundle> callback,
                                                                Handler handler) {
        return accountManager.getAuthToken(
                account,
                authTokenType,
                options,
                null,
                new ResolveLeakAccountManagerCallback(callback, activity),
                handler);
    }

    /**
     * We can't resolve the memory leak, cause the remote service must hold a callback object. 
     * <p/>The solution is making the hold callback empty, clear the callback and activity reference.
     */
    public static class ResolveLeakAccountManagerCallback implements AccountManagerCallback<Bundle> {

        private AccountManagerCallback<Bundle> originalCallback = null;
        private Activity originalActivity = null;

        public ResolveLeakAccountManagerCallback(AccountManagerCallback<Bundle> callback, Activity activity) {
            originalCallback = callback;
            originalActivity = activity;
        }

        @Override
        public void run(AccountManagerFuture<Bundle> future) {
            Bundle bundle = null;
            try {
                bundle = future.getResult();
            } catch (Exception e) {
                // error happen
            }

            if (bundle == null) {
                callAndClear(future);
            } else {
                Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
                if (intent != null) {
                    if (originalActivity != null) {
                        try {
                            //make FutureTask callback again
                            new RInstance(FutureTask.class, future).setValue("state", 0);
                        } catch (Exception e) {}
                        originalActivity.startActivity(intent);
                    } else {
                        callAndClear(future);
                    }
                } else {
                    callAndClear(future);
                }
            }
        }

        /**
         * clear and call
         * @param future
         */
        private void callAndClear(AccountManagerFuture<Bundle> future) {
            if (originalCallback != null) {
                originalCallback.run(future);
                originalCallback = null;
            }
            originalActivity = null;
        }
    }
}

@vanniktech
Copy link
Contributor

I've just gotten this again on an O device.

@pyricau
Copy link
Member

pyricau commented Jun 28, 2018

@vanniktech please provide the leak trace and open a separate issue linking to this one.

@vanniktech
Copy link
Contributor

Sure thing. Opened #1042

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants