HarmonyOS开发:卡片生命周期——创建、更新、删除与定时刷新

举报
Jack20 发表于 2026/06/27 22:32:02 2026/06/27
【摘要】 HarmonyOS开发:卡片生命周期——创建、更新、删除与定时刷新📌 核心要点:卡片生命周期由系统驱动,onAddForm/onUpdateForm/onRemoveForm三个回调构成了卡片从生到死的完整链路,理解每个阶段的时序和约束是写出稳定卡片的前提。 背景与动机你有没有遇到过这种诡异的情况:卡片添加上去好好的,过一会儿数据就不对了?或者用户删了卡片,你的后台还在拼命更新一个不存在...

HarmonyOS开发:卡片生命周期——创建、更新、删除与定时刷新

📌 核心要点:卡片生命周期由系统驱动,onAddForm/onUpdateForm/onRemoveForm三个回调构成了卡片从生到死的完整链路,理解每个阶段的时序和约束是写出稳定卡片的前提。

背景与动机

你有没有遇到过这种诡异的情况:卡片添加上去好好的,过一会儿数据就不对了?或者用户删了卡片,你的后台还在拼命更新一个不存在的formId?

这些问题的根源都是——没搞清楚卡片的生命周期。

卡片不是普通UI组件,它的生命周期不由你的代码控制,而是由系统调度。用户什么时候添加、系统什么时候触发更新、用户什么时候删除,你只能被动响应。但"被动响应"不等于"随便写",每个回调都有严格的执行约束。超时了数据就丢了,异常了卡片就白屏了。

搞清楚卡片生命周期,你才能知道:

  • 在哪个回调里做什么事最合适
  • 哪些操作不能在回调里做
  • 怎么在生命周期切换时保持数据一致性
  • 定时刷新到底是怎么触发的

核心原理

卡片完整生命周期

flowchart TB
    A[用户长按桌面<br/>选择添加卡片] --> B[系统创建卡片实例]
    B --> C[onAddForm回调]
    C --> D[返回FormBindingData]
    D --> E[卡片渲染显示]
    
    E --> F{等待更新触发}
    F -->|定时触发| G[onUpdateForm回调]
    F -->|应用主动更新| H[formProvider.updateForm]
    F -->|卡片可见性变化| I[onVisibilityChange]
    
    G --> J[返回新FormBindingData]
    J --> E
    
    H --> K[直接更新卡片数据]
    K --> E
    
    I --> L[暂停/恢复更新]
    L --> F
    
    E --> M[用户长按卡片<br/>选择移除]
    M --> N[onRemoveForm回调]
    N --> O[清理资源]
    O --> P[卡片从桌面消失]
    
    E --> Q[应用被卸载]
    Q --> N
    
    classDef userAction fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    classDef callback fill:#E3F2FD,stroke:#1565C0,color:#0D47A1
    classDef data fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef endState fill:#FCE4EC,stroke:#C62828,color:#B71C1C
    
    class A,M,Q userAction
    class C,G,I,N callback
    class D,J,K,H data
    class E,P,O endState

这张图涵盖了卡片从生到死的所有路径。但有几个细节需要特别说明:

  1. onAddForm只执行一次——用户添加卡片时触发,之后不会再触发
  2. onUpdateForm可以执行多次——每次系统触发更新都会调用
  3. onRemoveForm是最终回调——执行完这个,卡片就彻底没了
  4. formProvider.updateForm不触发onUpdateForm——它是直接更新,绕过FormAbility

回调执行时序

sequenceDiagram
    participant User as 用户
    participant Desktop as 桌面
    participant FMS as FormManagerService
    participant FA as FormAbility
    participant Cache as 数据缓存
    
    Note over User,Cache: 阶段一:创建
    User->>Desktop: 添加卡片
    Desktop->>FMS: 请求创建
    FMS->>FA: onAddForm(want)
    FA->>Cache: 读取缓存数据
    Cache-->>FA: 返回缓存
    FA-->>FMS: FormBindingData
    FMS->>Desktop: 渲染卡片
    
    Note over User,Cache: 阶段二:定时更新
    loop 每隔N小时
        FMS->>FA: onUpdateForm(formId)
        FA->>Cache: 读取最新数据
        Cache-->>FA: 返回数据
        FA-->>FMS: 新FormBindingData
        FMS->>Desktop: 刷新卡片
    end
    
    Note over User,Cache: 阶段三:删除
    User->>Desktop: 移除卡片
    Desktop->>FMS: 请求删除
    FMS->>FA: onRemoveForm(formId)
    FA->>Cache: 清理该卡片缓存
    FA-->>FMS: 完成
    FMS->>Desktop: 移除卡片UI

