粘性事件:HarmonyOS APP开发中延迟事件处理

举报
Jack20 发表于 2026/06/19 18:58:35 2026/06/19
【摘要】 粘性事件:延迟事件处理“先订阅后发布"还是"先发布后订阅”?粘性事件让顺序不再重要 一、背景与动机:为什么需要粘性事件? 1.1 订阅顺序问题考虑这个场景:你的应用启动时需要获取当前网络状态,但网络状态变化事件已经发布过了。// ❌ 普通事件:错过了就没了// 1. 系统启动,网络连接,发布事件publish('NETWORK_CONNECTED') // 时刻T1// 2. 应用启动,...

粘性事件:延迟事件处理

“先订阅后发布"还是"先发布后订阅”?粘性事件让顺序不再重要

一、背景与动机:为什么需要粘性事件?

1.1 订阅顺序问题

考虑这个场景:你的应用启动时需要获取当前网络状态,但网络状态变化事件已经发布过了。

// ❌ 普通事件:错过了就没了
// 1. 系统启动,网络连接,发布事件
publish('NETWORK_CONNECTED')  // 时刻T1

// 2. 应用启动,订阅事件(晚了!)
subscribe('NETWORK_CONNECTED', callback)  // 时刻T2 > T1

// 问题:callback永远不会被调用,因为事件已经过去了

**粘性事件(Sticky Event)**解决了这个问题:事件会被缓存,新订阅者订阅时立即收到最近的事件。

// ✅ 粘性事件:不会错过
// 1. 系统启动,网络连接,发布粘性事件
publishSticky('NETWORK_CONNECTED')  // 事件被缓存

// 2. 应用启动,订阅粘性事件
subscribeSticky('NETWORK_CONNECTED', callback)  // 立即收到缓存的事件!

// callback会立即被调用,收到最新的网络状态

1.2 粘性事件的特点

图片.png

1.3 适用场景

场景 为什么用粘性事件
获取当前状态 订阅时立即获取最新状态,无需查询
配置同步 新组件启动时获取当前配置
位置信息 订阅时获取最后已知位置
网络状态 启动时立即知道是否联网
用户登录状态 新页面立即知道登录状态

二、核心原理:粘性事件机制

2.1 粘性事件存储

sequenceDiagram
    participant Publisher as 发布者
    participant Manager as CommonEventManager
    participant Cache as 粘性事件缓存
    participant Subscriber1 as 订阅者1
    participant Subscriber2 as 订阅者2(新)
    
    Note over Publisher: 发布粘性事件
    Publisher->>Manager: publish(event, data, sticky=true)
    Manager->>Cache: 保存事件
    Cache-->>Cache: 覆盖旧事件
    
    Note over Subscriber1: 已订阅
    Manager->>Subscriber1: 回调
    
    Note over Subscriber2: 新订阅(事件已发布)
    Subscriber2->>Manager: subscribe(event, getSticky=true)
    Manager->>Cache: 查找缓存事件
    Cache-->>Manager: 返回缓存数据
    Manager->>Subscriber2: 立即回调
    
    classDef primary fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    classDef warning fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef success fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    
    class Publisher,Subscriber1,Subscriber2 primary
    class Manager warning
    class Cache success

2.2 粘性事件 vs 普通事件

// 内部实现对比(伪代码)

// 普通事件发布
publishNormal(event: string, data: unknown) {
    // 直接分发给当前订阅者
    const subscribers = this.getSubscribers(event)
    for (const sub of subscribers) {
        sub.callback(data)
    }
    // 事件不保存,分发完就没了
}

// 粘性事件发布
publishSticky(event: string, data: unknown) {
    // 1. 保存到缓存(覆盖旧的)
    this.stickyCache.set(event, data)
    
    // 2. 分发给当前订阅者
    const subscribers = this.getSubscribers(event)
    for (const sub of subscribers) {
        sub.callback(data)
    }
}

