HarmonyOS开发:卡片数据更新——FormProvider与数据驱动

举报
Jack20 发表于 2026/06/27 22:29:18 2026/06/27
【摘要】 HarmonyOS开发:卡片数据更新——FormProvider与数据驱动📌 核心要点:FormProvider是卡片数据更新的核心引擎,掌握定时更新、按需更新、缓存策略三大机制,才能让卡片数据既及时又不费电。 背景与动机卡片做出来了,数据是死的。用户看一眼,哦,26度,再看一眼,还是26度——哪怕外面已经下起了暴雨。数据不更新,卡片就是个摆设。但更新数据这件事,远没有你想的那么简单。更...

HarmonyOS开发:卡片数据更新——FormProvider与数据驱动

📌 核心要点:FormProvider是卡片数据更新的核心引擎,掌握定时更新、按需更新、缓存策略三大机制,才能让卡片数据既及时又不费电。

背景与动机

卡片做出来了,数据是死的。用户看一眼,哦,26度,再看一眼,还是26度——哪怕外面已经下起了暴雨。

数据不更新,卡片就是个摆设。

但更新数据这件事,远没有你想的那么简单。更新太快,电量哗哗掉;更新太慢,数据过时用户骂你;更新逻辑写错了,卡片白屏闪退。怎么在"及时性"和"省电"之间找平衡?这就是FormProvider要解决的问题。

FormProvider是鸿蒙提供的卡片数据更新API,它提供了两种更新模式:

  1. 定时更新:系统按配置的时间间隔自动触发更新
  2. 按需更新:应用主动调用formProvider.updateForm()推送更新

两种模式各有适用场景,搞清楚它们的机制和限制,才能写出"恰到好处"的卡片数据更新逻辑。

核心原理

卡片数据更新的完整链路

flowchart TB
    subgraph 更新触发源
        A[定时触发<br/>scheduledUpdateTime] --> D[FormManagerService]
        B[应用主动触发<br/>formProvider.updateForm] --> D
        C[系统事件触发<br/>网络恢复/屏幕亮起] --> D
    end
    
    subgraph 更新执行
        D --> E{验证卡片有效性}
        E -->|有效| F[调用FormAbility.onUpdateForm]
        E -->|无效/已删除| G[忽略更新]
        F --> H[获取新FormBindingData]
        H --> I[系统渲染引擎合并数据]
        I --> J[卡片UI刷新]
    end
    
    subgraph 缓存层
        K[本地数据缓存] --> F
        L[网络数据源] --> K
    end
    
    classDef trigger fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    classDef service fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef render fill:#E3F2FD,stroke:#1565C0,color:#0D47A1
    classDef cache fill:#FCE4EC,stroke:#C62828,color:#B71C1C
    
    class A,B,C trigger
    class D,E,F,G service
    class H,I,J render
    class K,L cache

关键点:不管哪种触发源,最终都要经过FormManagerService验证卡片有效性。如果用户已经删了卡片,更新请求会被直接忽略。所以你不用担心"更新一个不存在的卡片"会出问题。

定时更新机制

定时更新是静态卡片最常用的更新方式。配置在form_config.json里:

{
  "updateEnabled": true,
  "scheduledUpdateTime": "07:00",
  "updateDuration": 1
}

这三个字段怎么配合?

  • updateEnabled: true:启用定时更新
  • scheduledUpdateTime: "07:00":最早触发更新的时间点
  • updateDuration: 1:从scheduledUpdateTime开始,每小时更新一次

但这里有个大坑——scheduledUpdateTime不是精确时间。系统会根据当前负载、电量等因素决定实际触发时间。你配了07:00,可能07:15才触发,甚至更晚。

updateDuration的含义也容易误解:

updateDuration值 含义
0 只在scheduledUpdateTime更新一次
1 从scheduledUpdateTime开始,每小时更新一次
2 从scheduledUpdateTime开始,每2小时更新一次
以此类推
16 每16小时更新一次(最大值)