注意一个关键细节:formProvider.updateForm和onUpdateForm是两条独立的更新路径

  • onUpdateForm:系统定时触发,你在回调里返回新数据
  • formProvider.updateForm:你主动调用,直接推送数据给卡片

两者不冲突,可以同时使用。但要注意数据一致性——两条路径推的数据不能矛盾。

定时刷新的触发机制

定时刷新不是简单的"每隔N小时执行一次"。它的触发逻辑是这样的:

  1. 系统读取form_config.json中的scheduledUpdateTimeupdateDuration
  2. scheduledUpdateTime时刻,系统检查是否满足更新条件(电量、网络、用户活跃度)
  3. 满足条件则触发onUpdateForm,不满足则延迟
  4. 如果updateDuration > 0,系统会在后续时间点继续检查和触发
flowchart LR
    A[scheduledUpdateTime<br/>07:00] --> B{系统检查条件}
    B -->|电量>20%<br/>网络可用<br/>用户活跃| C[触发onUpdateForm]
    B -->|条件不满足| D[延迟触发<br/>可能07:15或更晚]
    C --> E[等待下一个周期]
    D --> E
    
    E --> F{updateDuration}
    F -->|=0| G[仅此一次<br/>明天07:00再触发]
    F -->|=1| H[1小时后再次检查<br/>08:00]
    F -->|=2| I[2小时后再次检查<br/>09:00]
    
    classDef time fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    classDef check fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef action fill:#E3F2FD,stroke:#1565C0,color:#0D47A1
    
    class A time
    class B check
    class C,D action
    class G,H,I action

代码实战

基础用法:完整的生命周期管理

一个健壮的FormAbility必须正确处理每个生命周期回调,并维护卡片的状态。

// LifecycleFormAbility.ets
import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'

// 卡片状态管理
interface CardState {
  formId: string
  formName: string
  dimension: number
  createdAt: number
  lastUpdated: number
  data: Record<string, string>
}

class CardStateManager {
  private cards: Map<string, CardState> = new Map()
  private static instance: CardStateManager
  
  static getInstance(): CardStateManager {
    if (!CardStateManager.instance) {
      CardStateManager.instance = new CardStateManager()
    }
    return CardStateManager.instance
  }
  
  // 卡片创建
  addCard(formId: string, formName: string, dimension: number, data: Record<string, string>): void {
    this.cards.set(formId, {
      formId,
      formName,
      dimension,
      createdAt: Date.now(),
      lastUpdated: Date.now(),
      data
    })
    hilog.info(0x0000, 'CardState', `卡片创建: ${formId}, 类型: ${formName}, 尺寸: ${dimension}`)
  }
  
  // 卡片更新
  updateCard(formId: string, data: Record<string, string>): void {
    const card = this.cards.get(formId)
    if (card) {
      card.data = { ...card.data, ...data }
      card.lastUpdated = Date.now()
    }
  }
  
  // 卡片删除
  removeCard(formId: string): CardState | undefined {
    const card = this.cards.get(formId)
    this.cards.delete(formId)
    hilog.info(0x0000, 'CardState', `卡片删除: ${formId}`)
    return card
  }
  
  // 获取卡片信息
  getCard(formId: string): CardState | undefined {
    return this.cards.get(formId)
  }
  
  // 获取所有卡片ID
  getAllFormIds(): string[] {
    return Array.from(this.cards.keys())
  }
  
  // 获取指定类型的卡片
  getCardsByType(formName: string): CardState[] {
    const result: CardState[] = []
    this.cards.forEach(card => {
      if (card.formName === formName) result.push(card)
    })
    return result
  }
}

const stateManager = CardStateManager.getInstance()

export default class LifecycleFormAbility extends FormExtensionAbility {
  
  // ===== 创建阶段 =====
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
    const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string
    const dimension = want.parameters?.['ohos.extra.param.key.form_dimension'] as number
    
