如何复用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
相关产品推荐
相关产品推荐