所以如果你配了scheduledUpdateTime: "07:00", updateDuration: 1,实际更新时间可能是07:00、08:00、09:00……直到第二天07:00重新开始循环。

按需更新机制

定时更新不够灵活?用按需更新。应用在任何时候都可以调用formProvider.updateForm()主动推送数据。

import { formProvider, formBindingData } from '@kit.AbilityKit'

// 主动更新指定卡片
function pushCardUpdate(formId: string, data: Record<string, string>): void {
  const bindingData = formBindingData.createFormBindingData(data)
  
  formProvider.updateForm(formId, bindingData)
    .then(() => {
      console.info('卡片更新成功')
    })
    .catch((err: Error) => {
      console.error(`卡片更新失败: ${err.message}`)
    })
}

按需更新的优势是实时性——数据变了就推,不等定时器。但代价是你要自己管理更新逻辑,而且更新频率受系统限流。

更新频率限制

系统对卡片更新频率有严格限制,防止应用疯狂更新导致电量崩盘:

更新方式 最低间隔 说明
定时更新 1小时 updateDuration最小值为1
按需更新 5分钟 两次updateForm调用间隔不低于5分钟
动态卡片实时更新 无硬性限制 但受电量管控,过于频繁会被系统降频

5分钟这个限制是很多新手的噩梦。你做了个消息卡片,收到新消息想立刻更新?对不起,5分钟内只能更新一次。

怎么破?两种思路:

  1. 合并更新:5分钟内收到多条消息,攒到一起更新
  2. 用动态卡片:动态卡片没有5分钟限制,适合实时场景

代码实战

基础用法:定时更新 + 数据缓存

最简单的场景:天气卡片,每小时更新一次,数据从网络获取并缓存。

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

// 天气数据缓存
let weatherCache: Map<string, CachedWeather> = new Map()

interface CachedWeather {
  data: WeatherInfo
  timestamp: number  // 缓存时间
}

interface WeatherInfo {
  city: string
  temperature: string
  weather: string
  icon: string
  humidity: string
  aqi: string
}

export default class WeatherFormAbility extends FormExtensionAbility {
  
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
    const city = want.parameters?.['city'] as string || '北京'
    
    // 先返回缓存数据,再异步更新
    const cached = weatherCache.get(city)
    if (cached && this.isCacheValid(cached.timestamp)) {
      return formBindingData.createFormBindingData(cached.data)
    }
    
    // 无缓存,返回默认数据
    const defaultData: WeatherInfo = {
      city: city,
      temperature: '--°',
      weather: '加载中',
      icon: 'loading',
      humidity: '--',
      aqi: '--'
    }
    
    // 异步获取最新数据并更新
    this.fetchWeatherAsync(formId, city)
    