    hilog.info(0x0000, 'Lifecycle', `onAddForm: id=${formId}, name=${formName}, dim=${dimension}`)
    
    // 获取初始数据
    const initialData = this.getInitialData(formName)
    
    // 注册卡片状态
    stateManager.addCard(formId, formName, dimension, initialData)
    
    return formBindingData.createFormBindingData(initialData)
  }
  
  // ===== 更新阶段 =====
  onUpdateForm(formId: string): formBindingData.FormBindingData {
    hilog.info(0x0000, 'Lifecycle', `onUpdateForm: ${formId}`)
    
    const card = stateManager.getCard(formId)
    if (!card) {
      // 卡片状态丢失,返回空数据
      hilog.warn(0x0000, 'Lifecycle', `卡片状态不存在: ${formId}`)
      return formBindingData.createFormBindingData({ error: '状态丢失' })
    }
    
    // 获取最新数据
    const newData = this.getLatestData(card.formName)
    
    // 更新状态
    stateManager.updateCard(formId, newData)
    
    return formBindingData.createFormBindingData(newData)
  }
  
  // ===== 删除阶段 =====
  onRemoveForm(formId: string): void {
    hilog.info(0x0000, 'Lifecycle', `onRemoveForm: ${formId}`)
    
    const card = stateManager.removeCard(formId)
    if (card) {
      // 清理该卡片相关的资源
      this.cleanupCardResources(card)
    }
  }
  
  // ===== 事件处理 =====
  onFormEvent(formId: string, message: string): void {
    hilog.info(0x0000, 'Lifecycle', `onFormEvent: ${formId}, msg=${message}`)
    
    const card = stateManager.getCard(formId)
    if (!card) return
    
    // 处理卡片发来的消息
    const msg = JSON.parse(message)
    this.handleCardMessage(formId, card.formName, msg)
  }
  
  // ===== 可见性变化(6.0新增) =====
  onVisibilityChange(formIds: string[], isVisible: boolean): void {
    hilog.info(0x0000, 'Lifecycle', `onVisibilityChange: ${formIds}, visible=${isVisible}`)
    
    formIds.forEach(formId => {
      if (isVisible) {
        this.resumeCardUpdates(formId)
      } else {
        this.pauseCardUpdates(formId)
      }
    })
  }
  
  // ===== 辅助方法 =====
  private getInitialData(formName: string): Record<string, string> {
    switch (formName) {
      case 'weatherCard':
        return { city: '北京', temperature: '--°', weather: '加载中' }
      case 'todoCard':
        return { count: '0', latest: '暂无待办' }
      default:
        return { title: '加载中' }
    }
  }
  
  private getLatestData(formName: string): Record<string, string> {
    // 从数据源获取最新数据
    switch (formName) {
      case 'weatherCard':
        return this.fetchWeatherData()
      case 'todoCard':
        return this.fetchTodoData()
      default:
        return { title: '已更新' }
    }
  }
  
  private fetchWeatherData(): Record<string, string> {
    return {
      city: '北京',
      temperature: '26°',
      weather: '晴',
      updateTime: new Date().toLocaleTimeString()
    }
  }
  
  private fetchTodoData(): Record<string, string> {
    return {
      count: '3',
      latest: '完成周报',
      updateTime: new Date().toLocaleTimeString()
    }
  }
  
  private cleanupCardResources(card: CardState): void {
    // 取消该卡片相关的定时器
    // 清理该卡片相关的缓存
    // 通知数据源停止为该卡片提供数据
    hilog.info(0x0000, 'Lifecycle', `清理卡片资源: ${card.formId}`)
  }
  
  private handleCardMessage(formId: string, formName: string, msg: Record<string, string>): void {
    // 根据消息类型处理
    hilog.info(0x0000, 'Lifecycle', `处理卡片消息: ${JSON.stringify(msg)}`)
  }
  
  private resumeCardUpdates(formId: string): void {
    // 卡片变为可见,恢复更新
    hilog.info(0x0000, 'Lifecycle', `恢复卡片更新: ${formId}`)
  }
  
  private pauseCardUpdates(formId: string): void {
    // 卡片变为不可见,暂停更新
    hilog.info(0x0000, 'Lifecycle', `暂停卡片更新: ${formId}`)
  }
}

进阶用法:定时刷新与数据同步

