本文介绍AliPlayerKit策略系统的设计背景、核心概念、内置策略及使用方法。
设计背景
策略系统的设计源于对AUI Kits规模化落地中业务定制与框架演进矛盾的思考。
作为面向客户的低代码方案,AUI Kits需支持不同业务方对UI及核心流程的定制。若业务逻辑直接嵌入框架代码,将造成高度耦合:框架升级时易引发代码冲突、业务逻辑失效,甚至阻碍版本迭代。
本质上,此类逻辑属于客户业务需求而非框架能力。缺乏合理承载机制将导致业务差异侵入框架,威胁系统稳定性与可演进性。
策略系统为此而生:
将业务差异抽象为独立扩展的策略单元。
解耦业务逻辑与框架核心。
业务方可在策略层扩展,避免侵入框架。
该机制使业务能力沉淀为系统化、可复用的体系,形成 可迭代、可复用、可扩展 的能力基础,为AliPlayerKit长期演进提供架构支撑。
概念介绍
什么是策略系统?
策略系统(Strategy System)是统一管理策略组件的架构机制,负责策略的注册机制、生命周期管理、上下文注入及事件订阅,并在播放器运行中统一管理策略的创建、启动、暂停、重置与销毁。
基于该机制,开发者可按需启用/禁用策略、快速实现自定义策略,或完全替换默认策略以满足业务定制需求。
什么是策略?
策略(Strategy)是播放器业务逻辑的封装单元,实现特定业务能力或自定义逻辑(如首帧统计、卡顿检测、流量保护、记忆播放)。
核心特征:
单一职责:聚焦独立功能目标。
事件驱动:通过事件总线响应播放器状态。
独立隔离:策略间互不干扰。
通过可插拔可组合的设计,非核心能力被抽象为策略单元,保持播放器核心简洁,同时提供灵活扩展能力。
功能特性
解决问题
策略系统需解决的痛点:
维护困难:业务逻辑分散,难维护复用。
配置缺失:不同场景需不同逻辑组合,缺乏灵活配置机制。
源码入侵:定制逻辑需修改框架源码。
实例干扰:多播放器场景下业务逻辑易串台。
核心价值
策略系统抽离并封装播放器的监控与分析逻辑,支持客户按需自主配置与组合。
使用方式 | 说明 | 优势 |
使用默认策略 | 播放器组件使用官方默认策略。 | 简化接入流程,开箱即用。 |
自定义策略 | 实现特定业务需求的策略。 | 满足各类定制化业务需求。 |
不使用策略 | 仅使用播放能力。 | 纯播放场景,无逻辑依赖。 |
架构优势:
解耦:策略与播放器核心逻辑分离,职责清晰。
复用:策略可在不同播放器实例间复用。
可扩展:无需修改框架即可自定义策略。
隔离:策略间相互独立,互不影响。
核心能力
能力 | 说明 |
事件驱动 | 策略通过事件总线订阅播放器事件,响应式执行。 |
生命周期管理 | 统一管理策略的启动、停止、重置、销毁。 |
上下文注入 | 策略可通过上下文访问播放器状态和数据。 |
工厂模式 | 支持全局注册策略工厂,自动应用于所有播放器实例。 |
内置策略
AliPlayerKit 提供了三个开箱即用的内置策略。
首帧耗时策略(FirstFrameStrategy)
统计播放器从准备阶段(PREPARING)到首帧渲染完成的耗时,细分三个阶段:
阶段 | 说明 |
准备阶段 | PREPARING → Prepared |
渲染阶段 | Prepared → FirstFrameRendered |
总耗时 | PREPARING → FirstFrameRendered |
使用场景:性能监控、用户体验优化、播放器调优。
卡顿检测策略(StutterDetectStrategy)
监控播放过程中的卡顿状态,统计卡顿情况:
指标 | 说明 |
卡顿次数 | 卡顿发生的次数。 |
卡顿时长 | 每次卡顿的持续时间。 |
卡顿总时长 | 整个播放会话的卡顿时长总和。 |
有效播放时长 | 实际观看时长(不含暂停和卡顿)。 |
使用场景:播放质量监控、用户体验分析。
流量保护策略(TrafficProtectionStrategy)
监听网络状态变化,在特定场景下提醒用户:
场景 | 行为 |
WiFi转换为移动网络 | 提示用户正在使用流量播放。 |
起播时为移动网络。 | 提示用户当前使用流量。 |
用场景:流量敏感场景、用户关怀。
基础使用
策略系统提供三种使用策略,开发者可根据需求选择合适的方式:
策略 | 说明 | 适用场景 |
使用默认策略 | 最简单的使用方式,播放器组件自动注册默认策略。 | 快速集成、标准播放场景。 |
自定义部分策略 | 只注册特定策略,其他不使用。 | 局部定制、按需启用。 |
完全自定义策略 | 自定义所有策略,创建完全个性化的监控体系。 | 深度定制、特定业务需求。 |
策略一:使用内置策略
最简单的使用方式,播放器组件将自动注册默认策略:
// 1. 创建播放器控制器(自动注册默认策略)
AliPlayerController controller = new AliPlayerController(this);
// 2. 准备播放数据
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
// 3. 绑定到视图
playerView.attach(controller, model);默认注册的策略包括:
FirstFrameStrategy(首帧耗时)。
StutterDetectStrategy(卡顿检测)。
TrafficProtectionStrategy(流量保护)。
策略二:自定义策略
只注册需要的策略,其他不启用:
// 1. 创建播放器控制器
AliPlayerController controller = new AliPlayerController(this);
// 2. 获取策略管理器并清除默认策略
StrategyManager strategyManager = controller.getStrategyManager();
// 3. 只注册需要的策略
strategyManager.register(new FirstFrameStrategy());
strategyManager.register(new StutterDetectStrategy());
// 4. 准备播放数据并绑定
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
playerView.attach(controller, model);策略三:完全自定义策略
实现自定义策略,创建完全个性化的监控体系:
// 1. 创建播放器控制器
AliPlayerController controller = new AliPlayerController(this);
// 2. 获取策略管理器
StrategyManager strategyManager = controller.getStrategyManager();
// 3. 注册自定义策略
strategyManager.register(new MyCustomStrategy());
strategyManager.register(new MyAnalyticsStrategy());
// 4. 准备播放数据并绑定
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
playerView.attach(controller, model);进阶使用
如何实现自定义策略?
AliPlayerKit提供两种实现方式,根据需求选择:
方式 | 特点 | 适用场景 |
继承 | 代码量少,框架管理事件订阅。 | 大多数监控策略。 |
实现 | 灵活性高,完全自主控制。 | 需要特殊控制逻辑。 |
方式一:继承 BaseStrategy(推荐)
继承 BaseStrategy 是最简单的方式,框架已封装好事件订阅和生命周期管理。
适用场景:大多数监控和分析策略,如统计、检测、日志等。
创建策略类。
继承
BaseStrategy并实现必要方法:public class MyAnalyticsStrategy extends BaseStrategy { private static final String TAG = "MyAnalyticsStrategy"; @NonNull @Override public String getName() { return TAG; // 返回策略名称,用于日志和调试 } @Nullable @Override protected List<Class<? extends PlayerEvent>> observedEvents() { // 声明需要订阅的事件类型 return Arrays.asList( PlayerEvents.StateChanged.class, PlayerEvents.Prepared.class, PlayerEvents.Info.class ); } @Override public void onEvent(@NonNull PlayerEvent event) { // 过滤非当前播放器事件 if (!isCurrentPlayer(event)) return; // 处理事件 if (event instanceof PlayerEvents.StateChanged) { handleStateChanged((PlayerEvents.StateChanged) event); } else if (event instanceof PlayerEvents.Prepared) { handlePrepared((PlayerEvents.Prepared) event); } else if (event instanceof PlayerEvents.Info) { handleInfo((PlayerEvents.Info) event); } } @Override public void onReset() { super.onReset(); // 重置内部状态,准备处理新的视频 } private void handleStateChanged(PlayerEvents.StateChanged event) { // 处理状态变化 } private void handlePrepared(PlayerEvents.Prepared event) { // 处理准备完成 } private void handleInfo(PlayerEvents.Info event) { // 处理播放信息更新 } }注册使用。
StrategyManager strategyManager = controller.getStrategyManager(); strategyManager.register(new MyAnalyticsStrategy());
示例参考:strategies/FirstFrameStrategy.java。
方式二:实现IStrategy接口
直接实现 IStrategy 接口可以获得更高的灵活性,但需要自行处理事件订阅。
适用场景:需要完全控制策略行为,或需要特殊初始化逻辑。
创建策略类。
实现
IStrategy接口,并自行管理事件订阅:public class MyCustomStrategy implements IStrategy, PlayerEventBus.EventListener<PlayerEvent> { private static final String TAG = "MyCustomStrategy"; private StrategyContext mContext; @NonNull @Override public String getName() { return TAG; } @Override public void onStart(@NonNull StrategyContext context) { mContext = context; // 手动订阅事件 PlayerEventBus.getInstance().subscribe(PlayerEvents.StateChanged.class, this); } @Override public void onStop() { // 手动取消订阅 PlayerEventBus.getInstance().unsubscribe(PlayerEvents.StateChanged.class, this); mContext = null; } @Override public void onReset() { // 重置内部状态 } @Override public void onEvent(@NonNull PlayerEvent event) { // 处理事件 } }注册使用。
StrategyManager strategyManager = controller.getStrategyManager(); strategyManager.register(new MyAnalyticsStrategy());
如何实现带回调的策略?
策略可以通过回调接口将结果通知给业务层:
public class MyAnalyticsStrategy extends BaseStrategy {
public interface Callback {
void onAnalysisComplete(AnalysisResult result);
}
@Nullable
private final Callback mCallback;
// 支持无参构造
public MyAnalyticsStrategy() {
this(null);
}
// 支持传入回调
public MyAnalyticsStrategy(@Nullable Callback callback) {
mCallback = callback;
}
@Override
public void onEvent(@NonNull PlayerEvent event) {
// ... 业务逻辑
// 通过回调通知结果
if (mCallback != null) {
mCallback.onAnalysisComplete(result);
}
}
}使用方式:
strategyManager.register(new MyAnalyticsStrategy(result -> {
// 处理分析结果
Log.d("Analytics", "Result: " + result);
}));示例参考:strategies/FirstFrameStrategy.java。
如何访问播放器状态?
策略可以通过 StrategyContext 访问播放器状态和数据:
@Override
public void onEvent(@NonNull PlayerEvent event) {
if (!isCurrentPlayer(event)) return;
// 获取播放器 ID
String playerId = getPlayerId();
// 获取播放数据模型
AliPlayerModel model = mContext.getModel();
if (model != null) {
String title = model.getVideoTitle();
VideoSource source = model.getVideoSource();
}
// 获取播放器状态存储
IPlayerStateStore stateStore = mContext.getPlayerStateStore();
long position = stateStore.getCurrentPosition();
long duration = stateStore.getDuration();
PlayerState state = stateStore.getPlayState();
}mContext在构造函数中为null,需在onStart()之后才被初始化。
如何注册全局策略?
通过 StrategyRegistry 可以注册全局策略,自动应用于所有新创建的播放器实例:
// 注册全局策略
StrategyRegistry.addGlobalStrategy(() -> new MyAnalyticsStrategy());
// 之后创建的所有 AliPlayerController 都会自动注册此策略
AliPlayerController controller = new AliPlayerController(this); 恢复默认配置:
StrategyRegistry.resetToDefault();最佳实践
生命周期管理
public class MyStrategy extends BaseStrategy {
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // 必须调用// 初始化资源
}
@Overridepublic void onStop() {
// 清理资源super.onStop(); // 必须调用
}
@Overridepublic void onReset() {
super.onReset();
// 重置内部状态,准备处理新视频
}
}事件过滤
在多播放器场景下,务必过滤事件来源:
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 过滤非当前播放器事件,避免串台if (!isCurrentPlayer(event)) return;
// 处理当前播放器的事件
}资源清理
策略中如果有 Handler、Runnable 等异步资源,务必在 onStop() 中清理:
public class MyStrategy extends BaseStrategy {
private final Handler mHandler = new Handler();
private Runnable mRunnable;
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context);
mRunnable = () -> doSomething();
mHandler.postDelayed(mRunnable, 1000);
}
@Overridepublic void onStop() {
// 清理异步任务,避免内存泄漏
mHandler.removeCallbacks(mRunnable);
super.onStop();
}
}注意事项
场景 | 推荐做法 | 原因 |
多播放器场景 | 使用 | 避免事件串台。 |
长时间运行任务 | 在 | 避免内存泄漏。 |
视频切换 | 在 | 确保状态正确。 |
访问 Context | 通过 | 确保上下文可用。 |
示例参考
项目提供了完整的示例,位于 playerkit-examples/example-strategy-system。
示例功能
功能 | 说明 |
记忆播放策略 | 自动记录播放进度,下次播放时恢复。 |
策略注册演示 | 演示如何注册自定义策略。 |
运行示例
在Demo App中选择 Strategy System 示例查看效果。
API 参考
核心接口
接口/类 | 说明 |
| 策略接口,定义生命周期方法。 |
| 策略基类,封装事件订阅管理。 |
| 策略管理器,管理策略生命周期。 |
| 策略上下文,提供只读的播放器信息。 |
| 策略注册中心,管理全局策略。 |
BaseStrategy 方法
方法 | 说明 |
| 返回策略名称,用于日志和调试。 |
| 返回需要订阅的事件类型列表。 |
| 事件回调,处理订阅的事件。 |
| 检查事件是否来自当前播放器。 |
| 获取当前播放器 ID。 |
StrategyManager 方法
方法 | 说明 |
| 注册策略。 |
| 注销策略。 |
| 根据名称获取策略。 |
| 启动所有策略。 |
| 停止所有策略。 |
| 重置所有策略。 |
| 销毁管理器。 |
StrategyContext 方法
方法 | 说明 |
| 获取播放器 ID。 |
| 获取播放数据模型。 |
| 获取播放器状态存储(只读)。 |
技术原理
整体架构
策略系统采用 事件驱动 + 管理器调度 的架构模式。
设计要点:
策略不持有播放器引用:通过事件驱动,彻底解耦。
策略只关注事件:只需声明订阅什么和如何处理。
管理器统一调度:生命周期、事件分发由管理器控制。
生命周期
策略采用三阶段生命周期:
生命周期 | 触发时机 | 说明 |
|
| 策略启动,订阅事件,初始化资源。 |
| 播放内容切换 | 重置内部状态,准备处理新视频。 |
|
| 策略停止,取消订阅,清理资源。 |
事件驱动机制
策略通过事件总线订阅播放器事件,采用发布-订阅模式。
事件订阅流程:
策略在
observedEvents()中声明需要订阅的事件类型BaseStrategy在onStart()中自动订阅这些事件BaseStrategy在onStop()中自动取消订阅策略在
onEvent()中处理接收到的事件
设计理念:事件驱动机制彻底解耦策略与播放器—策略无需持有播放器引用,只需关注事件订阅与处理逻辑,实现关注点分离。
多播放器隔离
在多播放器场景下,策略系统通过以下机制确保隔离:
独立 StrategyManager:每个
AliPlayerController拥有独立的StrategyManager。独立 StrategyContext:每个策略上下文绑定特定播放器 ID。
事件过滤:策略通过
isCurrentPlayer(event)过滤事件来源。
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 每个播放器实例的事件都带有 playerId// isCurrentPlayer() 检查事件是否来自当前播放器if (!isCurrentPlayer(event)) return;
// 只处理当前播放器的事件
}常见问题
策略什么时候启动?
调用 playerView.attach() 时自动启动所有已注册的策略。
如何获取播放器当前状态?
通过 StrategyContext 的 getPlayerStateStore() 方法:
IPlayerStateStore stateStore = mContext.getPlayerStateStore();
PlayerState state = stateStore.getPlayState();
long position = stateStore.getCurrentPosition();如何调试策略?
使用 LogHub 查看日志,TAG 格式:策略名称.BaseStrategy 或 StrategyManager。
高频崩溃错例
以下是客户反馈中最常导致崩溃的问题,请务必避免:
错例 1:在构造函数中访问 mContext
错误代码:
public class MyStrategy extends BaseStrategy {
public MyStrategy() {
super();
// 构造函数中 mContext 为 null
String playerId = getPlayerId(); // 返回空字符串
AliPlayerModel model = mContext.getModel(); // NullPointerException!
}
}
正确代码:
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // ✅ 必须首先调用// 在 onStart 之后才能访问 mContext
String playerId = getPlayerId(); // 正常获取
AliPlayerModel model = mContext.getModel(); // 正常获取
}
崩溃原因:mContext 在 onStart() 调用后才被赋值,构造函数执行时还未启动策略。
错例 2:忘记调用 super.onStart() 导致事件订阅失效
错误代码:
public class MyStrategy extends BaseStrategy {
@Overridepublic void onStart(@NonNull StrategyContext context) {
// 忘记调用 super.onStart(context)// mContext 为 null,事件订阅不会执行
initMyResources();
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 永远不会被调用!因为事件订阅未执行
}
}
正确代码:
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // ✅ 必须首先调用
initMyResources();
}
崩溃原因:super.onStart(context) 会初始化 mContext 并订阅 observedEvents() 返回的事件。不调用会导致 mContext 为 null、事件订阅无效。
错例 3:未过滤事件导致多播放器串台
错误代码:
public class MyStrategy extends BaseStrategy {
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 未过滤事件来源,多播放器场景会串台if (event instanceof PlayerEvents.StateChanged) {
// 所有播放器的状态变化都会触发此逻辑
updateUI(); // 可能操作错误的播放器 UI
}
}
}
正确代码:
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 首先过滤非当前播放器事件if (!isCurrentPlayer(event)) return;
if (event instanceof PlayerEvents.StateChanged) {
updateUI(); // 只处理当前播放器的事件
}
}
崩溃原因:在多播放器场景下,所有播放器的事件都会分发到所有策略。不进行过滤会导致策略处理其他播放器的事件。
错例 4:onStop 未清理异步任务导致内存泄漏
错误代码:
public class MyStrategy extends BaseStrategy {
private Handler handler = new Handler();
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context);
handler.postDelayed(() -> updateProgress(), 1000); // 延迟任务
}
@Overridepublic void onStop() {
// 忘记移除延迟任务super.onStop();
}
}
正确代码:
@Overridepublic void onStop() {
handler.removeCallbacksAndMessages(null); // 清理所有延迟任务super.onStop();
}
崩溃原因:延迟任务持有策略实例引用,策略停止后无法被 GC,导致内存泄漏。任务执行时可能访问已销毁的资源。
错例 5:observedEvents 返回 null 导致事件订阅失败
错误代码:
public class MyStrategy extends BaseStrategy {
@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
// 返回 null,虽然不会崩溃,但事件订阅不会执行return null;
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 永远不会被调用
}
}
正确代码:
@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // 声明要订阅的事件
PlayerEvents.Prepared.class
);
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// 现在可以正常收到事件了
}
崩溃原因:BaseStrategy 在订阅事件时会检查 observedEvents() 返回值,null 或空列表都不会订阅任何事件。
错例 6:onReset 未重置内部状态导致数据错误
错误代码:
public class MyStrategy extends BaseStrategy {
private int mPlayCount = 0;
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
if (!isCurrentPlayer(event)) return;
if (event instanceof PlayerEvents.Prepared) {
mPlayCount++; // 视频切换时未重置,会累积
Log.d(TAG, "Play count: " + mPlayCount);
}
}
@Overridepublic void onReset() {
super.onReset();
// 忘记重置 mPlayCount
}
}
正确代码:
@Overridepublic void onReset() {
super.onReset();
mPlayCount = 0; // 重置内部状态
}崩溃原因:onReset() 在视频切换时调用,用于重置策略内部状态。不重置会导致状态累积,影响统计准确性。