Android ExoPlayer屏幕旋转时自动恢复视频播放问题求助
解决FragmentStatePagerAdapter下屏幕旋转视频续播问题
我太懂这个坑了——用FragmentStatePagerAdapter管理带SimpleExoPlayerView的Fragment时,屏幕旋转不仅会触发Fragment重建,连播放器视图也会被重置,明明存了播放位置却没法自动续播。这核心问题在于Fragment和播放器实例的生命周期没对齐,下面给你一步步拆解修复方案:
1. 用ViewModel托管ExoPlayer,脱离Fragment生命周期
Fragment在配置变化(比如旋转)时会被销毁重建,但ViewModel会保留下来。把播放器实例放到ViewModel里,就能避免重建时丢失播放器状态:
编写PlayerViewModel
class PlayerViewModel : ViewModel() { var player: SimpleExoPlayer? = null var playbackPosition: Long = 0 var currentWindow: Int = 0 var playWhenReady: Boolean = true // 初始化播放器,复用已有实例 fun initializePlayer(context: Context) { if (player == null) { player = SimpleExoPlayer.Builder(context).build().apply { playWhenReady = this@PlayerViewModel.playWhenReady seekTo(currentWindow, playbackPosition) } } } // 释放播放器,仅在ViewModel销毁时调用 fun releasePlayer() { player?.let { // 保存当前状态 playbackPosition = it.currentPosition currentWindow = it.currentWindowIndex playWhenReady = it.playWhenReady it.release() player = null } } override fun onCleared() { super.onCleared() releasePlayer() // 页面真正销毁时释放资源 } }
2. 改造Fragment,绑定ViewModel与播放器视图
Fragment只负责视图的创建和绑定,播放器的生命周期交给ViewModel管理:
class VideoFragment : Fragment() { private lateinit var playerViewModel: PlayerViewModel private var playerView: SimpleExoPlayerView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) playerViewModel = ViewModelProvider(this).get(PlayerViewModel::class.java) // 从savedInstanceState恢复进程被杀后的状态 savedInstanceState?.let { playerViewModel.playbackPosition = it.getLong("playbackPosition", 0) playerViewModel.currentWindow = it.getInt("currentWindow", 0) playerViewModel.playWhenReady = it.getBoolean("playWhenReady", true) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) playerView = view.findViewById(R.id.player_view) // 初始化并绑定播放器到视图 playerViewModel.initializePlayer(requireContext()) playerView?.player = playerViewModel.player // 设置媒体源(这里从Fragment参数取视频地址,根据你的实际逻辑调整) val mediaUrl = arguments?.getString("video_url") ?: "" playerViewModel.player?.setMediaItem(MediaItem.fromUri(mediaUrl)) playerViewModel.player?.prepare() } override fun onDestroyView() { super.onDestroyView() // 视图销毁时只解绑播放器,不释放(ViewModel还持有实例) playerView?.player = null playerView = null } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // 保存状态到Bundle,应对进程被杀场景 playerViewModel.player?.let { outState.putLong("playbackPosition", it.currentPosition) outState.putInt("currentWindow", it.currentWindowIndex) outState.putBoolean("playWhenReady", it.playWhenReady) } } }
3. 优化PagerAdapter的Fragment管理
如果你用的是AndroidX的FragmentStatePagerAdapter,记得传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数,减少后台Fragment的不必要生命周期触发:
class VideoPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter( fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ) { private val videoList = listOf("video_url_1", "video_url_2", "video_url_3") override fun getItem(position: Int): Fragment { return VideoFragment().apply { arguments = Bundle().apply { putString("video_url", videoList[position]) } } } override fun getCount(): Int = videoList.size }
4. 可选:切换到ViewPager2(更稳定的方案)
FragmentStatePagerAdapter已经被标记为Deprecated,官方推荐用ViewPager2 + FragmentStateAdapter,它的Fragment生命周期管理更严谨,配置变化时的行为更可控:
class VideoPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { private val videoList = listOf("video_url_1", "video_url_2", "video_url_3") override fun getItemCount(): Int = videoList.size override fun createFragment(position: Int): Fragment { return VideoFragment().apply { arguments = Bundle().apply { putString("video_url", videoList[position]) } } } }
关键注意点
- 不要在Fragment的
onActivityCreated里恢复状态,而是通过ViewModel绑定,因为ViewModel会自动跨越配置变化。 - 务必在ViewModel的
onCleared方法里释放播放器,避免内存泄漏。 onSaveInstanceState仅用于应对进程被杀的场景,日常配置变化的状态由ViewModel保存即可。
内容的提问来源于stack exchange,提问作者M.Ali
相关产品推荐
相关产品推荐