定时刷新的关键是理解"系统触发"和"应用主动"两种更新方式的配合。最佳实践是:定时刷新兜底,主动更新补位

// SyncFormAbility.ets —— 带数据同步的卡片
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { distributedKVStore } from '@kit.ArkData'

// 分布式键值存储,用于UIAbility和FormAbility数据同步
let kvStore: distributedKVStore.SingleKVStore | null = null

// 卡片更新队列——解决5分钟限制
interface PendingUpdate {
  formId: string
  data: Record<string, string>
  scheduledTime: number  // 计划执行时间
}

class UpdateScheduler {
  private pendingQueue: PendingUpdate[] = []
  private timer: number | null = null
  private readonly MIN_INTERVAL = 5 * 60 * 1000  // 5分钟
  
  // 添加待更新任务
  scheduleUpdate(formId: string, data: Record<string, string>): void {
    const lastUpdate = this.getLastUpdateTime(formId)
    const nextAvailableTime = lastUpdate + this.MIN_INTERVAL
    
    if (Date.now() >= nextAvailableTime) {
      // 可以立即更新
      this.executeUpdate(formId, data)
    } else {
      // 需要延迟更新
      this.pendingQueue.push({
        formId,
        data,
        scheduledTime: nextAvailableTime
      })
      this.scheduleNextExecution()
    }
  }
  
  // 执行更新
  private executeUpdate(formId: string, data: Record<string, string>): void {
    const bindingData = formBindingData.createFormBindingData(data)
    formProvider.updateForm(formId, bindingData)
      .then(() => {
        this.recordUpdateTime(formId)
        hilog.info(0x0000, 'UpdateScheduler', `更新成功: ${formId}`)
      })
      .catch((err: Error) => {
        hilog.error(0x0000, 'UpdateScheduler', `更新失败: ${err.message}`)
      })
  }
  
  // 调度下一次执行
  private scheduleNextExecution(): void {
    if (this.timer) return
    
    // 找到最近的待执行任务
    const nextTask = this.pendingQueue
      .sort((a, b) => a.scheduledTime - b.scheduledTime)[0]
    
    if (!nextTask) return
    
    const delay = Math.max(nextTask.scheduledTime - Date.now(), 0)
    this.timer = setTimeout(() => {
      this.processQueue()
      this.timer = null
    }, delay)
  }
  
  // 处理队列
  private processQueue(): void {
    const now = Date.now()
    const ready: PendingUpdate[] = []
    const pending: PendingUpdate[] = []
    
    this.pendingQueue.forEach(task => {
      if (task.scheduledTime <= now) {
        ready.push(task)
      } else {
        pending.push(task)
      }
    })
    
    this.pendingQueue = pending
    
    // 合并同一formId的更新(只保留最新的)
    const merged = new Map<string, Record<string, string>>()
    ready.forEach(task => {
      merged.set(task.formId, { ...(merged.get(task.formId) || {}), ...task.data })
    })
    
    // 执行更新
    merged.forEach((data, formId) => {
      this.executeUpdate(formId, data)
    })
    
    // 调度下一轮
    if (this.pendingQueue.length > 0) {
      this.scheduleNextExecution()
    }
  }
  
  private updateTimes: Map<string, number> = new Map()
  
  private getLastUpdateTime(formId: string): number {
    return this.updateTimes.get(formId) || 0
  }
  
  private recordUpdateTime(formId: string): void {
    this.updateTimes.set(formId, Date.now())
  }
}

const scheduler = new UpdateScheduler()

export default class SyncFormAbility extends FormExtensionAbility {
  
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
    
    // 从KV存储读取数据
    const data = this.readFromKVStore(formId)
    
    return formBindingData.createFormBindingData(data)
  }
  
  onUpdateForm(formId: string): formBindingData.FormBindingData {
    // 定时更新:从KV存储读取最新数据
    const data = this.readFromKVStore(formId)
    return formBindingData.createFormBindingData(data)
  }
  
  onRemoveForm(formId: string): void {
    // 清理KV存储中该卡片的数据
    this.removeFromKVStore(formId)
  }
  
  // 从KV存储读取数据
  private readFromKVStore(formId: string): Record<string, string> {
    if (!kvStore) {
      return { title: '数据加载中' }
    }
    
    try {
      const value = kvStore.get(formId)
      if (value) {
        return JSON.parse(value as string)
      }
    } catch (err) {
      hilog.error(0x0000, 'SyncForm', `KV读取失败: ${err}`)
    }
    
    return { title: '暂无数据' }
  }
  
  private removeFromKVStore(formId: string): void {
    if (!kvStore) return
    
    try {
      kvStore.delete(formId)
    } catch (err) {
      hilog.error(0x0000, 'SyncForm', `KV删除失败: ${err}`)
    }
  }
}

