PlayerMessage 的设计核心是一个状态机(State Machine),用于确保消息在复杂的播放器线程模型中能够安全、准确地传递。我们可以将这些字段分为三大类来理解:
1. 基础配置(定义消息是什么)
这些字段在消息创建时设定,定义了“谁来处理”、“处理什么”以及“何时处理”。
target: 消息的接收者(实现了Target接口)。这是处理逻辑的核心,通常是Renderer或其他播放器组件。sender: 消息的发送者,负责将消息放入播放器的消息队列。type: 消息类型(整数常量),用于告知target该如何处理该消息。payload: 携带的具体数据,可以是任何对象(如设置参数、配置信息等)。looper: 指定消息处理的线程。这非常重要,因为ExoPlayer是多线程环境,该字段确保消息被发送到正确的线程处理,避免线程不安全问题。mediaItemIndex&positionMs: 定义了消息的触发时机(在哪一段视频的哪个时间点触发)。如果不设置,通常意味着立即触发。
2. 状态标志(追踪生命周期)
这些字段用于记录消息在传递过程中的状态,防止非法操作(例如:消息发出了还能修改吗?)。
isSent: 消息是否已经调用了send()。一旦变为true,上述所有配置字段(Type, Payload, Position 等)都不可再更改,保证了线程安全。isDelivered: 消息是否已经成功抵达target并被执行。isProcessed: 消息是否处理完毕(无论是因为成功执行了,还是因为被取消了)。isCanceled: 消息是否在发送后被手动取消。
3. 控制与同步(决定消息如何终结)
deleteAfterDelivery: 决定消息是一次性的还是重复触发的。如果为false,则当播放器循环播放回到该位置时,消息会再次触发。clock: 用于blockUntilDelivered(timeoutMs)方法中,计算超时时间。
PlayerMessage 生命周期流程图
理解这些状态的关键在于理解消息从创建到销毁的旅程:
- 创建 (Initialization): 设置 Target、Timeline、Looper。
- 配置 (Configuration): 调用
setPayload,setPosition等。此时isSent为false,允许修改。 - 发送 (Send): 调用
send()。状态isSent置为true。此时配置被锁定。 - 排队与执行 (Delivery): 播放器在
looper指定的线程中处理消息,调用target.handleMessage()。 - 结束 (Completion):
- 成功:执行完毕,调用
markAsProcessed(true)。 - 取消:调用
cancel(),状态isCanceled置为true,调用markAsProcessed(false)。 - 标记为已处理 (
isProcessed = true) 后,blockUntilDelivered()等待的方法会收到通知并返回。
- 成功:执行完毕,调用
核心设计哲学
您可以看到代码中大量使用了 checkState(!isSent)。这体现了不可变性(Immutability)的设计原则:一旦消息投入发送队列,其状态就不应该被外部随意更改,从而避免了极其难以调试的竞态条件(Race Conditions)。
这种机制确保了即便您在 UI 线程创建消息,也可以安全地将其发送到播放器的播放线程(Playback Thread)进行执行。