You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复用ExoPlayer实例并绑定到视图?含ViewPager2场景

如何在ViewPager2的多Fragment场景下复用ExoPlayer实例并绑定视图

这是个非常常见的ExoPlayer优化场景,核心思路是把Player实例托管在生命周期比Fragment更稳定的组件中,同时在Fragment的生命周期回调里做好视图的绑定/解绑,再配合页面可见性控制播放状态。下面给你一套完整的落地方案:

核心设计思路

将SimpleExoPlayer实例放在SharedViewModel中(推荐用ViewModel而非Activity,更贴合Jetpack架构规范),ViewModel的生命周期和宿主Activity绑定,不受Fragment创建/销毁的影响,两个Fragment可以共享同一个实例。同时在Fragment的视图生命周期中,完成Player与PlayerView的绑定、解绑,并通过页面可见性来控制播放状态。


具体实现步骤

1. 创建SharedViewModel持有Player实例

定义一个共享ViewModel,负责Player的创建、持有和资源释放,同时处理播放状态的保存与恢复:

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.android.exoplayer2.SimpleExoPlayer

class SharedPlayerViewModel(application: Application) : AndroidViewModel(application) {
    private var _player: SimpleExoPlayer? = null
    val player: SimpleExoPlayer? get() = _player

    // 保存播放进度与状态,用于页面切换时恢复
    private val _playbackState = MutableLiveData<Pair<Long, Boolean>>()
    val playbackState: LiveData<Pair<Long, Boolean>> get() = _playbackState

    init {
        // 用Application Context初始化Player,避免内存泄漏
        _player = SimpleExoPlayer.Builder(getApplication()).build()
    }

    // 保存当前播放状态
    fun savePlaybackState() {
        _player?.let {
            _playbackState.value = Pair(it.currentPosition, it.playWhenReady)
        }
    }

    // 恢复之前的播放状态
    fun restorePlaybackState() {
        _playbackState.value?.let { (position, playWhenReady) ->
            _player?.apply {
                seekTo(position)
                this.playWhenReady = playWhenReady
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        // ViewModel销毁时必须释放Player资源,避免后台耗电与内存泄漏
        _player?.release()
        _player = null
    }
}

2. 在Fragment中绑定/解绑PlayerView

两个Fragment(PageOneFragment和PageTwoFragment)的逻辑基本一致,以下是PageOneFragment的示例代码:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.google.android.exoplayer2.ui.PlayerView

class PageOneFragment : Fragment() {
    private lateinit var playerView: PlayerView
    private lateinit var viewModel: SharedPlayerViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_page_one, container, false)
        playerView = view.findViewById(R.id.playerView1)
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 获取共享ViewModel(用requireActivity()确保两个Fragment拿到同一个实例)
        viewModel = ViewModelProvider(requireActivity())[SharedPlayerViewModel::class.java]
        
        // 将Player绑定到当前Fragment的PlayerView
        viewModel.player?.let {
            playerView.player = it
        }
    }

    override fun onResume() {
        super.onResume()
        // 页面可见时恢复之前的播放状态
        viewModel.restorePlaybackState()
    }

    override fun onPause() {
        super.onPause()
        // 页面不可见时保存播放状态并暂停
        viewModel.savePlaybackState()
        viewModel.player?.playWhenReady = false
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 视图销毁时必须解绑Player,避免Player持有已销毁的View引用导致内存泄漏
        playerView.player = null
    }
}

PageTwoFragment只需替换对应的PlayerView ID(比如playerView2),其余逻辑完全相同。

3. 配合ViewPager2的页面切换监听(可选但推荐)

由于ViewPager2默认会预加载相邻Fragment,仅靠Fragment的生命周期可能不够精准。可以在宿主Activity中监听页面切换,更精准地控制播放:

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.viewpager2.widget.ViewPager2

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: SharedPlayerViewModel
    private lateinit var viewPager: ViewPager2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this)[SharedPlayerViewModel::class.java]
        viewPager = findViewById(R.id.viewPager2)
        
        // 设置ViewPager2的适配器(这里假设你已经实现了ViewPagerAdapter)
        viewPager.adapter = ViewPagerAdapter(this)

        // 监听页面切换,仅让当前可见页面播放
        viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                viewModel.player?.playWhenReady = true
                viewModel.restorePlaybackState()
            }

            override fun onPageScrollStateChanged(state: Int) {
                super.onPageScrollStateChanged(state)
                // 页面滑动时暂停播放,提升流畅度
                if (state != ViewPager2.SCROLL_STATE_IDLE) {
                    viewModel.player?.playWhenReady = false
                }
            }
        })
    }
}

关键注意事项

  • 内存泄漏防范:必须在Fragment的onDestroyView中调用playerView.player = null,避免Player持有已销毁的View引用。
  • 自动生命周期管理:如果想简化播放状态的管理,可以使用PlayerLifecycleController,它会自动根据Activity的生命周期暂停/恢复播放:
    val controller = PlayerLifecycleController(viewModel.player, this)
    controller.start()
    
  • 资源释放:ViewModel的onCleared方法必须调用player.release(),否则会导致后台播放耗电和资源泄漏。

内容的提问来源于stack exchange,提问作者nirazverma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 11:22:54