HarmonyOS APP开发中的应用切换:最近任务列表与前后台切换状态管理

举报
Jack20 发表于 2026/06/20 18:25:55 2026/06/20
【摘要】 HarmonyOS APP开发中的应用切换:最近任务列表与前后台切换状态管理📌 核心要点:掌握 HarmonyOS 应用前后台切换的生命周期管理,理解最近任务列表的交互机制,实现多实例管理与切换状态保存的完整方案。 一、背景与动机你正在用音乐 App 听歌,突然收到一条微信消息,切过去回复,再切回来——歌还在播,进度还在,一切如初。这种"无缝切换"的体验,用户觉得理所当然,但背后是开发者...

HarmonyOS APP开发中的应用切换:最近任务列表与前后台切换状态管理

📌 核心要点:掌握 HarmonyOS 应用前后台切换的生命周期管理,理解最近任务列表的交互机制,实现多实例管理与切换状态保存的完整方案。


一、背景与动机

你正在用音乐 App 听歌,突然收到一条微信消息,切过去回复,再切回来——歌还在播,进度还在,一切如初。这种"无缝切换"的体验,用户觉得理所当然,但背后是开发者精心设计的状态保存与恢复机制。

应用切换看似简单——不就是前台变后台、后台变前台嘛?但魔鬼藏在细节里。你的应用在后台时,系统随时可能回收内存;用户从最近任务列表划掉你的应用,你的进程就没了;多个 Ability 实例同时存在,状态怎么隔离?这些问题如果处理不好,轻则数据丢失,重则应用崩溃。

更棘手的是,HarmonyOS 的多设备协同场景下,应用可能从手机切到平板、从平板切到手表,切换不再只是"前后台"那么简单,而是跨设备的流转。理解基础的切换机制,是迈向分布式能力的必经之路。


二、核心原理

2.1 应用前后台切换完整流程

sequenceDiagram
    participant User as 用户
    participant System as 系统
    participant App as 应用(UIAbility)
    participant Memory as 内存管理
    
    User->>System: 按Home键/切到其他应用
    System->>App: onBackground()
    App->>App: 保存UI状态
    App->>App: 释放非必要资源
    App-->>System: 切换完成
    
    Note over Memory: 应用进入后台<br/>可能被系统回收
    
    alt 系统内存不足
        Memory->>System: 回收后台应用
        System->>App: onDestroy()
        Note over App: 进程被杀
    end
    
    User->>System: 从最近任务返回
    alt 进程未被回收(热启动)
        System->>App: onForeground()
        App->>App: 恢复UI状态
        App->>App: 检查数据新鲜度
    else 进程已被回收(冷启动)
        System->>App: onCreate()
        App->>App: 重新初始化
        App->>App: 从持久化存储恢复
    end
    
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff

2.2 最近任务列表机制

flowchart TB
    A[用户上滑停顿] --> B[系统展示最近任务列表]
    B --> C{用户操作}
    C --> D[点击任务卡片]
    C --> E[上滑划掉任务]
    C --> F[点击清除全部]
    
    D --> G[热启动/温启动]
    G --> H[onForeground/onCreate]
    
    E --> I[系统终止应用进程]
    I --> J[onDestroy回调]
    
    F --> K[批量终止所有后台应用]
    
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
    
    class D,G,H primary
    class E,I,J error
    class F,K warning
    class A,B info

2.3 Ability 生命周期与切换的关系

生命周期回调 触发场景 应做的事 不应做的事
onCreate 冷启动/温启动 核心初始化 耗时IO操作
onWindowStageCreate 窗口创建 加载UI内容 阻塞渲染
onForeground 热启动/从后台回前台 恢复状态、刷新数据 重复初始化
onBackground 进入后台 保存状态、释放资源 启动新任务
onDestroy 进程终止 清理资源、持久化 异步操作(可能来不及)

2.4 launchType 与多实例

launchType 行为 典型场景
singleton 全局唯一实例,新请求走 onNewWant 主界面、首页
multiton 每次请求创建新实例 文档编辑、聊天窗口
specified 开发者决定是否复用 聊天会话(同一会话复用)

三、代码实战

3.1 完整的切换状态保存与恢复

import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { preferences } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[SwitchManager]';
const PREF_NAME = 'app_state_cache';

/**
 * 应用状态数据模型
 */
interface AppState {
  // 列表滚动位置
  scrollOffset: number;
  // 当前选中的Tab索引
  currentTabIndex: number;
  // 搜索关键词
  searchKeyword: string;
  // 用户输入的草稿
  inputDraft: string;
  // 进入后台的时间戳
  backgroundTimestamp: number;
}