    return formBindingData.createFormBindingData(defaultData)
  }
  
  onUpdateForm(formId: string): formBindingData.FormBindingData {
    // 定时更新时触发
    const city = this.getCityByFormId(formId)
    const cached = weatherCache.get(city)
    
    if (cached && this.isCacheValid(cached.timestamp)) {
      return formBindingData.createFormBindingData(cached.data)
    }
    
    // 缓存过期,异步获取
    this.fetchWeatherAsync(formId, city)
    
    // 返回旧缓存(总比没有强)
    if (cached) {
      return formBindingData.createFormBindingData(cached.data)
    }
    
    return formBindingData.createFormBindingData({
      city: city,
      temperature: '--°',
      weather: '更新中',
      icon: 'loading',
      humidity: '--',
      aqi: '--'
    })
  }
  
  onRemoveForm(formId: string): void {
    hilog.info(0x0000, 'WeatherForm', `卡片删除: ${formId}`)
  }
  
  // 异步获取天气数据
  private async fetchWeatherAsync(formId: string, city: string): Promise<void> {
    try {
      const weather = await this.fetchWeatherFromNetwork(city)
      
      // 更新缓存
      weatherCache.set(city, {
        data: weather,
        timestamp: Date.now()
      })
      
      // 主动推送更新
      const bindingData = formBindingData.createFormBindingData(weather)
      formProvider.updateForm(formId, bindingData)
      
      hilog.info(0x0000, 'WeatherForm', `天气数据更新成功: ${city}`)
    } catch (err) {
      hilog.error(0x0000, 'WeatherForm', `天气数据获取失败: ${err}`)
    }
  }
  
  // 网络请求(简化示例)
  private async fetchWeatherFromNetwork(city: string): Promise<WeatherInfo> {
    const request = http.createHttp()
    const response = await request.request(
      `https://api.weather.com/v1?city=${city}`,
      { method: http.RequestMethod.GET }
    )
    
    if (response.responseCode === 200) {
      const result = JSON.parse(response.result as string)
      return {
        city: city,
        temperature: `${result.temp}°`,
        weather: result.weather,
        icon: result.icon,
        humidity: `${result.humidity}%`,
        aqi: result.aqi.toString()
      }
    }
    
    throw new Error(`HTTP ${response.responseCode}`)
  }
  
  // 缓存是否有效(30分钟内有效)
  private isCacheValid(timestamp: number): boolean {
    return Date.now() - timestamp < 30 * 60 * 1000
  }
  
  // 根据formId获取城市(简化)
  private getCityByFormId(formId: string): string {
    return '北京'
  }
}

关键策略:先返回缓存,再异步更新。用户添加卡片时不会看到空白,等网络请求完成后数据自动刷新。这个模式在所有网络数据场景都适用。

进阶用法:事件驱动的按需更新

有些场景不适合定时更新——比如快递卡片,包裹状态可能几小时不变,也可能1分钟内连续变3次。这时候需要事件驱动:收到推送通知后立即更新卡片。

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

// 活跃卡片管理
let activeCards: Map<string, CardInfo> = new Map()

interface CardInfo {
  formId: string
  trackingNo: string
  lastUpdateTime: number
}

export default class ExpressFormAbility extends FormExtensionAbility {
  
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string
    const trackingNo = want.parameters?.['trackingNo'] as string || 'SF1234567890'
    
    // 注册卡片
    activeCards.set(formId, {
      formId: formId,
      trackingNo: trackingNo,
      lastUpdateTime: Date.now()
    })
    
    // 返回快递状态
    return this.getExpressData(trackingNo)
  }
  
  onUpdateForm(formId: string): formBindingData.FormBindingData {
    const info = activeCards.get(formId)
    if (info) {
      return this.getExpressData(info.trackingNo)
    }
    return formBindingData.createFormBindingData({ status: '未知' })
  }
  
  onRemoveForm(formId: string): void {
    activeCards.delete(formId)
    hilog.info(0x0000, 'ExpressForm', `卡片删除: ${formId}`)
  }
  
  // 获取快递数据
  private getExpressData(trackingNo: string): formBindingData.FormBindingData {
    const expressInfo = this.queryExpressFromCache(trackingNo)
    
    return formBindingData.createFormBindingData({
      trackingNo: trackingNo,
      status: expressInfo.status,
      location: expressInfo.location,
      updateTime: expressInfo.updateTime,
      estimatedArrival: expressInfo.estimatedArrival
    })
  }
  
  private queryExpressFromCache(trackingNo: string): ExpressInfo {
    // 从本地缓存读取
    return {
      status: '运输中',
      location: '北京转运中心',
      updateTime: '14:30',
      estimatedArrival: '明天 10:00'
    }
  }
}

// ===== 在UIAbility中监听推送并更新卡片 =====
// PushHandler.ets
import { formProvider, formBindingData } from '@kit.AbilityKit'

export class PushHandler {
  
