HarmonyOS 多设备短视频开发 :24 — AppStorage/AppStorageV2 应用级状态
页面级状态由组件装饰器管理,而"跨页面、跨组件树、与应用同生命周期"的全局状态需要专门容器:V1 时代的 AppStorage 与 V2 时代的 AppStorageV2。本项目用 AppStorageV2 承载最关键的全局对象——窗口信息 WindowInfo,由 common/multishortvideobase/src/main/ets/utils/WindowUtil.ets 统一维护;同时用 @Provider/@Consumer 在页面根部构建 Provider 树共享路由栈、侧栏开关、深色模式等。本文对比 AppStorage 与 AppStorageV2 的差异,结合项目代码讲解全局状态的最佳实践。

二、AppStorage 基础与局限
AppStorage 是 V1 的 UI 应用级状态容器,单例驻留于 UIAbility 生命周期内,支持简单类型与对象。V1 常用写法:
// V1 写法(对照):存储与读取全局属性
AppStorage.setOrCreate('isDark', false); // 写入
const isDark = AppStorage.get<boolean>('isDark'); // 读取
局限同样明显:对嵌套对象需配合 @Observed/@ObjectLink 才能深层观察;所有操作都走字符串 key,缺少类型约束;装饰器(@StorageLink/@StorageProp)与 V2 组件体系不兼容,项目迁到 V2 后无法直接使用。因此在 V2 工程中,AppStorage 仅作为兼容层存在,新代码应使用 AppStorageV2。
三、AppStorageV2 与 connect 连接
AppStorageV2 以"连接类对象"为核心:用 @ObservedV2 装饰的类作为存储类型,connect 建立连接后返回同一实例,任何持有该实例的组件都能观察其 @Trace 属性。WindowUtil.ets 中的实现:
// common/multishortvideobase/src/main/ets/utils/WindowUtil.ets(真实代码)
import { AppStorageV2, UIContext, window } from '@kit.ArkUI';
export const WINDOW_INFO_STORAGE_KEY = 'MultiShortVideoWindowInfo';
@ObservedV2
export class WindowInfo {
@Trace public windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED;
@Trace public isFullScreen: boolean = false;
@Trace public orientation: window.Orientation = window.Orientation.UNSPECIFIED;
@Trace public windowSize: window.Size = { width: 0, height: 0 };
@Trace public widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_XS;
@Trace public heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM;
...
}
export function getWindowInfoState(): WindowInfo {
return AppStorageV2.connect<WindowInfo>(WindowInfo, WINDOW_INFO_STORAGE_KEY, () => new WindowInfo())!;
}
connect 的三个参数分别是存储类型、存储键、默认值工厂。首次调用创建并存入,之后任何模块调用 getWindowInfoState() 拿到的都是同一实例;由于类成员带 @Trace,属性修改会精确通知所有引用该对象的组件。WindowUtil 单例持有 mainWindowInfo 引用,在窗口回调中就地修改属性:
public onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => {
this.mainWindowInfo.windowSize = windowSize;
this.mainWindowInfo.widthBp = this.uiContext!.getWindowWidthBreakpoint();
this.mainWindowInfo.heightBp = this.uiContext!.getWindowHeightBreakpoint();
};
组件侧只需 @Local windowInfo: WindowInfo = getWindowInfoState(),即可在断点、避让区、沉浸状态变化时自动刷新,无需任何手动通知——这是本项目"断点驱动自适应布局"的状态基石。
四、Provider/Consumer 树:页面级全局状态的分工
AppStorageV2 解决"应用级、跨模块"状态;页面内跨组件通信则交给 @Provider/@Consumer 树,二者分工如下:
| 状态 | 载体 | 生命周期 | 示例 |
|---|---|---|---|
| 窗口信息、避让区 | AppStorageV2 + WindowInfo | 整个应用 | widthBp、statusBarTopVp |
| 主题/侧栏/路由 | @Provider/@Consumer | 页面/路由栈 | isDark、showSideComment |
| 播放进度 | @Provider/@Consumer | 视频页组件树 | currentTime、duration |
products/default/src/main/ets/view/Index.ets 在根组件构建 Provider 树:
@Entry
@ComponentV2
struct Index {
@Local windowInfo: WindowInfo = getWindowInfoState();
@Provider('isDark') isDark: boolean = false;
@Provider('pathStack') pathStack: NavPathStack = new NavPathStack();
@Provider('showSideComment') showSideComment: boolean = false;
@Provider('showSideIndividual') showSideIndividual: boolean = false;
...
}
视频页 AdaptiveVideo.ets 与播放器 AdaptiveAVPlayer.ets 则跨层消费/转发(@Consumer('showSideComment')、@Consumer('currentTime') 等)。当用户在大屏点击评论图标时,AdaptiveVideo 修改 showSideComment 并 pushPathByName(‘SplitComment’),根组件 Navigation 随即切换为分栏模式;点击空白区域时,根组件的手势判定会 pop 路由并复位侧栏开关——Provider/Consumer 树让"深层组件改、根部响应"成为可能。
值得注意的还有 AdaptiveVideo 的"消费再提供"模式:它从根组件消费 pathStack 与侧栏开关,同时把播放器上报的进度(@Consumer(‘currentTime’)/@Consumer(‘duration’))以 @Provider(‘windowSizeWidth’)/@Provider(‘commentCount’) 等继续下发给更深层的 UI,形成"中转站"。这比让每个深层组件都直接连接 AppStorageV2 更符合"就近提供、按需消费"的原则——AppStorageV2 只存放真正需要全局唯一的对象,页面内部的共享数据留在组件树内,既减少全局命名空间污染,也让数据流的追踪路径保持在页面范围内。
五、AppStorageV2 的更多能力与生命周期
AppStorageV2 除 connect 外,还提供 remove 等管理接口,可用于在页面退出时解除连接:
// 解除指定 key 的连接(AppStorageV2 管理接口)
AppStorageV2.remove<WindowInfo>(WINDOW_INFO_STORAGE_KEY);
需要明确的是:AppStorageV2 的生命周期与应用进程一致,窗口信息这类"从 Ability 启动持续到退出"的状态无需手动 remove;若某类全局状态只在特定页面存在(如临时会话上下文),则在页面销毁时 remove 并置空引用,避免内存泄漏。此外,AppStorageV2 存的是可观察对象(@ObservedV2 类),与 V1 AppStorage 的"值拷贝存储"不同,连接方拿到的是同一引用,修改 @Trace 属性即为修改全局状态,这也要求写入方遵守单向数据流约定,避免多处同时改写导致状态发散。
六、跨组件通信的最佳实践
- 全局唯一且跨模块共享 → AppStorageV2.connect:如 WindowInfo。存储键用模块级常量(WINDOW_INFO_STORAGE_KEY),避免魔法字符串。
- 页面内跨层共享 → @Provider/@Consumer:key 统一字符串命名,Provider 尽量集中在页面根部(Index.ets),便于追溯。
- 父子直连 → @Param/@Event:能用 @Param 传参就不用 Provider,缩小影响面。
- AppStorageV2 存放可观察类对象,而非散落的基本类型:类对象配合 @Trace 支持属性级刷新,散落键值则难以组织与清理。
- AppStorageV2 与持久化解耦:AppStorageV2 只存内存态(进程内),如需跨启动恢复(断点、播放位置)应配合 Preferences 落地(见文章 25)。
七、总结与最佳实践
- V2 工程优先使用 AppStorageV2:connect 连接 @ObservedV2 类,类型安全、属性级可观察、全模块共享。
- WindowInfo 是"全局状态单例"的教科书实现:单例维护(WindowUtil)+ 全局存储(AppStorageV2)+ 组件观察(@Local + getWindowInfoState)三层协作。
- 页面级共享状态(主题、侧栏、路由)用 @Provider/@Consumer 树收敛在根部,避免滥用 AppStorageV2。
- 新旧代码共存时,V1 的 AppStorage 仅作兼容层,不新增依赖。
- 明确状态生命周期:应用级状态进 AppStorageV2,页面级状态进 Provider 树,临时状态用 @Local,职责清晰才能保证多设备工程的可维护性。
更多推荐




所有评论(0)