export default class SwitchAwareAbility extends UIAbility {
  // 内存中的状态缓存
  private appState: AppState = {
    scrollOffset: 0,
    currentTabIndex: 0,
    searchKeyword: '',
    inputDraft: '',
    backgroundTimestamp: 0
  };

  // 数据新鲜度阈值(5分钟)
  private readonly DATA_FRESHNESS_THRESHOLD = 5 * 60 * 1000;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0001, TAG, 'onCreate');

    // 冷启动时,从持久化存储恢复状态
    if (launchParam.launchReason === AbilityConstant.LaunchReason.STARTUP_NORMAL ||
        launchParam.launchReason === AbilityConstant.LaunchReason.STARTUP_RECENT_TASK) {
      this.restoreStateFromPersistence();
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0001, TAG, 'onWindowStageCreate');
    windowStage.loadContent('pages/Index');
  }

  /**
   * onForeground - 从后台回到前台
   * 这是应用切换最关键的回调
   */
  onForeground(): void {
    hilog.info(0x0001, TAG, 'onForeground');

    // 计算后台时长
    const backgroundDuration = Date.now() - this.appState.backgroundTimestamp;
    hilog.info(0x0001, TAG, `后台时长: ${backgroundDuration}ms`);

    // 根据后台时长决定恢复策略
    if (backgroundDuration > this.DATA_FRESHNESS_THRESHOLD) {
      // 后台超过5分钟,数据可能过期,需要刷新
      hilog.info(0x0001, TAG, '数据可能过期,执行刷新');
      this.refreshStaleData();
    } else {
      // 后台时间短,直接恢复UI状态
      hilog.info(0x0001, TAG, '数据新鲜,恢复UI状态');
      this.restoreUIState();
    }
  }

  /**
   * onBackground - 进入后台
   * 保存状态、释放资源
   */
  onBackground(): void {
    hilog.info(0x0001, TAG, 'onBackground');

    // 记录进入后台的时间
    this.appState.backgroundTimestamp = Date.now();

    // 1. 保存UI状态到内存
    this.saveUIState();

    // 2. 持久化关键状态(防止进程被杀后丢失)
    this.persistState();

    // 3. 释放非必要资源
    this.releaseResources();
  }

  /**
   * 保存UI状态
   */
  private saveUIState() {
    // 通过 AppStorage 收集各页面的状态
    this.appState.scrollOffset = AppStorage.get<number>('scrollOffset') || 0;
    this.appState.currentTabIndex = AppStorage.get<number>('currentTabIndex') || 0;
    this.appState.searchKeyword = AppStorage.get<string>('searchKeyword') || '';
    this.appState.inputDraft = AppStorage.get<string>('inputDraft') || '';

    hilog.info(0x0001, TAG, `UI状态已保存: scrollOffset=${this.appState.scrollOffset}`);
  }

  /**
   * 恢复UI状态
   */
  private restoreUIState() {
    // 将状态写回 AppStorage,各页面通过 @StorageLink 自动同步
    AppStorage.setOrCreate('scrollOffset', this.appState.scrollOffset);
    AppStorage.setOrCreate('currentTabIndex', this.appState.currentTabIndex);
    AppStorage.setOrCreate('searchKeyword', this.appState.searchKeyword);
    AppStorage.setOrCreate('inputDraft', this.appState.inputDraft);

    hilog.info(0x0001, TAG, 'UI状态已恢复');
  }

  /**
   * 持久化关键状态到本地存储
   */
  private async persistState() {
    try {
      const pref = await preferences.getPreferences(this.context, PREF_NAME);
      await pref.put('scrollOffset', this.appState.scrollOffset);
      await pref.put('currentTabIndex', this.appState.currentTabIndex);
      await pref.put('searchKeyword', this.appState.searchKeyword);
      await pref.put('inputDraft', this.appState.inputDraft);
      await pref.put('backgroundTimestamp', this.appState.backgroundTimestamp);
      await pref.flush();
      hilog.info(0x0001, TAG, '状态已持久化');
    } catch (err) {
      hilog.error(0x0001, TAG, `持久化失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 从持久化存储恢复状态(冷启动时使用)
   */
  private async restoreStateFromPersistence() {
    try {
      const pref = await preferences.getPreferences(this.context, PREF_NAME);
      this.appState.scrollOffset = await pref.get('scrollOffset', 0) as number;
      this.appState.currentTabIndex = await pref.get('currentTabIndex', 0) as number;
      this.appState.searchKeyword = await pref.get('searchKeyword', '') as string;
      this.appState.inputDraft = await pref.get('inputDraft', '') as string;
      this.appState.backgroundTimestamp = await pref.get('backgroundTimestamp', 0) as number;
      hilog.info(0x0001, TAG, '从持久化存储恢复状态完成');
    } catch (err) {
      hilog.error(0x0001, TAG, `恢复状态失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 刷新过期数据
   */
  private refreshStaleData() {
    // 重新请求网络数据
    // 刷新缓存
    // 更新UI
    hilog.info(0x0001, TAG, '过期数据已刷新');
  }

  /**
   * 释放非必要资源
   */
  private releaseResources() {
    // 释放图片缓存
    // 暂停动画
    // 取消未完成的网络请求(可选)
    hilog.info(0x0001, TAG, '非必要资源已释放');
  }

  onDestroy(): void {
    hilog.info(0x0001, TAG, 'onDestroy');
    // 最终持久化,确保数据不丢失
    this.persistState();
  }
}

3.2 多实例管理(specified 模式)

import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[MultiInstance]';

/**
 * 多实例管理 Ability
 * 使用 specified launchType,由开发者控制实例复用
 * 
 * module.json5 配置:
 * "launchType": "specified"
 */
export default class ChatAbility extends UIAbility {
  // 实例标识映射表:key -> instanceId
  private static instanceMap: Map<string, string> = new Map();

  /**
   * onAcceptWant 回调 - 决定是否复用已有实例
   * 这是 specified 模式的核心回调
   * 返回字符串key:相同key复用实例,不同key创建新实例
   */
  onAcceptWant(want: Want): string {
    // 从 Want 参数中获取会话ID
    const chatId = want.parameters?.chatId as string;

    if (chatId) {
      // 同一个聊天会话复用同一个实例
      const instanceKey = `chat_${chatId}`;
      hilog.info(0x0001, TAG, `会话实例Key: ${instanceKey}`);
      return instanceKey;
    }

    // 没有指定chatId,创建新实例
    return `chat_new_${Date.now()}`;
  }

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    const chatId = want.parameters?.chatId as string;
    const instanceKey = chatId ? `chat_${chatId}` : 'new';

    hilog.info(0x0001, TAG, `创建实例: ${instanceKey}`);

    // 记录实例
    ChatAbility.instanceMap.set(instanceKey, this.context.abilityInfo.name);

    // 根据chatId加载对应的聊天数据
    this.loadChatData(chatId);
  }

  /**
   * onNewWant - 已有实例收到新的Want
   * singleton 或 specified 模式下复用实例时触发
   */
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    const chatId = want.parameters?.chatId as string;
    hilog.info(0x0001, TAG, `复用实例,新chatId: ${chatId}`);

    // 切换到新的聊天会话
    this.loadChatData(chatId);

    // 通知UI更新
    AppStorage.setOrCreate('currentChatId', chatId);
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/ChatPage');
  }

  onForeground(): void {
    hilog.info(0x0001, TAG, '聊天页面回到前台');
  }

  onBackground(): void {
    hilog.info(0x0001, TAG, '聊天页面进入后台');
    // 保存当前聊天草稿
    this.saveChatDraft();
  }

  /**
   * 加载聊天数据
   */
  private loadChatData(chatId: string) {
    if (!chatId) return;
    // 从数据库加载聊天记录
    hilog.info(0x0001, TAG, `加载聊天数据: ${chatId}`);
  }

  /**
   * 保存聊天草稿
   */
  private saveChatDraft() {
    const draft = AppStorage.get<string>('chatDraft') || '';
    if (draft) {
      hilog.info(0x0001, TAG, '聊天草稿已保存');
    }
  }
}