// 普通事件订阅
subscribeNormal(event: string, callback: Function) {
    // 只添加到订阅列表
    this.addSubscriber(event, callback)
    // 不会立即回调
}

// 粘性事件订阅
subscribeSticky(event: string, callback: Function) {
    // 1. 添加到订阅列表
    this.addSubscriber(event, callback)
    
    // 2. 如果有缓存事件,立即回调
    if (this.stickyCache.has(event)) {
        const cachedData = this.stickyCache.get(event)
        callback(cachedData)  // 立即触发
    }
}

2.3 粘性事件生命周期

图片.png

三、代码实战:粘性事件应用

3.1 发布粘性事件

// StickyEventPublisher.ets
import { commonEventManager, CommonEventPublishData } from '@kit.BasicServicesKit'
import hilog from '@ohos.hilog'

const TAG = 'StickyPublisher'
const DOMAIN = 0xFF00

/**
 * 粘性事件发布者
 */
export class StickyEventPublisher {
    
    /**
     * 发布配置变更粘性事件
     * 新订阅者会立即收到当前配置
     */
    static async publishConfig(config: AppConfig): Promise<void> {
        const options: CommonEventPublishData = {
            event: 'com.example.CONFIG_CHANGED',
            data: JSON.stringify(config),
            
            // 关键:标记为粘性事件
            isSticky: true,
            
            parameters: {
                theme: config.theme,
                language: config.language,
                fontSize: config.fontSize
            }
        }
        
        try {
            await commonEventManager.publish(options)
            hilog.info(DOMAIN, TAG, `Config published: ${JSON.stringify(config)}`)
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Publish failed: ${err.message}`)
        }
    }
    
    /**
     * 发布用户登录状态粘性事件
     */
    static async publishLoginState(userInfo: UserInfo | null): Promise<void> {
        const options: CommonEventPublishData = {
            event: 'com.example.LOGIN_STATE',
            data: userInfo ? JSON.stringify(userInfo) : '',
            isSticky: true,
            
            parameters: {
                isLoggedIn: userInfo !== null,
                userId: userInfo?.id ?? 0,
                userName: userInfo?.name ?? ''
            }
        }
        
        try {
            await commonEventManager.publish(options)
            hilog.info(DOMAIN, TAG, `Login state published: ${userInfo ? 'logged in' : 'logged out'}`)
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Publish failed: ${err.message}`)
        }
    }
    
    /**
     * 发布网络状态粘性事件
     */
    static async publishNetworkState(status: NetworkStatus): Promise<void> {
        const options: CommonEventPublishData = {
            event: 'com.example.NETWORK_STATE',
            data: JSON.stringify(status),
            isSticky: true,
            
            parameters: {
                isConnected: status.isConnected,
                type: status.type,
                wifiName: status.wifiName ?? ''
            }
        }
        
        try {
            await commonEventManager.publish(options)
            hilog.info(DOMAIN, TAG, `Network state published`)
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Publish failed: ${err.message}`)
        }
    }
    
    /**
     * 发布位置信息粘性事件
     */
    static async publishLocation(location: Location | null): Promise<void> {
        const options: CommonEventPublishData = {
            event: 'com.example.LOCATION_UPDATE',
            data: location ? JSON.stringify(location) : '',
            isSticky: true,
            
            parameters: {
                hasLocation: location !== null,
                latitude: location?.latitude ?? 0,
                longitude: location?.longitude ?? 0,
                timestamp: Date.now()
            }
        }
        
        try {
            await commonEventManager.publish(options)
            hilog.info(DOMAIN, TAG, `Location published`)
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Publish failed: ${err.message}`)
        }
    }
}

/**
 * 应用配置
 */
interface AppConfig {
    theme: 'light' | 'dark'
    language: string
    fontSize: number
}

/**
 * 用户信息
 */
interface UserInfo {
    id: number
    name: string
    avatar?: string
}

/**
 * 网络状态
 */
interface NetworkStatus {
    isConnected: boolean
    type: string
    wifiName?: string
}

/**
 * 位置信息
 */
