【问题标题】:Fragment leaks when exit form activity退出表单活动时片段泄漏
【发布时间】:2020-01-23 09:33:20
【问题描述】:

我不知道如何在我的 PlabackActivity 中堵住泄漏

这是 PlaybackActivity 的重要部分:

    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<FrameworkMediaCrypto>(
            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: () -> Unit = {}

    fun playWhenPrepared() {
        if (isPrepared()) {
            play()
        } else {
            playWhenPreparedCallback = { play() }
        }
    }

    private fun onPreparedStateChanged() {
        if (isPrepared()) {
            playWhenPreparedCallback()
            playWhenPreparedCallback = {}
        }
    }
    fun play() {
        player.playWhenReady = true
    }
    }

这是 LeakCanary 报告:

    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

【问题讨论】:

  • ExoPlayerAdapter 是你的类还是任何库类?
  • ExoPlayerAdapter 是我的课
  • 该班级的邮政编码
  • handler.removeCallbacks(runnable) 在片段销毁时调用。
  • 嘿,你有没有设法解决这个问题.. 因为我面临同样的问题

标签: android memory-leaks leakcanary


【解决方案1】:

ExoPlayerAdapter 有一个context 字段是活动实例,它有一个callback 字段是PlaybackFragment 的内部类,并且它不断向主线程发布runnableExoPlayerAdapter 实例。

在 Android 上,对象具有生命周期。活动被创建然后被销毁,片段也是如此。你必须决定ExoPlayerAdapter的生命周期应该是什么,并相应地编写你的代码。

  • PlaybackFragment 应该清除 Fragment.onDestroy() 中的 ExoPlayerAdapter 回调
  • 如果ExoPlayerAdapter 的寿命应该比活动实例长,那么您应该将应用程序上下文而不是活动上下文传递给它。如果它应该和活动实例一样长,那么你应该在活动被销毁时调用handler.removeCallbacks(runnable)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多