Я не могу понять, как устранить утечку в моей 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