interface Location {
    latitude: number
    longitude: number
    accuracy?: number
}

3.2 订阅粘性事件

// StickyEventSubscriber.ets
import { commonEventManager, CommonEventSubscribeInfo, CommonEventData } from '@kit.BasicServicesKit'
import { EventHandler, EventRunner } from '@kit.BasicServicesKit'
import hilog from '@ohos.hilog'

const TAG = 'StickySubscriber'
const DOMAIN = 0xFF00

/**
 * 粘性事件订阅者
 */
export class StickyEventSubscriber {
    private subscriber: commonEventManager.CommonEventSubscriber = null
    private mainHandler: EventHandler
    
    // 当前状态(从粘性事件获取)
    private currentConfig: AppConfig | null = null
    private isLoggedIn: boolean = false
    private currentUser: UserInfo | null = null
    private networkStatus: NetworkStatus | null = null
    
    // 状态变化回调
    private onConfigChange: ((config: AppConfig) => void) | null = null
    private onLoginStateChange: ((isLoggedIn: boolean, user: UserInfo | null) => void) | null = null
    private onNetworkChange: ((status: NetworkStatus) => void) | null = null
    
    constructor() {
        // 创建主线程Handler
        const mainRunner = EventRunner.getMainEventRunner()
        this.mainHandler = new EventHandler(mainRunner)
    }
    
    /**
     * 设置配置变化回调
     */
    setOnConfigChange(callback: (config: AppConfig) => void): void {
        this.onConfigChange = callback
    }
    
    /**
     * 设置登录状态变化回调
     */
    setOnLoginStateChange(callback: (isLoggedIn: boolean, user: UserInfo | null) => void): void {
        this.onLoginStateChange = callback
    }
    
    /**
     * 设置网络状态变化回调
     */
    setOnNetworkChange(callback: (status: NetworkStatus) => void): void {
        this.onNetworkChange = callback
    }
    