  // 收到快递状态变更推送时调用
  static onExpressStatusChanged(trackingNo: string, newStatus: ExpressStatus): void {
    // 找到所有使用该快递单号的卡片
    activeCards.forEach((info, formId) => {
      if (info.trackingNo === trackingNo) {
        // 检查更新间隔(不低于5分钟)
        if (Date.now() - info.lastUpdateTime < 5 * 60 * 1000) {
          hilog.warn(0x0000, 'ExpressPush', `更新间隔不足5分钟,跳过: ${formId}`)
          return
        }
        
        const data = {
          trackingNo: trackingNo,
          status: newStatus.status,
          location: newStatus.location,
          updateTime: newStatus.updateTime,
          estimatedArrival: newStatus.estimatedArrival
        }
        
        const bindingData = formBindingData.createFormBindingData(data)
        formProvider.updateForm(formId, bindingData)
          .then(() => {
            info.lastUpdateTime = Date.now()
            hilog.info(0x0000, 'ExpressPush', `卡片更新成功: ${formId}`)
          })
          .catch((err: Error) => {
            hilog.error(0x0000, 'ExpressPush', `更新失败: ${err.message}`)
          })
      }
    })
  }
}

事件驱动更新的核心是:数据源推数据,而不是卡片拉数据。收到推送后立刻更新卡片,没推送就不更新,既及时又省电。

完整示例:多卡片批量更新管理

实际项目中,用户可能添加了多张卡片。当数据变化时,你需要批量更新所有相关卡片。但5分钟间隔限制是针对单张卡片的,不是针对所有卡片的。所以每张卡片要独立追踪更新时间。

// CardUpdateManager.ets —— 卡片更新管理器
import { formProvider, formBindingData } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'

interface ManagedCard {
  formId: string
  formName: string
  lastUpdateTime: number
  data: Record<string, string>
}

class CardUpdateManager {
  private cards: Map<string, ManagedCard> = new Map()
  private static instance: CardUpdateManager
  
  // 最小更新间隔(毫秒)
  private readonly MIN_UPDATE_INTERVAL = 5 * 60 * 1000  // 5分钟
  
  static getInstance(): CardUpdateManager {
    if (!CardUpdateManager.instance) {
      CardUpdateManager.instance = new CardUpdateManager()
    }
    return CardUpdateManager.instance
  }
  
  // 注册卡片
  registerCard(formId: string, formName: string, initialData: Record<string, string>): void {
    this.cards.set(formId, {
      formId,
      formName,
      lastUpdateTime: 0,  // 从未更新过
      data: initialData
    })
    hilog.info(0x0000, 'CardManager', `注册卡片: ${formId} (${formName})`)
  }
  
  // 注销卡片
  unregisterCard(formId: string): void {
    this.cards.delete(formId)
    hilog.info(0x0000, 'CardManager', `注销卡片: ${formId}`)
  }
  
  // 批量更新指定类型的卡片
  updateCardsByType(formName: string, newData: Record<string, string>): void {
    const now = Date.now()
    
    this.cards.forEach((card, formId) => {
      if (card.formName !== formName) return
      
      // 检查更新间隔
      if (now - card.lastUpdateTime < this.MIN_UPDATE_INTERVAL) {
        hilog.warn(0x0000, 'CardManager', 
          `卡片 ${formId} 更新间隔不足,剩余 ${Math.ceil((this.MIN_UPDATE_INTERVAL - (now - card.lastUpdateTime)) / 1000)}`)
        return
      }
      
      // 合并数据
      const mergedData = { ...card.data, ...newData }
      const bindingData = formBindingData.createFormBindingData(mergedData)
      
      formProvider.updateForm(formId, bindingData)
        .then(() => {
          card.lastUpdateTime = now
          card.data = mergedData
          hilog.info(0x0000, 'CardManager', `卡片更新成功: ${formId}`)
        })
        .catch((err: Error) => {
          hilog.error(0x0000, 'CardManager', `卡片更新失败: ${formId}, ${err.message}`)
          // 更新失败可能是卡片已删除
          if (err.message.includes('not exist')) {
            this.cards.delete(formId)
          }
        })
    })
  }
  