// ============ 调用方:启动指定聊天会话 ============

import { common } from '@kit.AbilityKit';

/**
 * 启动聊天会话的工具方法
 */
function openChatSession(context: common.UIAbilityContext, chatId: string) {
  const want: Want = {
    bundleName: 'com.example.chatapp',
    abilityName: 'ChatAbility',
    parameters: {
      chatId: chatId  // 传递会话ID,决定是否复用实例
    }
  };

  context.startAbility(want).then(() => {
    hilog.info(0x0001, TAG, `启动聊天会话成功: ${chatId}`);
  }).catch((err: Error) => {
    hilog.error(0x0001, TAG, `启动聊天会话失败: ${err.message}`);
  });
}

3.3 切换状态感知的 UI 组件

import { AppStorage } from '@kit.ArkUI';

/**
 * 切换状态感知的页面组件
 * 演示如何在UI层面响应前后台切换
 */
@Entry
@Component
struct SwitchAwarePage {
  // 通过 @StorageLink 双向绑定状态,Ability 层保存/恢复时自动同步
  @StorageLink('scrollOffset') scrollOffset: number = 0;
  @StorageLink('currentTabIndex') currentTabIndex: number = 0;
  @StorageLink('searchKeyword') searchKeyword: string = '';
  @StorageLink('inputDraft') inputDraft: string = '';