    /**
     * 开始订阅粘性事件
     */
    async startSubscribing(): Promise<void> {
        const subscribeInfo: CommonEventSubscribeInfo = {
            events: [
                'com.example.CONFIG_CHANGED',
                'com.example.LOGIN_STATE',
                'com.example.NETWORK_STATE',
                'com.example.LOCATION_UPDATE'
            ],
            
            // 关键:请求获取粘性事件
            getStickEvent: true
        }
        
        try {
            this.subscriber = await commonEventManager.createSubscriber(subscribeInfo)
            
            this.subscriber.subscribe((err, data: CommonEventData) => {
                if (err) {
                    hilog.error(DOMAIN, TAG, `Subscribe error: ${err.message}`)
                    return
                }
                
                // 转发到主线程处理
                this.mainHandler.sendEvent({
                    eventId: 1,
                    param: data
                })
            })
            
            // 设置主线程事件处理
            this.mainHandler.setEventHandler((eventId, param) => {
                if (eventId === 1) {
                    this.handleStickyEvent(param as CommonEventData)
                }
            })
            
            hilog.info(DOMAIN, TAG, 'Sticky event subscriber started')
            hilog.info(DOMAIN, TAG, 'Will receive cached events immediately')
            
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Start failed: ${err.message}`)
        }
    }
    
    /**
     * 处理粘性事件
     */
    private handleStickyEvent(data: CommonEventData): void {
        hilog.info(DOMAIN, TAG, `Received sticky event: ${data.event}`)
        
        switch (data.event) {
            case 'com.example.CONFIG_CHANGED':
                this.handleConfigChange(data)
                break
                
            case 'com.example.LOGIN_STATE':
                this.handleLoginStateChange(data)
                break
                
            case 'com.example.NETWORK_STATE':
                this.handleNetworkStateChange(data)
                break
                
            case 'com.example.LOCATION_UPDATE':
                this.handleLocationUpdate(data)
                break
        }
    }
    
    /**
     * 处理配置变化
     */
    private handleConfigChange(data: CommonEventData): void {
        try {
            // 解析配置数据
            this.currentConfig = JSON.parse(data.data) as AppConfig
            
            hilog.info(DOMAIN, TAG, `Config: theme=${this.currentConfig.theme}, lang=${this.currentConfig.language}`)
            
            // 触发回调
            if (this.onConfigChange) {
                this.onConfigChange(this.currentConfig)
            }
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Parse config failed: ${err.message}`)
        }
    }
    
    /**
     * 处理登录状态变化
     */
    private handleLoginStateChange(data: CommonEventData): void {
        const params = data.parameters
        this.isLoggedIn = params?.isLoggedIn ?? false
        
        if (this.isLoggedIn && data.data) {
            try {
                this.currentUser = JSON.parse(data.data) as UserInfo
            } catch (err) {
                this.currentUser = null
            }
        } else {
            this.currentUser = null
        }
        
        hilog.info(DOMAIN, TAG, `Login state: ${this.isLoggedIn}`)
        
        if (this.onLoginStateChange) {
            this.onLoginStateChange(this.isLoggedIn, this.currentUser)
        }
    }
    
    /**
     * 处理网络状态变化
     */
    private handleNetworkStateChange(data: CommonEventData): void {
        try {
            this.networkStatus = JSON.parse(data.data) as NetworkStatus
            
            hilog.info(DOMAIN, TAG, `Network: ${this.networkStatus.isConnected ? 'connected' : 'disconnected'}`)
            
            if (this.onNetworkChange) {
                this.onNetworkChange(this.networkStatus)
            }
        } catch (err) {
            hilog.error(DOMAIN, TAG, `Parse network state failed: ${err.message}`)
        }
    }
    
    /**
     * 处理位置更新
     */
    private handleLocationUpdate(data: CommonEventData): void {
        const params = data.parameters
        const hasLocation = params?.hasLocation ?? false
        
        if (hasLocation && data.data) {
            try {
                const location = JSON.parse(data.data) as Location
                hilog.info(DOMAIN, TAG, `Location: ${location.latitude}, ${location.longitude}`)
            } catch (err) {
                hilog.error(DOMAIN, TAG, `Parse location failed: ${err.message}`)
            }
        }
    }
    
    /**
     * 获取当前配置(从粘性事件获取)
     */
    getCurrentConfig(): AppConfig | null {
        return this.currentConfig
    }
    
    /**
     * 获取登录状态
     */
    getLoginState(): { isLoggedIn: boolean, user: UserInfo | null } {
        return {
            isLoggedIn: this.isLoggedIn,
            user: this.currentUser
        }
    }
    
    /**
     * 获取网络状态
     */
    getNetworkStatus(): NetworkStatus | null {
        return this.networkStatus
    }
    
    /**
     * 停止订阅
     */
    async stopSubscribing(): Promise<void> {
        if (this.subscriber) {
            try {
                await this.subscriber.unsubscribe()
                hilog.info(DOMAIN, TAG, 'Sticky event subscriber stopped')
            } catch (err) {
                hilog.error(DOMAIN, TAG, `Stop failed: ${err.message}`)
            }
        }
    }
}

3.3 实战:应用配置同步

// ConfigSync.ets
import { StickyEventPublisher } from './StickyEventPublisher'
import { StickyEventSubscriber } from './StickyEventSubscriber'
import hilog from '@ohos.hilog'

const TAG = 'ConfigSync'
const DOMAIN = 0xFF00

/**
 * 应用配置管理器
 * 使用粘性事件实现配置同步
 */
export class ConfigManager {
    private static instance: ConfigManager
    private subscriber: StickyEventSubscriber
    
    // 当前配置
    private config: AppConfig = {
        theme: 'light',
        language: 'zh',
        fontSize: 14
    }
    
    // 配置变化回调
    private onConfigUpdate: ((config: AppConfig) => void) | null = null
    
    private constructor() {
        this.subscriber = new StickyEventSubscriber()
    }
    