完整示例:带错误恢复的健壮卡片

生产环境的卡片必须能处理各种异常情况——网络超时、数据为空、卡片状态丢失。下面是一个带完整错误恢复机制的FormAbility:

// RobustFormAbility.ets
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'

// 错误恢复策略
enum RecoveryStrategy {
  USE_CACHE,      // 使用缓存
  USE_DEFAULT,    // 使用默认值
  SHOW_ERROR      // 显示错误信息
}

// 卡片数据包装器——自动处理错误
class CardDataWrapper {
  private formId: string
  private formName: string
  
  constructor(formId: string, formName: string) {
    this.formId = formId
    this.formName = formName
  }
  
  // 安全获取数据
  async safeFetch(fetchFn: () => Promise<Record<string, string>>): Promise<Record<string, string>> {
    try {
      const data = await fetchFn()
      
      // 验证数据完整性
      if (!this.validateData(data)) {
        hilog.warn(0x0000, 'CardData', `数据验证失败: ${this.formId}`)
        return this.getRecoveryData(RecoveryStrategy.USE_CACHE)
      }
      
      // 缓存数据
      this.cacheData(data)
      
      return data
    } catch (err) {
      hilog.error(0x0000, 'CardData', `数据获取失败: ${this.formId}, ${err}`)
      return this.getRecoveryData(RecoveryStrategy.USE_CACHE)
    }
  }
  
  // 验证数据
  private validateData(data: Record<string, string>): boolean {
    if (!data || Object.keys(data).length === 0) return false
    
    // 检查关键字段
    switch (this.formName) {
      case 'weatherCard':
        return !!data.city && !!data.temperature
      case 'todoCard':
        return data.count !== undefined
      default:
        return true
    }
  }
  
  // 缓存数据
  private cacheData(data: Record<string, string>): void {
    try {
      // 使用Preferences缓存
      const key = `card_cache_${this.formId}`
      // 实际项目用Preferences存储
      hilog.info(0x0000, 'CardData', `缓存数据: ${key}`)
    } catch (err) {
      hilog.error(0x0000, 'CardData', `缓存失败: ${err}`)
    }
  }
  
  // 获取恢复数据
  private getRecoveryData(strategy: RecoveryStrategy): Record<string, string> {
    switch (strategy) {
      case RecoveryStrategy.USE_CACHE:
        // 尝试读取缓存
        hilog.info(0x0000, 'CardData', `使用缓存恢复: ${this.formId}`)
        return this.readCache() || this.getDefaultData()
        
      case RecoveryStrategy.USE_DEFAULT:
        return this.getDefaultData()
        
      case RecoveryStrategy.SHOW_ERROR:
        return {
          title: '数据加载失败',
          subtitle: '请检查网络后重试',
          errorFlag: 'true'
        }
    }
  }
  
  private readCache(): Record<string, string> | null {
    // 从缓存读取
    return null
  }
  
  private getDefaultData(): Record<string, string> {
    switch (this.formName) {
      case 'weatherCard':
        return { city: '--', temperature: '--°', weather: '--', icon: 'default' }
      case 'todoCard':
        return { count: '0', latest: '暂无待办' }
      default:
        return { title: '加载中' }
    }
  }
}

// 活跃卡片追踪
let activeFormIds: Set<string> = new Set()
let cardWrappers: Map<string, CardDataWrapper> = new Map()

export default class RobustFormAbility extends FormExtensionAbility {
  
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
    const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string
    
    hilog.info(0x0000, 'RobustForm', `创建卡片: ${formId} (${formName})`)
    
    // 注册卡片
    activeFormIds.add(formId)
    cardWrappers.set(formId, new CardDataWrapper(formId, formName))
    
    // 立即返回默认数据
    const defaultData = this.getDefaultData(formName)
    