  // 页面可见性状态
  @State isPageVisible: boolean = true;
  // 后台时长提示
  @State backgroundDurationTip: string = '';

  // 模拟列表数据
  @State dataList: string[] = Array.from({ length: 50 }, (_, i) => `列表项 ${i + 1}`);

  // 列表控制器(用于恢复滚动位置)
  private scroller: Scroller = new Scroller();

  /**
   * 页面即将显示
   */
  onPageShow() {
    this.isPageVisible = true;

    // 恢复滚动位置(延迟执行,等列表渲染完成)
    setTimeout(() => {
      if (this.scrollOffset > 0) {
        this.scroller.scrollToIndex(this.scrollOffset);
        this.backgroundDurationTip = '已恢复上次浏览位置';
        // 3秒后清除提示
        setTimeout(() => {
          this.backgroundDurationTip = '';
        }, 3000);
      }
    }, 300);
  }

  /**
   * 页面即将隐藏
   */
  onPageHide() {
    this.isPageVisible = false;
  }

  build() {
    Column() {
      // 状态提示条
      if (this.backgroundDurationTip) {
        Row() {
          Text(this.backgroundDurationTip)
            .fontSize(12)
            .fontColor('#4CAF50')
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#E8F5E9')
        .justifyContent(FlexAlign.Center)
      }

      // 搜索栏(保留搜索关键词)
      Search({ value: this.searchKeyword, placeholder: '搜索...' })
        .width('100%')
        .height(48)
        .margin({ bottom: 12 })
        .onChange((value: string) => {
          this.searchKeyword = value;
        })

      // Tab 栏
      Tabs({ index: this.currentTabIndex }) {
        TabContent() {
          this.ListContent()
        }
        .tabBar('首页')

        TabContent() {
          this.DiscoverContent()
        }
        .tabBar('发现')

        TabContent() {
          this.ProfileContent()
        }
        .tabBar('我的')
      }
      .width('100%')
      .layoutWeight(1)
      .onChange((index: number) => {
        this.currentTabIndex = index;
      })

      // 底部输入栏(保留草稿)
      Row() {
        TextInput({ text: this.inputDraft, placeholder: '输入消息...' })
          .layoutWeight(1)
          .height(40)
          .onChange((value: string) => {
            this.inputDraft = value;
          })
        Button('发送')
          .height(40)
          .margin({ left: 8 })
          .onClick(() => {
            this.inputDraft = '';
          })
      }
      .width('100%')
      .padding(8)
      .backgroundColor('#FFFFFF')
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  ListContent() {
    List({ space: 8, scroller: this.scroller }) {
      ForEach(this.dataList, (item: string, index: number) => {
        ListItem() {
          Row() {
            Text(item)
              .fontSize(16)
              .fontColor('#333333')
          }
          .width('100%')
          .padding(16)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
        }
      }, (item: string, index: number) => `${index}`)
    }
    .width('100%')
    .height('100%')
    .padding({ left: 16, right: 16 })
    .onScroll(() => {
      // 滚动时记录位置(简化处理,记录可见的第一个item索引)
      this.scrollOffset = this.scroller.currentOffset().yOffset as number / 60;
    })
  }

  @Builder
  DiscoverContent() {
    Column() {
      Text('发现页面')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  ProfileContent() {
    Column() {
      Text('我的页面')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

四、踩坑与注意事项

4.1 onBackground 中做异步操作的陷阱

// ❌ 危险!onBackground 中做异步操作
onBackground(): void {
  // 这个异步操作可能来不及完成,进程就被杀了
  preferences.getPreferences(this.context, 'cache').then(pref => {
    pref.put('key', 'value');
    pref.flush(); // 可能永远执行不到
  });
}

// ✅ 正确做法:同步保存关键数据
onBackground(): void {
  // 使用同步API保存最关键的数据
  AppStorage.setOrCreate('key', 'value');
  // 异步操作放在 onCreate/onForeground 中补偿
}

4.2 AppStorage 与 LocalStorage 的选择

特性 AppStorage LocalStorage
作用域 应用全局 UIAbility 实例级
跨Ability共享 ✅ 是 ❌ 否
多实例隔离 ❌ 共享同一份数据 ✅ 每个实例独立
适合场景 全局状态(用户信息、主题) 实例级状态(聊天会话数据)

踩坑:多实例模式下,如果用 AppStorage 存储实例级数据,不同实例会互相覆盖!应该用 LocalStorage 替代。

4.3 最近任务列表的快照问题

HarmonyOS 会在应用进入后台时截取一张快照显示在最近任务列表中。如果你的应用包含敏感信息(如银行账号、聊天内容),应该:

onBackground(): void {
  // 在 onBackground 中用空白页覆盖敏感内容
  // 系统截取快照时就会截到空白页
  AppStorage.setOrCreate('showSensitiveContent', false);
}

onForeground(): void {
  // 回到前台时恢复显示
  AppStorage.setOrCreate('showSensitiveContent', true);
}

4.4 onNewWant 的调用时机

onNewWant 只在 singleton 和 specified 模式下,已有实例被重新拉起时触发。multiton 模式不会触发 onNewWant,因为每次都创建新实例,走的是 onCreate。

4.5 多窗口场景的状态冲突

在平板或折叠屏上,应用可能同时以多个窗口存在。此时 onForeground/onBackground 的语义会发生变化——一个窗口在前台,另一个窗口可能在后台,但它们属于同一个 UIAbility 实例。需要通过窗口事件而非 Ability 生命周期来管理状态。


五、HarmonyOS 6 适配

5.1 应用状态感知增强

HarmonyOS 6 新增了更精细的应用状态回调:

新增API 说明
onWindowStateChange 窗口状态变化回调(多窗口场景)
onVisibilityChange 可见性变化回调(分屏/悬浮窗)
onMemoryLevel 内存压力等级回调

5.2 状态保存框架

HarmonyOS 6 引入了声明式的状态保存框架 @StateSaver

// HarmonyOS 6 新增(示意)
@Entry
@Component
struct StateAwarePage {
  @StateSaver('scrollPosition')
  @State scrollPosition: number = 0;

  @StateSaver('tabIndex')
  @State tabIndex: number = 0;
  // 框架自动处理保存和恢复,无需手动调用
}

5.3 跨设备切换适配

HarmonyOS 6 强化了分布式能力,应用切换可能发生在不同设备之间:

onForeground(): void {
  // 检查是否跨设备恢复
  const currentDeviceId = this.context.distributedInfo?.deviceId;
  if (currentDeviceId !== this.lastDeviceId) {
    // 跨设备恢复,需要重新适配屏幕尺寸
    this.adaptToNewDevice(currentDeviceId);
  }
}

六、总结

mindmap
  root((应用切换))
    前后台切换
      onForeground
        恢复UI状态
        检查数据新鲜度
        刷新过期数据
      onBackground
        保存UI状态
        持久化关键数据
        释放非必要资源
        隐藏敏感信息
    最近任务列表
      系统自动截取快照
      敏感信息需遮盖
      划掉任务触发onDestroy
    多实例管理
      singleton
        全局唯一实例
        onNewWant复用
      multiton
        每次创建新实例
        不触发onNewWant
      specified
        开发者控制复用
        onAcceptWant返回key
    状态保存
      内存缓存
        AppStorage全局
        LocalStorage实例级
      持久化存储
        preferences
        数据库
      数据新鲜度
        后台时长判断
        超时自动刷新
    踩坑
      onBackground不做异步
      多实例AppStorage冲突
      快照敏感信息泄露
      多窗口状态管理

核心知识点回顾

  1. 前后台切换双回调:onBackground 保存状态、释放资源;onForeground 恢复状态、刷新数据
  2. 数据新鲜度判断:根据后台时长决定是直接恢复还是重新加载
  3. 三级状态保存:内存缓存(最快)→ 持久化存储(防进程被杀)→ 网络恢复(数据过期时)
  4. launchType 三模式:singleton(复用)、multiton(新建)、specified(开发者控制)
  5. 多实例隔离:实例级数据用 LocalStorage,全局数据用 AppStorage
  6. 安全考量:onBackground 中隐藏敏感信息,防止最近任务快照泄露

应用切换是用户体验的"最后一公里"。你的应用功能再强大,如果切换回来时数据丢了、页面重置了,用户也会觉得这是个半成品。把状态保存做到位,让切换真正"无缝"。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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