    /**
     * 获取单例
     */
    static getInstance(): ConfigManager {
        if (!ConfigManager.instance) {
            ConfigManager.instance = new ConfigManager()
        }
        return ConfigManager.instance
    }
    
    /**
     * 初始化
     */
    async initialize(): Promise<void> {
        // 设置配置变化回调
        this.subscriber.setOnConfigChange((config) => {
            this.config = config
            hilog.info(DOMAIN, TAG, `Config updated: ${JSON.stringify(config)}`)
            
            if (this.onConfigUpdate) {
                this.onConfigUpdate(config)
            }
        })
        
        // 开始订阅(会立即收到缓存的配置)
        await this.subscriber.startSubscribing()
        
        hilog.info(DOMAIN, TAG, 'ConfigManager initialized')
    }
    
    /**
     * 设置配置更新回调
     */
    setOnConfigUpdate(callback: (config: AppConfig) => void): void {
        this.onConfigUpdate = callback
    }
    
    /**
     * 获取当前配置
     */
    getConfig(): AppConfig {
        return { ...this.config }
    }
    
    /**
     * 更新配置
     */
    async updateConfig(newConfig: Partial<AppConfig>): Promise<void> {
        // 合并配置
        this.config = { ...this.config, ...newConfig }
        
        // 发布粘性事件(通知所有订阅者)
        await StickyEventPublisher.publishConfig(this.config)
        
        hilog.info(DOMAIN, TAG, `Config updated and published`)
    }
    
    /**
     * 切换主题
     */
    async toggleTheme(): Promise<void> {
        const newTheme = this.config.theme === 'light' ? 'dark' : 'light'
        await this.updateConfig({ theme: newTheme })
    }
    
    /**
     * 切换语言
     */
    async changeLanguage(language: string): Promise<void> {
        await this.updateConfig({ language })
    }
    
    /**
     * 调整字体大小
     */
    async adjustFontSize(delta: number): Promise<void> {
        const newSize = Math.max(12, Math.min(20, this.config.fontSize + delta))
        await this.updateConfig({ fontSize: newSize })
    }
    
    /**
     * 销毁
     */
    async destroy(): Promise<void> {
        await this.subscriber.stopSubscribing()
    }
}

/**
 * 在UI组件中使用
 */
@Entry
@Component
struct ConfigPage {
    @State theme: string = 'light'
    @State language: string = 'zh'
    @State fontSize: number = 14
    
    private configManager: ConfigManager = ConfigManager.getInstance()
    
    async aboutToAppear() {
        // 设置配置更新回调
        this.configManager.setOnConfigUpdate((config) => {
            // 更新UI(在主线程)
            this.theme = config.theme
            this.language = config.language
            this.fontSize = config.fontSize
        })
        
        // 初始化(会立即获取当前配置)
        await this.configManager.initialize()
        
        // 获取初始配置
        const config = this.configManager.getConfig()
        this.theme = config.theme
        this.language = config.language
        this.fontSize = config.fontSize
    }
    
    build() {
        Column() {
            Text('应用配置')
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
            
            // 主题切换
            Row() {
                Text('主题:')
                    .width(80)
                Toggle({ type: ToggleType.Switch, isOn: this.theme === 'dark' })
                    .onChange((isOn) => {
                        this.configManager.toggleTheme()
                    })
            }
            .width('100%')
            .margin({ top: 20 })
            
            // 语言选择
            Row() {
                Text('语言:')
                    .width(80)
                Select([
                    { value: '中文' },
                    { value: 'English' }
                ])
                    .selected(this.language === 'zh' ? 0 : 1)
                    .onSelect((index) => {
                        this.configManager.changeLanguage(index === 0 ? 'zh' : 'en')
                    })
            }
            .width('100%')
            .margin({ top: 20 })
            
            // 字体大小
            Row() {
                Text('字体大小:')
                    .width(80)
                Text(`${this.fontSize}`)
                    .width(50)
                Button('-')
                    .onClick(() => {
                        this.configManager.adjustFontSize(-1)
                    })
                Button('+')
                    .onClick(() => {
                        this.configManager.adjustFontSize(1)
                    })
            }
            .width('100%')
            .margin({ top: 20 })
        }
        .width('100%')
        .height('100%')
        .padding(20)
    }
}