  // 更新单张卡片(忽略间隔限制,用于定时更新场景)
  forceUpdateCard(formId: string, newData: Record<string, string>): void {
    const card = this.cards.get(formId)
    if (!card) {
      hilog.warn(0x0000, 'CardManager', `卡片不存在: ${formId}`)
      return
    }
    
    const mergedData = { ...card.data, ...newData }
    const bindingData = formBindingData.createFormBindingData(mergedData)
    
    formProvider.updateForm(formId, bindingData)
      .then(() => {
        card.lastUpdateTime = Date.now()
        card.data = mergedData
      })
      .catch((err: Error) => {
        hilog.error(0x0000, 'CardManager', `强制更新失败: ${err.message}`)
      })
  }
  
  // 获取所有活跃卡片ID
  getActiveFormIds(): string[] {
    return Array.from(this.cards.keys())
  }
  
  // 获取指定类型的卡片数量
  getCardCountByType(formName: string): number {
    let count = 0
    this.cards.forEach(card => {
      if (card.formName === formName) count++
    })
    return count
  }
}

export default CardUpdateManager

使用方式:

// 在FormAbility中注册
const manager = CardUpdateManager.getInstance()

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 initialData = { title: '加载中', content: '' }
  manager.registerCard(formId, formName, initialData)
  
  return formBindingData.createFormBindingData(initialData)
}

// 在数据变化时批量更新
function onWeatherChanged(city: string, newTemp: string): void {
  manager.updateCardsByType('weatherCard', {
    temperature: newTemp,
    updateTime: new Date().toLocaleTimeString()
  })
}

踩坑与注意事项

坑1:updateForm的5分钟限制是硬性的

别想着绕过5分钟限制。有些开发者尝试用多个formId轮着更新来变相提高频率——系统检测到这种行为会直接限流,所有更新请求都会被拒绝。

// ❌ 错误:试图绕过限制
const formIds = ['id1', 'id2', 'id3', 'id4', 'id5']
// 每分钟更新一个formId,5分钟轮一圈
setInterval(() => {
  const id = formIds[currentIndex % formIds.length]
  formProvider.updateForm(id, data)  // 系统会检测到异常模式
  currentIndex++
}, 60000)

// ✅ 正确:遵守5分钟限制,合并更新
setInterval(() => {
  // 5分钟更新一次,但包含所有最新数据
  formProvider.updateForm(formId, allLatestData)
}, 5 * 60 * 1000)

坑2:FormBindingData数据必须扁平化

FormBindingData只支持一层键值对,不支持嵌套对象。如果你的数据结构是嵌套的,必须先扁平化。

// ❌ 嵌套数据,卡片读不到
const badData = {
  weather: {
    temperature: '26°',
    city: '北京'
  }
}

// ✅ 扁平化数据
const goodData = {
  'weather.temperature': '26°',
  'weather.city': '北京'
}
// 或者更简单
const simpleData = {
  temperature: '26°',
  city: '北京'
}

坑3:网络请求不能在FormAbility里长时间阻塞

FormAbility的回调有执行时间限制。如果你在onAddForm里同步等待网络请求返回,超时后系统会直接杀掉回调,卡片显示空白。

// ❌ 错误:同步等待网络
async onAddForm(want: Want): Promise<formBindingData.FormBindingData> {
  const data = await this.fetchFromNetwork()  // 可能超时!
  return formBindingData.createFormBindingData(data)
}

// ✅ 正确:先返回缓存/默认数据,异步更新
onAddForm(want: Want): formBindingData.FormBindingData {
  // 立即返回缓存数据
  const cached = this.getCache()
  
  // 异步获取最新数据
  this.fetchAsync().then(data => {
    formProvider.updateForm(formId, formBindingData.createFormBindingData(data))
  })
  
  return formBindingData.createFormBindingData(cached)
}

坑4:formProvider.notifyVisibleForms优化

如果你有很多卡片,但用户当前只能看到其中几张,更新不可见的卡片纯属浪费。formProvider.notifyVisibleForms可以获取当前可见的卡片列表。

