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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:15:13