3.4 实战:多页面状态同步

// MultiPageSync.ets
import { StickyEventPublisher } from './StickyEventPublisher'
import { StickyEventSubscriber } from './StickyEventSubscriber'
import router from '@ohos.router'
import hilog from '@ohos.hilog'

const TAG = 'MultiPageSync'
const DOMAIN = 0xFF00

/**
 * 购物车状态
 */
interface CartState {
    items: CartItem[]
    totalCount: number
    totalPrice: number
}

interface CartItem {
    id: string
    name: string
    price: number
    quantity: number
}

/**
 * 购物车管理器
 * 使用粘性事件实现多页面同步
 */
export class CartManager {
    private static instance: CartManager
    private cartState: CartState = {
        items: [],
        totalCount: 0,
        totalPrice: 0
    }
    
    private constructor() {}
    
    static getInstance(): CartManager {
        if (!CartManager.instance) {
            CartManager.instance = new CartManager()
        }
        return CartManager.instance
    }
    
    /**
     * 添加商品到购物车
     */
    async addItem(item: CartItem): Promise<void> {
        // 查找是否已存在
        const existingIndex = this.cartState.items.findIndex(i => i.id === item.id)
        
        if (existingIndex >= 0) {
            // 增加数量
            this.cartState.items[existingIndex].quantity += item.quantity
        } else {
            // 添加新商品
            this.cartState.items.push(item)
        }
        
        // 更新统计
        this.updateStats()
        
        // 发布粘性事件
        await this.publishCartState()
    }
    
    /**
     * 移除商品
     */
    async removeItem(itemId: string): Promise<void> {
        this.cartState.items = this.cartState.items.filter(i => i.id !== itemId)
        this.updateStats()
        await this.publishCartState()
    }
    
    /**
     * 更新商品数量
     */
    async updateQuantity(itemId: string, quantity: number): Promise<void> {
        const item = this.cartState.items.find(i => i.id === itemId)
        if (item) {
            if (quantity <= 0) {
                await this.removeItem(itemId)
            } else {
                item.quantity = quantity
                this.updateStats()
                await this.publishCartState()
            }
        }
    }
    
    /**
     * 清空购物车
     */
    async clearCart(): Promise<void> {
        this.cartState = {
            items: [],
            totalCount: 0,
            totalPrice: 0
        }
        await this.publishCartState()
    }
    
    /**
     * 更新统计信息
     */
    private updateStats(): void {
        this.cartState.totalCount = this.cartState.items.reduce((sum, item) => sum + item.quantity, 0)
        this.cartState.totalPrice = this.cartState.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
    }
    
    /**
     * 发布购物车状态(粘性事件)
     */
    private async publishCartState(): Promise<void> {
        const options = {
            event: 'com.example.CART_STATE',
            data: JSON.stringify(this.cartState),
            isSticky: true,
            parameters: {
                totalCount: this.cartState.totalCount,
                totalPrice: this.cartState.totalPrice
            }
        }
        
        await commonEventManager.publish(options)
        hilog.info(DOMAIN, TAG, `Cart state published: ${this.cartState.totalCount} items`)
    }
    
    /**
     * 获取当前购物车状态
     */
    getCartState(): CartState {
        return { ...this.cartState }
    }
}

/**
 * 商品列表页
 */
@Entry
@Component
struct ProductListPage {
    @State cartCount: number = 0
    
    private cartManager: CartManager = CartManager.getInstance()
    private subscriber: StickyEventSubscriber = new StickyEventSubscriber()
    
    async aboutToAppear() {
        // 订阅购物车状态(粘性事件)
        this.subscriber.setOnCartStateChange((state: CartState) => {
            this.cartCount = state.totalCount
        })
        
        await this.subscriber.startSubscribing()
    }
    