import { formProvider } from '@kit.AbilityKit'

// 获取当前可见的卡片列表
async function getVisibleCards(): Promise<string[]> {
  try {
    const visibleForms = await formProvider.getFormsCount()
    // 进一步获取具体formId...
    return visibleForms as string[]
  } catch (err) {
    return []
  }
}

// 只更新可见的卡片
async function updateVisibleCards(data: Record<string, string>): Promise<void> {
  const visibleIds = await getVisibleCards()
  const bindingData = formBindingData.createFormBindingData(data)
  
  visibleIds.forEach(formId => {
    formProvider.updateForm(formId, bindingData)
  })
}

坑5:数据一致性

当你有多张卡片共享同一份数据源时,更新顺序可能导致短暂的数据不一致。比如先更新了卡片A的温度数据,还没来得及更新卡片B,用户看到了两张卡片显示不同温度。

解决方案:同一份数据的更新放在同一个微任务里

// ❌ 逐个更新,可能不一致
cards.forEach(formId => {
  formProvider.updateForm(formId, bindingData)  // 异步操作,顺序不确定
})

// ✅ 批量更新,数据一致
async function batchUpdate(formIds: string[], data: Record<string, string>): Promise<void> {
  const bindingData = formBindingData.createFormBindingData(data)
  const promises = formIds.map(formId => 
    formProvider.updateForm(formId, bindingData)
  )
  await Promise.all(promises)
}

HarmonyOS 6适配说明

  1. 智能更新调度:6.0引入了基于用户行为的智能更新调度。系统会追踪用户查看卡片的频率,自动调整更新间隔——常看的卡片更新更频繁,不常看的更新更少。你不需要改代码,但需要理解这个行为。

  2. formProvider.updateForm新增回调:6.0的updateForm方法新增了更新结果回调,你可以知道更新是否成功、失败原因是什么:

// 6.0新增的更新回调
formProvider.updateForm(formId, bindingData, (err, data) => {
  if (err) {
    hilog.error(0x0000, 'CardUpdate', `更新失败: ${err.code} - ${err.message}`)
    // 根据错误码处理
    if (err.code === 16500050) {
      // 卡片不存在,清理本地记录
      activeCards.delete(formId)
    }
  } else {
    hilog.info(0x0000, 'CardUpdate', '更新成功')
  }
})
  1. 数据压缩传输:6.0对FormBindingData的传输做了压缩优化,实际可传输的数据量从1KB提升到了约2KB(压缩后)。但你的数据结构仍然要精简,别因为"能传2KB"就塞2KB。

  2. 离线数据缓存:6.0新增了卡片数据的离线缓存机制。当网络不可用时,系统会使用最后一次成功更新的数据,而不是显示"加载失败"。你需要在FormBindingData中设置isOfflineData标记:

const bindingData = formBindingData.createFormBindingData({
  temperature: '26°',
  isOfflineData: true  // 6.0新增,标记这是离线缓存数据
})

总结

卡片数据更新是元服务开发的核心难点。定时更新省心但不及时,按需更新及时但要控制频率。两者结合,配合缓存策略,才能做到"数据及时、电量友好"。

核心要点回顾:

  • 定时更新由系统调度,scheduledUpdateTime不是精确时间
  • 按需更新5分钟间隔限制是硬性的,不能绕过
  • 先返回缓存数据,再异步更新,避免卡片空白
  • 多卡片批量更新要追踪每张卡片的更新时间
  • 数据必须扁平化,嵌套结构卡片读不到
评估维度 说明
学习难度 ⭐⭐⭐⭐ 更新机制、缓存策略、频率限制都需要深入理解
使用频率 ⭐⭐⭐⭐⭐ 每个卡片都涉及数据更新
重要程度 ⭐⭐⭐⭐⭐ 数据不更新,卡片就是死的

下一篇聊卡片生命周期——从创建到删除,每个阶段该做什么、不该做什么。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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