MediaControllerCompat memory leak

1k views Asked by At

I have a MediaControllerCompat which is created an instance when a MediaSession connection is established. When this connection is accomplished I create the MediaControllerCompat following way:

MediaControllerCompat mediaController = new MediaControllerCompat(this, token);
MediaControllerCompat.setMediaController(this, mediaController);

The token is acquire from MediaSession.

All the times that back button is pressed a leak is detected. I don't have any callback/listener registered to MediaControllerCompat. I already tried set MediaController to null on activity's onDestroy() method, no success.

MediaControllerCompat.setMediaController(this, null);

Follow bellow the LeakCanary log.

D/LeakCanary: * com.me.PlaybackFullscreenActivity has leaked:
D/LeakCanary: * GC ROOT android.os.ResultReceiver$MyResultReceiver.this$0
D/LeakCanary: * references android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi21$1.this$0 (anonymous subclass of android.os.ResultReceiver)
D/LeakCanary: * references android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi23.mControllerObj
D/LeakCanary: * references android.media.session.MediaController.mContext
D/LeakCanary: * leaks com.me.ui.playback.PlaybackFullscreenActivity instance
D/LeakCanary: * Retaining: 54 KB.
D/LeakCanary: * Reference Key: 004ed9cd-c668-4d23-9ee6-cecad1b980a5
D/LeakCanary: * Device: unknown Android Android SDK built for x86_64 sdk_google_phone_x86_64
D/LeakCanary: * Android Version: 7.1 API: 25 LeakCanary: 1.5 00f37f5
D/LeakCanary: * Durations: watch=5018ms, gc=115ms, heap dump=1936ms, analysis=6011ms
D/LeakCanary: * Details:
D/LeakCanary: * Instance of android.os.ResultReceiver$MyResultReceiver
D/LeakCanary: |   this$0 = android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi21$1@322318080 (0x13362f00)
D/LeakCanary: |   mDescriptor = java.lang.String@1887101392 (0x707ae1d0)
D/LeakCanary: |   mObject = -813433536
D/LeakCanary: |   mOwner = android.os.ResultReceiver$MyResultReceiver@322318176 (0x13362f60)
D/LeakCanary: |   shadow$_klass_ = android.os.ResultReceiver$MyResultReceiver
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi21$1
D/LeakCanary: |   this$0 = android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi23@322317952 (0x13362e80)
D/LeakCanary: |   mHandler = android.os.Handler@322318144 (0x13362f40)
D/LeakCanary: |   mLocal = true
D/LeakCanary: |   mReceiver = android.os.ResultReceiver$MyResultReceiver@322318176 (0x13362f60)
D/LeakCanary: |   shadow$_klass_ = android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi21$1
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi23
D/LeakCanary: |   mCallbackMap = java.util.HashMap@322587040 (0x133a49a0)
D/LeakCanary: |   mControllerObj = android.media.session.MediaController@322587088 (0x133a49d0)
D/LeakCanary: |   mExtraBinder = android.support.v4.media.session.MediaSessionCompat$MediaSessionImplApi21$ExtraSession@319823424 (0x13101e40)
D/LeakCanary: |   mPendingCallbacks = null
D/LeakCanary: |   shadow$_klass_ = android.support.v4.media.session.MediaControllerCompat$MediaControllerImplApi23
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of android.media.session.MediaController
D/LeakCanary: |   static MSG_UPDATE_EXTRAS = 7
D/LeakCanary: |   static MSG_DESTROYED = 8
D/LeakCanary: |   static MSG_UPDATE_VOLUME = 4
D/LeakCanary: |   static MSG_UPDATE_QUEUE_TITLE = 6
D/LeakCanary: |   static MSG_UPDATE_PLAYBACK_STATE = 2
D/LeakCanary: |   static $staticOverhead = byte[72]@317243393 (0x12e8c001)
D/LeakCanary: |   static MSG_UPDATE_QUEUE = 5
D/LeakCanary: |   static MSG_EVENT = 1
D/LeakCanary: |   static TAG = java.lang.String@1886292312 (0x706e8958)
D/LeakCanary: |   static MSG_UPDATE_METADATA = 3
D/LeakCanary: |   mCallbacks = java.util.ArrayList@322318048 (0x13362ee0)
D/LeakCanary: |   mCbRegistered = false
D/LeakCanary: |   mCbStub = android.media.session.MediaController$CallbackStub@322317984 (0x13362ea0)
D/LeakCanary: |   mContext = com.me.ui.playback.PlaybackFullscreenActivity@322837504 (0x133e1c00)
D/LeakCanary: |   mLock = java.lang.Object@319489728 (0x130b06c0)
D/LeakCanary: |   mPackageName = null
D/LeakCanary: |   mSessionBinder = android.media.session.ISessionController$Stub$Proxy@319491728 (0x130b0e90)
D/LeakCanary: |   mTag = null
D/LeakCanary: |   mToken = android.media.session.MediaSession$Token@319489760 (0x130b06e0)
D/LeakCanary: |   mTransportControls = android.media.session.MediaController$TransportControls@319489744 (0x130b06d0)
D/LeakCanary: |   shadow$_klass_ = android.media.session.MediaController
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of com.me.ui.playback.PlaybackFullscreenActivity
D/LeakCanary: |   static $staticOverhead = byte[16]@317706241 (0x12efd001)
D/LeakCanary: |   static serialVersionUID = 0
D/LeakCanary: |   static $change = null
D/LeakCanary: |   mToolbar = android.support.v7.widget.Toolbar@321094656 (0x13238400)
D/LeakCanary: |   playbackFragment = com.me.ui.playback.PlaybackFragment@318524080 (0x12fc4ab0)
D/LeakCanary: |   mDelegate = android.support.v7.app.AppCompatDelegateImplV23@320052000 (0x13139b20)
D/LeakCanary: |   mEatKeyUpEvent = false
D/LeakCanary: |   mResources = null
D/LeakCanary: |   mThemeId = 2131427393
D/LeakCanary: |   mCreated = true
D/LeakCanary: |   mFragments = android.support.v4.app.FragmentController@323740768 (0x134be460)
D/LeakCanary: |   mHandler = android.support.v4.app.FragmentActivity$1@323839264 (0x134d6520)
D/LeakCanary: |   mNextCandidateRequestIndex = 0
D/LeakCanary: |   mOptionsMenuInvalidated = false
D/LeakCanary: |   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@323840160 (0x134d68a0)
D/LeakCanary: |   mReallyStopped = true
D/LeakCanary: |   mRequestedPermissionsFromFragment = false
D/LeakCanary: |   mResumed = false
D/LeakCanary: |   mRetaining = false
D/LeakCanary: |   mStopped = true
D/LeakCanary: |   mStartedActivityFromFragment = false
D/LeakCanary: |   mStartedIntentSenderFromFragment = false
D/LeakCanary: |   mExtraDataMap = android.support.v4.util.SimpleArrayMap@323839232 (0x134d6500)
D/LeakCanary: |   mActionBar = null
D/LeakCanary: |   mActionModeTypeStarting = 0
D/LeakCanary: |   mActivityInfo = android.content.pm.ActivityInfo@319807616 (0x130fe080)
D/LeakCanary: |   mActivityTransitionState = android.app.ActivityTransitionState@323795264 (0x134cb940)
D/LeakCanary: |   mApplication = com.me.MainApplication@314898704 (0x12c4f910)
D/LeakCanary: |   mCalled = true
D/LeakCanary: |   mChangeCanvasToTranslucent = false
D/LeakCanary: |   mChangingConfigurations = false
D/LeakCanary: |   mComponent = android.content.ComponentName@323825776 (0x134d3070)
D/LeakCanary: |   mConfigChangeFlags = 0
D/LeakCanary: |   mCurrentConfig = android.content.res.Configuration@323855456 (0x134da460)
D/LeakCanary: |   mDecor = null
D/LeakCanary: |   mDefaultKeyMode = 0
D/LeakCanary: |   mDefaultKeySsb = null
D/LeakCanary: |   mDestroyed = true
D/LeakCanary: |   mDoReportFullyDrawn = false
D/LeakCanary: |   mEmbeddedID = null
D/LeakCanary: |   mEnableDefaultActionBarUp = false
D/LeakCanary: |   mEnterTransitionListener = android.app.SharedElementCallback$1@1888376616 (0x708e5728)
D/LeakCanary: |   mExitTransitionListener = android.app.SharedElementCallback$1@1888376616 (0x708e5728)
D/LeakCanary: |   mFinished = true
D/LeakCanary: |   mFragments = android.app.FragmentController@323740720 (0x134be430)
D/LeakCanary: |   mHandler = android.os.Handler@323839136 (0x134d64a0)
D/LeakCanary: |   mIdent = 169286722
D/LeakCanary: |   mInstanceTracker = android.os.StrictMode$InstanceTracker@323740736 (0x134be440)
D/LeakCanary: |   mInstrumentation = android.app.Instrumentation@315044816 (0x12c733d0)
D/LeakCanary: |   mIntent = android.content.Intent@323821632 (0x134d2040)
D/LeakCanary: |   mLastNonConfigurationInstances = null
D/LeakCanary: |   mMainThread = android.app.ActivityThread@314791872 (0x12c357c0)
D/LeakCanary: |   mManagedCursors = java.util.ArrayList@323839168 (0x134d64c0)
D/LeakCanary: |   mManagedDialogs = null
D/LeakCanary: |   mMenuInflater = null
D/LeakCanary: |   mParent = null
D/LeakCanary: |   mReferrer = java.lang.String@323822208 (0x134d2280)
D/LeakCanary: |   mResultCode = 0
D/LeakCanary: |   mResultData = null
D/LeakCanary: |   mResumed = false
D/LeakCanary: |   mSearchEvent = null
D/LeakCanary: |   mSearchManager = null
D/LeakCanary: |   mStartedActivity = false
D/LeakCanary: |   mStopped = true
D/LeakCanary: |   mTemporaryPause = false
D/LeakCanary: |   mTitle = java.lang.String@314691776 (0x12c1d0c0)
D/LeakCanary: |   mTitleColor = 0
D/LeakCanary: |   mTitleReady = true
D/LeakCanary: |   mToken = android.os.BinderProxy@323829824 (0x134d4040)
D/LeakCanary: |   mTranslucentCallback = null
D/LeakCanary: |   mUiThread = java.lang.Thread@1955762776 (0x74929258)
D/LeakCanary: |   mVisibleBehind = false
D/LeakCanary: |   mVisibleFromClient = true
D/LeakCanary: |   mVisibleFromServer = true
D/LeakCanary: |   mVoiceInteractor = null
D/LeakCanary: |   mWindow = com.android.internal.policy.PhoneWindow@317655136 (0x12ef0860)
D/LeakCanary: |   mWindowAdded = true
D/LeakCanary: |   mWindowManager = android.view.WindowManagerImpl@323839680 (0x134d66c0)
D/LeakCanary: |   mInflater = com.android.internal.policy.PhoneLayoutInflater@323776416 (0x134c6fa0)
D/LeakCanary: |   mOverrideConfiguration = null
D/LeakCanary: |   mResources = android.content.res.Resources@315044736 (0x12c73380)
D/LeakCanary: |   mTheme = android.content.res.Resources$Theme@323839712 (0x134d66e0)
D/LeakCanary: |   mThemeResource = 2131427393
D/LeakCanary: |   mBase = android.app.ContextImpl@319796480 (0x130fb500)
D/LeakCanary: |   shadow$_klass_ = com.me.ui.playback.PlaybackFullscreenActivity
D/LeakCanary: |   shadow$_monitor_ = 1293121552
D/LeakCanary: * Excluded Refs:
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mNextServedView
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedView
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mCurRootView
D/LeakCanary: | Field: android.os.UserManager.mContext
D/LeakCanary: | Field: android.net.ConnectivityManager.sInstance
D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
D/LeakCanary: | Thread:main (always)
D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
D/LeakCanary: | Class:java.lang.ref.WeakReference (always)
D/LeakCanary: | Class:java.lang.ref.SoftReference (always)
D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)
D/LeakCanary: | Class:java.lang.ref.Finalizer (always)
D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)

Can anybody help me?

Thanks in advance.

2

There are 2 answers

0
Arubu On BEST ANSWER

This leak was fixed and released in 25.2.0 support library. Font: issuetracker

0
azizbekian On

MediaControllerCompat.setMediaController() instantiates controllerObj. Then this object is used to perform setMediaController(activity, controllerObj). After this is performed, I see no seams that would make controllerObj not to be leaked. In other words, it seems that one should take care of nulling out that object on his own:

MediaSessionCompat mediaSessionCompat = ...;

MediaController mediaController =
    (MediaController) mediaSessionCompat.getController().getMediaController();

// explicitly nulling out MediaController
mediaController = null;

Note, that performing MediaControllerCompat.setMediaController(this, null) would not make previously set object to be nulled out, rather it would just update current instance with the new one. But controllerObj keeps a hard reference to the hosting activity and no one had taken care of nulling it out.