title: Fragment leaks when exit form activity
body: <p>I cannot figure out how to plug the leakage in my PlabackActivity</p>
  
  <p>here is important part of PlaybackActivity:</p>
  
  <pre><code>    class PlaybackActivity : BaseActivity() {
  
      private var playbackFragment: PlaybackFragment? = null
  
  
      public override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.playback_activity)
  
          val f = playbackFragment
              ?: run {
                  val fragment = PlaybackFragment.newInstance()
                  playbackFragment = fragment
                  fragment
              }
  
          replacePlaybackFragment(f)
  
      }
  
  
      private fun replacePlaybackFragment(fragment: BasePlaybackFragment) {
  
          val ft = supportFragmentManager.beginTransaction()
          ft.replace(R.id.playbackFragmentContainer, fragment, fragment.javaClass.simpleName )
          ft.addToBackStack(null)
          ft.commit()
      }
  
  
      override fun onDestroy() {
          Timber.d("onDestroy()")
          playbackFragment = null
          super.onDestroy()
      }
  }
  
  ExoPlayerAdapter:
  
      class ExoPlayerAdapter(val context: Context) {
  
      var callback: ExoPlayerAdapterCallback? = null
  
      private val player: SimpleExoPlayer
  
      private val runnable: Runnable = object : Runnable {
          override fun run() {
              callback?.onCurrentPositionChanged(this@ExoPlayerAdapter)
              callback?.onBufferedPositionChanged(this@ExoPlayerAdapter)
              handler.postDelayed(this, 1000)
          }
      }
      private val handler = Handler()
      private var initialized = false
  
      private var mediaSourceUri: Uri? = null
      private var mediaSourceDrmUri: Uri? = null
  
      private var hasDisplay: Boolean = false
      private var bufferingStart: Boolean = false
  
      @C.StreamType
      var audioStreamType: Int = 0
  
      init {
          player = initExo2()
  
          player.addAnalyticsListener(object : AnalyticsListener {
              override fun onDownstreamFormatChanged(eventTime: AnalyticsListener.EventTime?, mediaLoadData: MediaSourceEventListener.MediaLoadData?) {
                  mediaLoadData?.trackFormat
              }
          })
      }
  
      private fun initExo2(): SimpleExoPlayer {
  
          val defaultDrmSessionManager = DefaultDrmSessionManager&lt;FrameworkMediaCrypto&gt;(
              C.WIDEVINE_UUID,
              LicenceExoMediaDrm(),
              HttpMediaDrmCallback(),
              HashMap()
          )
  
          val exoPlayer = ExoPlayerFactory.newSimpleInstance(
              context,
              DefaultRenderersFactory(context),
              DefaultTrackSelector(AdaptiveTrackSelection.Factory()),
              DefaultLoadControl
                  .Builder()
                  .setAllocator(DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE * 512))
                  .createDefaultLoadControl(),
              defaultDrmSessionManager
          )
  
          return exoPlayer
      }
  
      fun reset() {
          changeToUninitialized()
          player.stop()
      }
  
      private fun changeToUninitialized() {
          if (initialized) {
              initialized = false
              if (hasDisplay) {
                  callback?.onPreparedStateChanged(this)
                  onPreparedStateChanged()
              }
          }
      }
  
      fun release() {
          changeToUninitialized()
          hasDisplay = false
          player.release()
      }
  
      fun onDetachedFromHost() {
          reset()
          release()
      }
      internal fun setDisplay(surfaceHolder: SurfaceHolder?) {
          val hadDisplay = hasDisplay
          hasDisplay = surfaceHolder != null
  
          if (hadDisplay == hasDisplay) {
              return
          }
  
          player.setVideoSurfaceHolder(surfaceHolder)
          if (hasDisplay) {
              if (initialized) {
                  callback?.onPreparedStateChanged(this)
                  onPreparedStateChanged()
              }
          } else {
              if (initialized) {
                  callback?.onPreparedStateChanged(this)
                  onPreparedStateChanged()
              }
          }
      }
  
      fun setProgressUpdatingEnabled(enabled: Boolean) {
          handler.removeCallbacks(runnable)
          if (!enabled) {
              return
          }
          handler.postDelayed(runnable, 1000)
      }
  
      fun setDataSource(mediaSourceUri: Uri?, drmUri: Uri): Boolean {
  
          Timber.d("setDataSource uri=$mediaSourceUri drm=$drmUri")
          if (if (this.mediaSourceUri != null) this.mediaSourceUri == mediaSourceUri else mediaSourceUri == null) {
              return false
          }
          this.mediaSourceUri = mediaSourceUri
          this.mediaSourceDrmUri = drmUri
  
          prepareMediaForPlaying()
          return true
      }
  
      private fun onCreateMediaSource(uri: Uri): MediaSource {
          Timber.d("onCreateMediaSource() $uri")
          val userAgent = Util.getUserAgent(context, "ExoPlayerAdapter")
          val defaultDataSourceFactory = DefaultDataSourceFactory(context, userAgent)
          return createDashSource(uri, defaultDataSourceFactory)
  
      }
  
      private fun createDashSource(url: Uri, dataSourceFactory: DefaultDataSourceFactory): DashMediaSource {
          return DashMediaSource.Factory(
              DefaultDashChunkSource.Factory(dataSourceFactory),
              dataSourceFactory
          )
              .setManifestParser(object : DashManifestParser() {})
              .setLoadErrorHandlingPolicy(AmbLoadErrorHandlingPolicy())
              .createMediaSource(url)
      }
  
      private fun prepareMediaForPlaying() {
          reset()
          if (mediaSourceUri != null) {
              val mediaSource = onCreateMediaSource(mediaSourceUri)
              player.prepare(mediaSource, true, true)
          } else {
              return
          }
  
          notifyBufferingStartEnd()
          callback?.onPlayStateChanged(this@ExoPlayerAdapter)
  
          //after reload player with new live stream the progress bar are not updated
          //so enable updating is needed.
          setProgressUpdatingEnabled(true)
      }
  
      var playWhenPreparedCallback: () -&gt; Unit = {}
  
      fun playWhenPrepared() {
          if (isPrepared()) {
              play()
          } else {
              playWhenPreparedCallback = { play() }
          }
      }
  
      private fun onPreparedStateChanged() {
          if (isPrepared()) {
              playWhenPreparedCallback()
              playWhenPreparedCallback = {}
          }
      }
      fun play() {
          player.playWhenReady = true
      }
      }
  </code></pre>
  
  <p>And here is LeakCanary report:</p>
  
  <pre><code>    HEAP ANALYSIS RESULT
  ====================================
  2 APPLICATION LEAKS
  
  References underlined with "~~~" are likely causes.
  Learn more at https://squ.re/leaks.
  
  232640 bytes retained by leaking objects
  Signature: b79dd5a31e7bdd68a5fe42fe4139e4a5625ce7
  ┬───
  │ GC Root: Input or output parameters in native code
  │
  ├─ android.os.MessageQueue instance
  │    Leaking: NO (MessageQueue#mQuitting is false)
  │    ↓ MessageQueue.mMessages
  │                   ~~~~~~~~~
  ├─ android.os.Message instance
  │    Leaking: UNKNOWN
  │    ↓ Message.callback
  │              ~~~~~~~~
  ├─ tvapp.player.exo2.ExoPlayerAdapter$runnable$1 instance
  │    Leaking: UNKNOWN
  │    Anonymous class implementing java.lang.Runnable
  │    ↓ ExoPlayerAdapter$runnable$1.this$0
  │                                  ~~~~~~
  ├─ tvapp.player.exo2.ExoPlayerAdapter instance
  │    Leaking: UNKNOWN
  │    ↓ ExoPlayerAdapter.context
  │                       ~~~~~~~
  ╰→ tvapp.player.PlaybackActivity instance
  ​     Leaking: YES (ObjectWatcher was watching this because tvapp.player.PlaybackActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
  ​     key = cdf5901a-cd24-49ba-b61f-d8d36e092dfa
  ​     watchDurationMillis = 15583
  ​     retainedDurationMillis = 10583
  
  10621 bytes retained by leaking objects
  Signature: 21cac0892d97b1f1d0dd68f578a458aa63ddc4f
  ┬───
  │ GC Root: Input or output parameters in native code
  │
  ├─ android.os.MessageQueue instance
  │    Leaking: NO (MessageQueue#mQuitting is false)
  │    ↓ MessageQueue.mMessages
  │                   ~~~~~~~~~
  ├─ android.os.Message instance
  │    Leaking: UNKNOWN
  │    ↓ Message.callback
  │              ~~~~~~~~
  ├─ tvapp.player.exo2.ExoPlayerAdapter$runnable$1 instance
  │    Leaking: UNKNOWN
  │    Anonymous class implementing java.lang.Runnable
  │    ↓ ExoPlayerAdapter$runnable$1.this$0
  │                                  ~~~~~~
  ├─ tvapp.player.exo2.ExoPlayerAdapter instance
  │    Leaking: UNKNOWN
  │    ↓ ExoPlayerAdapter.callback
  │                       ~~~~~~~~
  ├─ tvapp.player.PlaybackFragment$setupUi$4 instance
  │    Leaking: UNKNOWN
  │    Anonymous subclass of tvapp.player.exo2.ExoPlayerAdapterCallback
  │    ↓ PlaybackFragment$setupUi$4.this$0
  │                                    ~~~~~~
  ╰→ tvapp.player.PlaybackFragment instance
  ​     Leaking: YES (ObjectWatcher was watching this because tvapp.player.PlaybackFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
  ​     key = e4624d0a-b31a-4813-969b-08345bff6423
  ​     watchDurationMillis = 15489
  ​     retainedDurationMillis = 10488
  ​     key = eb37451d-0ff6-45be-ad7b-7120f391f03d
  ​     retainedDurationMillis = 10489
  </code></pre>