    async aboutToDisappear() {
        await this.subscriber.stopSubscribing()
    }
    
    build() {
        Column() {
            // 购物车图标(显示数量)
            Row() {
                Text('商品列表')
                    .fontSize(20)
                    .fontWeight(FontWeight.Bold)
                
                Blank()
                
                Badge({ count: this.cartCount, position: BadgePosition.RightTop }) {
                    Image($r('app.media.cart_icon'))
                        .width(30)
                        .height(30)
                }
                .onClick(() => {
                    router.pushUrl({ url: 'pages/CartPage' })
                })
            }
            .width('100%')
            .padding(10)
            
            // 商品列表
            List() {
                ForEach(['商品A', '商品B', '商品C'], (name: string) => {
                    ListItem() {
                        Row() {
                            Text(name)
                            Blank()
                            Button('加入购物车')
                                .onClick(async () => {
                                    await this.cartManager.addItem({
                                        id: name,
                                        name: name,
                                        price: 99.9,
                                        quantity: 1
                                    })
                                })
                        }
                        .width('100%')
                        .padding(10)
                    }
                })
            }
            .width('100%')
            .layoutWeight(1)
        }
        .width('100%')
        .height('100%')
    }
}

/**
 * 购物车页面
 */
@Entry
@Component
struct CartPage {
    @State cartState: CartState = {
        items: [],
        totalCount: 0,
        totalPrice: 0
    }
    
    private cartManager: CartManager = CartManager.getInstance()
    private subscriber: StickyEventSubscriber = new StickyEventSubscriber()
    
    async aboutToAppear() {
        // 订阅购物车状态
        // 粘性事件:立即获取当前购物车状态
        this.subscriber.setOnCartStateChange((state: CartState) => {
            this.cartState = state
        })
        
        await this.subscriber.startSubscribing()
        
        hilog.info(DOMAIN, TAG, `Cart page loaded with ${this.cartState.totalCount} items`)
    }
    
    async aboutToDisappear() {
        await this.subscriber.stopSubscribing()
    }
    
    build() {
        Column() {
            Text('购物车')
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
            
            List() {
                ForEach(this.cartState.items, (item: CartItem) => {
                    ListItem() {
                        Row() {
                            Text(item.name)
                            Blank()
                            Text(`¥${item.price}`)
                            Text(` x ${item.quantity}`)
                            Button('删除')
                                .onClick(async () => {
                                    await this.cartManager.removeItem(item.id)
                                })
                        }
                        .width('100%')
                        .padding(10)
                    }
                })
            }
            .width('100%')
            .layoutWeight(1)
            
            // 总价
            Row() {
                Text(`总计: ¥${this.cartState.totalPrice.toFixed(2)}`)
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold)
                
                Blank()
                
                Button('结算')
                    .enabled(this.cartState.totalCount > 0)
            }
            .width('100%')
            .padding(10)
        }
        .width('100%')
        .height('100%')
    }
}

四、踩坑与注意事项

4.1 坑一:粘性事件堆积

问题:多次发布粘性事件,只有最新的会被缓存,旧事件会丢失。

// ❌ 多次发布不同数据
await publishSticky('MY_EVENT', { version: 1 })
await publishSticky('MY_EVENT', { version: 2 })
await publishSticky('MY_EVENT', { version: 3 })

// 新订阅者只会收到 version: 3
// version: 1 和 version: 2 丢失了

// ✅ 如果需要保留历史,使用数组
const history: Data[] = []
history.push({ version: 1 })
history.push({ version: 2 })
history.push({ version: 3 })

await publishSticky('MY_EVENT_HISTORY', history)

4.2 坑二:粘性事件数据过期

问题:缓存的粘性事件可能已经过期。

// ❌ 直接使用粘性事件数据
this.subscriber.setOnDataChange((data) => {
    // data可能是很久以前的数据
    this.processData(data)
})