    // 异步获取真实数据
    const wrapper = cardWrappers.get(formId)
    if (wrapper) {
      wrapper.safeFetch(() => this.fetchData(formName))
        .then(data => {
          // 数据获取成功,主动更新
          const bindingData = formBindingData.createFormBindingData(data)
          formProvider.updateForm(formId, bindingData)
        })
    }
    
    return formBindingData.createFormBindingData(defaultData)
  }
  
  onUpdateForm(formId: string): formBindingData.FormBindingData {
    hilog.info(0x0000, 'RobustForm', `更新卡片: ${formId}`)
    
    const wrapper = cardWrappers.get(formId)
    if (!wrapper) {
      // 卡片状态丢失,尝试恢复
      hilog.warn(0x0000, 'RobustForm', `状态丢失,使用默认数据: ${formId}`)
      return formBindingData.createFormBindingData(this.getDefaultData('unknown'))
    }
    
    // 同步返回缓存数据(onUpdateForm不能异步)
    const cachedData = this.getCachedData(formId)
    if (cachedData) {
      return formBindingData.createFormBindingData(cachedData)
    }
    
    return formBindingData.createFormBindingData(this.getDefaultData('unknown'))
  }
  
  onRemoveForm(formId: string): void {
    hilog.info(0x0000, 'RobustForm', `删除卡片: ${formId}`)
    
    activeFormIds.delete(formId)
    cardWrappers.delete(formId)
    this.clearCache(formId)
  }
  
  private async fetchData(formName: string): Promise<Record<string, string>> {
    // 模拟网络请求
    return new Promise((resolve) => {
      setTimeout(() => {
        switch (formName) {
          case 'weatherCard':
            resolve({
              city: '北京',
              temperature: '26°',
              weather: '晴',
              updateTime: new Date().toLocaleTimeString()
            })
            break
          case 'todoCard':
            resolve({
              count: '5',
              latest: '完成周报',
              updateTime: new Date().toLocaleTimeString()
            })
            break
          default:
            resolve({ title: '已更新' })
        }
      }, 500)
    })
  }
  
  private getDefaultData(formName: string): Record<string, string> {
    switch (formName) {
      case 'weatherCard':
        return { city: '--', temperature: '--°', weather: '加载中' }
      case 'todoCard':
        return { count: '--', latest: '加载中' }
      default:
        return { title: '加载中' }
    }
  }
  
  private getCachedData(formId: string): Record<string, string> | null {
    // 从缓存读取
    return null
  }
  
  private clearCache(formId: string): void {
    // 清理缓存
  }
}

踩坑与注意事项

坑1:onAddForm回调有执行时间限制

FormAbility的回调有执行时间限制(通常5秒)。如果你在onAddForm里做耗时操作(同步网络请求、大量计算),超时后系统会丢弃返回的数据,卡片显示空白。

// ❌ 错误:同步耗时操作
onAddForm(want: Want): formBindingData.FormBindingData {
  const data = this.syncFetchFromNetwork()  // 可能超时!
  return formBindingData.createFormBindingData(data)
}

// ✅ 正确:先返回默认数据,异步更新
onAddForm(want: Want): formBindingData.FormBindingData {
  const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
  
  // 立即返回默认数据
  const defaultData = { title: '加载中' }
  
  // 异步获取真实数据
  this.asyncFetch().then(data => {
    formProvider.updateForm(formId, formBindingData.createFormBindingData(data))
  })
  
  return formBindingData.createFormBindingData(defaultData)
}

坑2:onUpdateForm里不能做异步操作

onUpdateForm是同步回调,必须立即返回FormBindingData。你不能在里面用await或返回Promise。

// ❌ 错误:异步返回
async onUpdateForm(formId: string): Promise<formBindingData.FormBindingData> {
  const data = await this.fetchData()  // 编译可能通过,但运行时行为不确定
  return formBindingData.createFormBindingData(data)
}

// ✅ 正确:同步返回缓存数据
onUpdateForm(formId: string): formBindingData.FormBindingData {
  const cached = this.readFromCache(formId)
  return formBindingData.createFormBindingData(cached)
}

坑3:FormAbility可能在onCreate后立即被销毁

FormAbility的生命周期很短——系统调用完回调后,可能立即销毁FormAbility实例。这意味着你不能依赖FormAbility的成员变量来保存状态。

