第3章 基础播放器创建与生命周期绑定
3.1 原理概述
ExoPlayer 的播放器实例与 Android Activity/Fragment 生命周期深度耦合。错误的生命周期管理会导致内存泄漏、播放状态丢失、Surface 失效等严重问题。本章的核心原理围绕两条线展开:
- Builder 模式:通过
ExoPlayer.Builder链式调用创建高度可配置的播放器实例,避免构造参数膨胀。 - 生命周期托管:播放器必须跟随宿主组件的
onStart→onResume→onPause→onStop进行精确的播放控制与资源释放。
核心原则:播放器实例的创建应在
onCreate 中完成,释放应在 onDestroy 中完成;播放/暂停行为应绑定到 onStart/onStop,Surface 管理绑定到 onResume/onPause。
3.2 实现流程
基础播放器的完整生命周期流程包含 6 个阶段,下表呈现各阶段的关键操作:
| 阶段 | 宿主回调 | 播放器操作 | View 状态 |
|---|---|---|---|
| 创建 | onCreate |
new ExoPlayer.Builder(context).build() |
PlayerView 初始化,绑定 player |
| 准备 | onCreate 或 onStart |
setMediaItem() + prepare() |
Surface 未就绪,显示占位 |
| 可见 | onStart |
play() |
Surface 可用,开播 |
| 前台 | onResume |
(保持播放) | Surface 重建后重新绑定 |
| 暂停 | onPause |
pause() |
保留最后帧 |
| 停止 | onStop |
stop(),释放 Surface |
清空显示 |
| 销毁 | onDestroy |
release() |
解除绑定 |
3.3 核心 API 与代码实现
以下是一个标准的 Activity 内实现播放器生命周期绑定的完整代码模板:
public class PlayerActivity extends AppCompatActivity {
private ExoPlayer player;
private PlayerView playerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
// 1. 通过 Builder 创建播放器实例
player = new ExoPlayer.Builder(this)
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(15000)
.setPauseAtEndOfMedia(false)
.build();
// 2. 绑定 PlayerView
playerView = findViewById(R.id.playerView);
playerView.setPlayer(player);
playerView.setUseController(true);
// 3. 准备媒体
MediaItem mediaItem = MediaItem.fromUri("https://example.com/sample.mp4");
player.setMediaItem(mediaItem);
player.prepare();
}
@Override
protected void onStart() {
super.onStart();
if (player != null) {
player.play();
}
}
@Override
protected void onResume() {
super.onResume();
if (player != null) {
// Surface 在 onResume 后可用,PlayerView 内部自动处理
}
}
@Override
protected void onPause() {
super.onPause();
if (player != null) {
player.pause();
}
}
@Override
protected void onStop() {
super.onStop();
if (player != null) {
player.stop();
}
}
@Override
protected void onDestroy() {
if (player != null) {
player.release();
player = null;
}
super.onDestroy();
}
}
Builder 关键配置参数:
setSeekBackIncrementMs()/setSeekForwardIncrementMs()— 快进退时长setPauseAtEndOfMedia()— 播完自动暂停setRenderersFactory()— 自定义渲染器工厂setTrackSelector()— 轨道选择策略setLoadControl()— 缓冲控制
3.4 View 绑定的内部机制
| PlayerView 方法 | 作用 | 调用时机 |
|---|---|---|
setPlayer(ExoPlayer) |
绑定播放器,自动监听播放状态更新 UI | 播放器创建后立即调用 |
getPlayer() |
返回当前绑定的播放器引用 | 任何需要访问播放器的时候 |
setUseController(boolean) |
启用或禁用控制器面板 | 初始化时配置 |
setControllerVisibilityListener(...) |
监听控制器显隐 | 需要定制交互时 |
3.5 常见错误与避坑
错误 1:在 onPause 中调用 release()
导致播放器完全销毁,返回前台后不可恢复。正确做法:onPause 中仅调用 pause(),release() 仅在 onDestroy 中执行。
导致播放器完全销毁,返回前台后不可恢复。正确做法:onPause 中仅调用 pause(),release() 仅在 onDestroy 中执行。
错误 2:多次 setPlayer 导致播放器关联混乱
每个 PlayerView 只能绑定一个播放器,重复绑定前需先
每个 PlayerView 只能绑定一个播放器,重复绑定前需先
playerView.setPlayer(null) 解绑旧实例。
错误 3:Fragment 中忘记处理 onDestroyView
Fragment 的 View 生命周期与 Fragment 本身不同步。必须在
Fragment 的 View 生命周期与 Fragment 本身不同步。必须在
onDestroyView 中解绑 PlayerView,避免 View 泄漏。
// Fragment 中的正确释放方式
@Override
public void onDestroyView() {
super.onDestroyView();
playerView.setPlayer(null); // 先解绑 View
// 播放器本身在 onDestroy 中 release
}
@Override
public void onDestroy() {
super.onDestroy();
if (player != null) {
player.release();
player = null;
}
}
3.6 性能与稳定性优化
- 懒初始化:PlayerView 的
setPlayer在onCreate中执行即可,不要延迟到onResume,避免首次播放白屏。 - Surface 重建保护:切换后台再返回时,Surface 可能重建。PlayerView 内部会自动处理,但自定义 Surface 场景下需在
onResume中重新绑定。 - 内存泄漏预防:播放器持有 Context 引用,务必在
onDestroy中调用release()并置空。 - 播放状态保存:在
onSaveInstanceState中保存播放位置,在onRestoreInstanceState中恢复,避免旋转屏幕后从头播放。
// 保存与恢复播放位置
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (player != null) {
outState.putLong("position", player.getCurrentPosition());
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
long position = savedInstanceState.getLong("position", 0);
if (player != null && position > 0) {
player.seekTo(position);
}
}
3.7 调试方法
生命周期绑定的调试推荐使用以下手段:
- 在 Activity/Fragment 的每个生命周期回调中添加日志输出,观察调用顺序是否正确。
- 使用 ExoPlayer 的
addListener()监听Player.StateListener,跟踪播放状态转换。 - 通过
player.getPlaybackState()实时获取状态码:STATE_IDLE(1)、STATE_BUFFERING(2)、STATE_READY(3)、STATE_ENDED(4)。
// 添加播放状态监听
player.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
Log.d("PlayerState", "状态变更: " + playbackState);
}
@Override
public void onPlayerError(PlaybackException error) {
Log.e("PlayerError", "播放错误: " + error.getMessage());
}
});
3.8 部署检查清单
| 检查项 | 验证方法 | 预期结果 |
|---|---|---|
| Builder 配置完整 | 查看构建日志 | 无异常抛出 |
| View 绑定正确 | 运行后点击播放 | 视频画面正常显示 |
| 后台暂停 | 按 Home 键 | 播放暂停,返回后继续 |
| 旋转恢复 | 旋转设备 | 播放位置不变 |
| 无泄漏 | Profile / LeakCanary | 退出后无 Player 实例残留 |