// ✅ 检查数据时效性
this.subscriber.setOnDataChange((data) => {
    const timestamp = data.parameters?.timestamp ?? 0
    const now = Date.now()
    const age = now - timestamp
    
    if (age > 60000) {  // 数据超过1分钟
        // 数据可能过期,重新获取
        this.refreshData()
    } else {
        // 数据新鲜,直接使用
        this.processData(data)
    }
})

4.3 坑三:订阅时立即回调导致的问题

问题:粘性事件订阅时会立即回调,此时组件可能未完全初始化。

// ❌ 回调时组件未初始化
@Entry
@Component
struct MyPage {
    @State data: string = ''
    
    async aboutToAppear() {
        // 订阅时立即回调,但this.data可能还未初始化
        this.subscriber.setOnDataChange((data) => {
            this.data = data  // 可能出错
        })
        
        await this.subscriber.startSubscribing()
    }
}

// ✅ 使用标志位控制
@Entry
@Component
struct MyPage {
    @State data: string = ''
    private isReady: boolean = false
    
    async aboutToAppear() {
        this.subscriber.setOnDataChange((data) => {
            if (this.isReady) {
                this.data = data
            }
        })
        
        await this.subscriber.startSubscribing()
        
        // 标记为就绪
        this.isReady = true
    }
}

4.4 坑四:忘记设置getStickEvent

问题:订阅时忘记设置getStickEvent: true,收不到缓存事件。

// ❌ 忘记设置getStickEvent
const subscribeInfo: CommonEventSubscribeInfo = {
    events: ['com.example.MY_EVENT']
    // 缺少 getStickEvent: true
}

// 订阅后不会收到缓存的粘性事件

// ✅ 正确设置
const subscribeInfo: CommonEventSubscribeInfo = {
    events: ['com.example.MY_EVENT'],
    getStickEvent: true  // 关键!
}

五、HarmonyOS 6适配指南

5.1 API变更

变更项 HarmonyOS 5 HarmonyOS 6
粘性事件发布 isSticky: true 同上
粘性事件订阅 getStickEvent: true 同上
清除粘性事件 新增removeStickyEvent()

5.2 新增功能

// HarmonyOS 6新增:清除粘性事件
import { commonEventManager } from '@kit.BasicServicesKit'

// 清除特定粘性事件
await commonEventManager.removeStickyEvent('com.example.MY_EVENT')

// 清除所有粘性事件
await commonEventManager.removeAllStickyEvents()

// HarmonyOS 6新增:获取粘性事件(不订阅)
const stickyData = await commonEventManager.getStickyEvent('com.example.MY_EVENT')
if (stickyData) {
    console.log(`Current sticky event: ${stickyData.data}`)
}

5.3 性能优化

// HarmonyOS 6:粘性事件缓存管理
class StickyEventManager {
    // 设置缓存过期时间
    static setCacheExpiry(event: string, ttl: number) {
        // ttl: 毫秒
        // 超过ttl的缓存事件会被清除
    }
    
    // 设置缓存大小限制
    static setMaxCacheSize(maxSize: number) {
        // 限制粘性事件缓存的总大小
    }
}

// 使用示例
StickyEventManager.setCacheExpiry('com.example.LOCATION', 300000)  // 5分钟
StickyEventManager.setMaxCacheSize(1024 * 1024)  // 1MB

六、总结

6.1 核心要点回顾

图片.png

6.2 最佳实践清单

  • [ ] 发布时设置isSticky: true
  • [ ] 订阅时设置getStickEvent: true
  • [ ] 检查数据时效性
  • [ ] 处理组件未初始化的情况
  • [ ] 使用时间戳标记数据新鲜度
  • [ ] 定期清理过期的粘性事件
  • [ ] 避免存储大量数据在粘性事件中

6.3 适用场景总结

场景 粘性事件优势
状态同步 新页面立即获取当前状态
配置管理 配置变更立即生效
购物车 多页面数据实时同步
用户登录 新页面立即知道登录状态
网络状态 启动时立即获取网络状态

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。