// ❌ 错误:依赖成员变量
export default class BadFormAbility extends FormExtensionAbility {
  private cardCount: number = 0  // 可能丢失!
  
  onAddForm(want: Want): formBindingData.FormBindingData {
    this.cardCount++  // 不可靠
    return formBindingData.createFormBindingData({ count: this.cardCount.toString() })
  }
}

// ✅ 正确:使用外部存储
let globalCardCount: number = 0  // 模块级变量,相对可靠

export default class GoodFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    globalCardCount++
    return formBindingData.createFormBindingData({ count: globalCardCount.toString() })
  }
}

更可靠的方式是使用Preferences或KV存储来持久化状态。

坑4:卡片ID可能重复使用

系统可能会复用已删除卡片的formId。如果你在onRemoveForm里清理了缓存,但之后系统又用同一个formId创建了新卡片,你的缓存就没了。

解决方案:onRemoveForm里不要立即删除缓存,而是标记为"已删除"。下次onAddForm时检查是否有同formId的缓存,有则复用。

坑5:定时刷新在设备休眠时不触发

设备休眠时,定时刷新不会触发。设备唤醒后,系统会补发一次更新。但如果你依赖"每N小时更新一次"来计算数据,可能会出现时间跳跃。

onUpdateForm(formId: string): formBindingData.FormBindingData {
  // ❌ 错误:假设更新间隔固定
  const hoursSinceLastUpdate = 1  // 不一定!设备休眠期间不会更新
  
  // ✅ 正确:使用实际时间计算
  const now = Date.now()
  const lastUpdate = this.getLastUpdateTime(formId)
  const actualHours = (now - lastUpdate) / (1000 * 60 * 60)
  
  return formBindingData.createFormBindingData(this.calculateData(actualHours))
}

HarmonyOS 6适配说明

  1. onVisibilityChange回调:6.0新增了onVisibilityChange回调,当卡片从桌面滑出/滑入可视区域时触发。你可以利用这个回调暂停不可见卡片的更新,节省电量和CPU。

  2. onAcquireFormState回调:6.0新增了onAcquireFormState回调,系统在添加卡片前会先调用这个回调查询卡片状态。你可以在这里返回卡片是否可用、是否需要网络等信息,系统据此决定是否允许添加。

  3. 生命周期回调超时时间调整:6.0将onAddFormonUpdateForm的超时时间从5秒延长到了10秒,给了开发者更多操作空间。但仍然建议尽快返回,不要做耗时操作。

  4. 卡片状态持久化增强:6.0新增了formProvider.saveFormState()API,允许你在FormAbility被销毁前保存卡片状态。下次FormAbility被创建时,可以通过formProvider.restoreFormState()恢复。

// 6.0新增的状态保存/恢复
onAddForm(want: Want): formBindingData.FormBindingData {
  // 尝试恢复之前的状态
  const savedState = formProvider.restoreFormState(this.context, formId)
  if (savedState) {
    return formBindingData.createFormBindingData(savedState)
  }
  
  return formBindingData.createFormBindingData(this.getDefaultData())
}

onRemoveForm(formId: string): void {
  // 保存卡片状态(以防formId被复用)
  formProvider.saveFormState(this.context, formId, this.getCurrentData(formId))
}

总结

卡片生命周期是系统驱动的,你只能被动响应。但"被动"不等于"被动挨打"——理解每个回调的时序、约束、最佳实践,你就能写出健壮的卡片。

核心要点回顾:

  • onAddForm只执行一次,必须立即返回数据,不能做耗时操作
  • onUpdateForm是同步回调,必须同步返回缓存数据
  • onRemoveForm是最终回调,必须清理所有资源
  • 定时刷新不是精确的,系统会根据条件决定是否触发
  • FormAbility可能随时被销毁,状态必须外部存储
  • 5分钟更新限制是硬性的,用更新队列管理
评估维度 说明
学习难度 ⭐⭐⭐⭐ 回调时序、状态管理、错误恢复都需要深入理解
使用频率 ⭐⭐⭐⭐⭐ 每个卡片都涉及生命周期管理
重要程度 ⭐⭐⭐⭐⭐ 生命周期管理不好,卡片就是定时炸弹

下一篇聊卡片交互——点击跳转、消息事件、深链接,让卡片从"能看"变成"能用"。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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