HarmonyOS开发:动态特性包——Feature包配置

举报
Jack20 发表于 2026/06/25 20:37:24 2026/06/25
【摘要】 HarmonyOS开发:动态特性包——Feature包配置📌 核心要点:Feature包是HarmonyOS按需安装的核心机制,将非核心功能拆分为独立模块,用户用到时才下载安装,大幅减小首次安装体积。 背景与动机你的应用功能越来越多:首页、搜索、购物车、订单、支付、客服、直播、社区……全塞进一个HAP?首次安装包50MB+,用户还没用就先被下载大小劝退了。而且仔细想想,用户打开应用的第一...

HarmonyOS开发:动态特性包——Feature包配置

📌 核心要点:Feature包是HarmonyOS按需安装的核心机制,将非核心功能拆分为独立模块,用户用到时才下载安装,大幅减小首次安装体积。

背景与动机

你的应用功能越来越多:首页、搜索、购物车、订单、支付、客服、直播、社区……全塞进一个HAP?首次安装包50MB+,用户还没用就先被下载大小劝退了。

而且仔细想想,用户打开应用的第一件事是什么?看首页。直播、社区、客服这些功能,可能80%的用户压根不会用。凭什么让他们为用不上的功能付出下载时间和存储空间?

这就是Feature包存在的意义。核心功能放Entry,随应用安装;扩展功能放Feature,用到时再下载。首次安装可能就5MB,用户感知好,留存率高。

但Feature包不是随便拆的。拆错了,模块间通信复杂、加载体验差、维护成本高。怎么拆、怎么配、怎么管理,是这篇文章要讲清楚的。

核心原理

Atomic Service与动态安装

HarmonyOS的动态安装体系基于Atomic Service(元服务)理念:

flowchart TD
    A[用户安装应用] --> B[只安装Entry HAP]
    B --> C[用户使用核心功能]
    C --> D{触发扩展功能?}
    D -->|| C
    D -->|| E[检查Feature是否已安装]
    E -->|已安装| F[直接跳转Feature]
    E -->|未安装| G[从应用市场下载Feature]
    G --> H{下载安装成功?}
    H -->|| F
    H -->|| I[提示用户重试]
    F --> J[使用Feature功能]
    J --> K{退出Feature?}
    K -->|| C

    classDef start fill:#2ECC71,stroke:#25A55A,color:#fff
    classDef process fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef decision fill:#F39C12,stroke:#D68910,color:#fff
    classDef error fill:#FF6B6B,stroke:#CC5555,color:#fff
    classDef success fill:#9B59B6,stroke:#8E44AD,color:#fff

    class A,B,C start
    class D,E,F,J process
    class G,H,K decision
    class I error

关键概念:

  • Entry HAP:应用入口,必须安装,包含核心功能
  • Feature HAP:扩展功能包,按需安装,不能独立运行
  • HSP:共享模块,随Entry安装,供Entry和Feature共用
  • 动态安装:Feature包在运行时按需从应用市场下载安装

Feature包设计原则

拆Feature包不是"想拆就拆",要遵循以下原则:

  1. 功能独立性:Feature模块的功能应该相对独立,不依赖其他Feature模块
  2. 体积合理性:Feature模块体积不宜过小(几百KB的不值得拆),也不宜过大(超过20MB拆分效果不明显)
  3. 使用频率:低频使用的功能才适合拆成Feature,高频使用的功能应该放Entry
  4. 通信简洁:Feature与Entry的通信要尽量简单,避免复杂的双向数据流

Feature包生命周期

flowchart LR
    A[未安装] -->|下载安装| B[已安装]
    B -->|用户使用| C[运行中]
    C -->|退出| B
    B -->|系统回收| D[已卸载]
    D -->|再次使用| A

    classDef uninstalled fill:#FF6B6B,stroke:#CC5555,color:#fff
    classDef installed fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef running fill:#2ECC71,stroke:#25A55A,color:#fff
    classDef recycled fill:#F39C12,stroke:#D68910,color:#fff

    class A uninstalled
    class B installed
    class C running
    class D recycled

Feature包有四种状态:未安装 → 已安装 → 运行中 → 已卸载(系统回收)。系统可能在存储不足时自动卸载不常用的Feature包,下次使用时需要重新下载。

代码实战

基础用法:创建Feature模块

第一步:在工程中创建Feature模块

// feature_live/src/main/module.json5
{
  "module": {
    "name": "feature_live",
    "type": "feature",                    // Feature类型
    "description": "$string:live_desc",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": false,          // 关键:不随应用安装
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "LiveAbility",
        "srcEntry": "./ets/liveability/LiveAbility.ets",
        "description": "$string:LiveAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:LiveAbility_label",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          }
        ]
      }
    ]
  }
}

关键配置:

  • type: "feature"——声明为Feature模块
  • deliveryWithInstall: false——不随应用安装,这是Feature的核心特征

第二步:在build-profile.json5中注册模块

// build-profile.json5
{
  "app": {
    "signingConfigs": [],
    "compileSdkVersion": 12,
    "compatibleSdkVersion": 10,
    "products": [
      {
        "name": "default",
        "signingConfig": "default"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [{ "name": "default", "applyToProducts": ["default"] }]
    },
    {
      "name": "feature_live",
      "srcPath": "./feature_live",
      "targets": [{ "name": "default", "applyToProducts": ["default"] }]
    },
    {
      "name": "shared_common",
      "srcPath": "./shared_common",
      "targets": [{ "name": "default", "applyToProducts": ["default"] }]
    }
  ]
}

第三步:从Entry跳转到Feature

// entry/src/main/ets/pages/Index.ets
import { common, abilityManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { bundleManager } from '@kit.AbilityKit';

@Entry
@Component
struct IndexPage {
  // 检查Feature模块是否已安装
  async isFeatureInstalled(moduleName: string): Promise<boolean> {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE
      );
      const installedModules = bundleInfo.hapModuleInfos?.map(m => m.name) || [];
      return installedModules.includes(moduleName);
    } catch (error) {
      console.error(`检查模块安装状态失败: ${error}`);
      return false;
    }
  }

  // 跳转到直播Feature
  async navigateToLive(): Promise<void> {
    const moduleName = 'feature_live';
    const isInstalled = await this.isFeatureInstalled(moduleName);

    if (!isInstalled) {
      // Feature未安装,需要先安装
      console.info('直播模块未安装,正在安装...');
      const installed = await this.installFeature(moduleName);
      if (!installed) {
        console.error('直播模块安装失败');
        return;
      }
    }

    // 跳转到Feature的Ability
    const context = getContext(this) as common.UIAbilityContext;
    const want = {
      deviceId: '',
      bundleName: 'com.example.myapp',
      abilityName: 'LiveAbility',
      moduleName: moduleName
    };

    try {
      await context.startAbility(want);
      console.info('成功跳转到直播模块');
    } catch (err) {
      const error = err as BusinessError;
      console.error(`跳转失败: ${error.code} - ${error.message}`);
    }
  }

  // 安装Feature模块
  async installFeature(moduleName: string): Promise<boolean> {
    try {
      // 通过应用市场安装(需要AGC配置)
      // 这里使用installer模块进行安装
      const context = getContext(this) as common.UIAbilityContext;
      const installer = await bundleManager.getBundleInstaller();

      // 构造安装参数
      const installParam: bundleManager.InstallParam = {
        userId: 100,
        installFlag: bundleManager.InstallFlag.NORMAL,
        isKeepData: false
      };

      // 注意:实际安装需要从应用市场获取Feature的HAP路径
      // 这里是示意代码,实际路径需要从AGC获取
      const hapPaths = [`/data/storage/el2/base/haps/${moduleName}.hap`];

      await installer.install(hapPaths, installParam);
      console.info(`${moduleName} 安装成功`);
      return true;
    } catch (error) {
      console.error(`${moduleName} 安装失败: ${error}`);
      return false;
    }
  }

  build() {
    Column() {
      Text('首页')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)

      Button('进入直播间')
        .margin({ top: 20 })
        .onClick(() => this.navigateToLive())
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

进阶用法:Feature包安装状态管理

在实际项目中,你需要一个统一的Feature管理器来处理安装状态、下载进度、错误重试:

// shared_common/src/main/ets/manager/FeatureManager.ets
// Feature模块管理器

import { bundleManager } from '@kit.AbilityKit';
import { common } from '@kit.AbilityKit';
import { emitter } from '@kit.BasicServicesKit';

// Feature模块定义
interface FeatureModule {
  name: string;               // 模块名
  displayName: string;        // 显示名称
  abilityName: string;        // 入口Ability名
  description: string;        // 描述
  estimatedSize: string;      // 预估大小
  requiredHsp: string[];      // 依赖的HSP模块
}

// 安装状态
enum FeatureInstallState {
  NOT_INSTALLED = 'not_installed',
  INSTALLING = 'installing',
  INSTALLED = 'installed',
  INSTALL_FAILED = 'install_failed'
}

// 安装进度事件
interface InstallProgressEvent {
  moduleName: string;
  state: FeatureInstallState;
  progress: number;           // 0-100
  error?: string;
}

// 支持的事件ID
const EVENT_FEATURE_INSTALL = 10001;

export class FeatureManager {
  private static instance: FeatureManager | null = null;
  private context: common.UIAbilityContext | null = null;
  private installStates: Map<string, FeatureInstallState> = new Map();
  private installProgress: Map<string, number> = new Map();

  // 注册所有Feature模块
  private readonly featureRegistry: FeatureModule[] = [
    {
      name: 'feature_live',
      displayName: '直播',
      abilityName: 'LiveAbility',
      description: '观看和参与直播',
      estimatedSize: '8MB',
      requiredHsp: ['shared_common']
    },
    {
      name: 'feature_social',
      displayName: '社区',
      abilityName: 'SocialAbility',
      description: '社区互动与内容分享',
      estimatedSize: '12MB',
      requiredHsp: ['shared_common']
    },
    {
      name: 'feature_ai',
      displayName: 'AI助手',
      abilityName: 'AiAbility',
      description: '智能对话与AI功能',
      estimatedSize: '15MB',
      requiredHsp: ['shared_common']
    }
  ];

  private constructor() {}

  static getInstance(): FeatureManager {
    if (!FeatureManager.instance) {
      FeatureManager.instance = new FeatureManager();
    }
    return FeatureManager.instance;
  }

  init(context: common.UIAbilityContext): void {
    this.context = context;
    // 初始化所有模块的安装状态
    this.refreshInstallStates();
  }

  // 刷新所有Feature的安装状态
  async refreshInstallStates(): Promise<void> {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE
      );
      const installedModules = new Set(
        bundleInfo.hapModuleInfos?.map(m => m.name) || []
      );

      for (const feature of this.featureRegistry) {
        const state = installedModules.has(feature.name)
          ? FeatureInstallState.INSTALLED
          : FeatureInstallState.NOT_INSTALLED;
        this.installStates.set(feature.name, state);
      }
    } catch (error) {
      console.error(`刷新安装状态失败: ${error}`);
    }
  }

  // 获取Feature安装状态
  getInstallState(moduleName: string): FeatureInstallState {
    return this.installStates.get(moduleName) || FeatureInstallState.NOT_INSTALLED;
  }

  // 获取安装进度
  getInstallProgress(moduleName: string): number {
    return this.installProgress.get(moduleName) || 0;
  }

  // 获取所有Feature列表(含状态)
  getAllFeatures(): Array<FeatureModule & { state: FeatureInstallState; progress: number }> {
    return this.featureRegistry.map(f => ({
      ...f,
      state: this.getInstallState(f.name),
      progress: this.getInstallProgress(f.name)
    }));
  }

  // 安装Feature
  async installFeature(moduleName: string): Promise<boolean> {
    const feature = this.featureRegistry.find(f => f.name === moduleName);
    if (!feature) {
      console.error(`未注册的Feature: ${moduleName}`);
      return false;
    }

    // 检查是否已安装
    if (this.getInstallState(moduleName) === FeatureInstallState.INSTALLED) {
      return true;
    }

    // 更新状态为安装中
    this.installStates.set(moduleName, FeatureInstallState.INSTALLING);
    this.installProgress.set(moduleName, 0);
    this.emitProgress(moduleName, FeatureInstallState.INSTALLING, 0);

    try {
      // 模拟下载进度(实际应从应用市场获取)
      for (let progress = 0; progress <= 100; progress += 10) {
        await this.delay(200);    // 模拟下载耗时
        this.installProgress.set(moduleName, progress);
        this.emitProgress(moduleName, FeatureInstallState.INSTALLING, progress);
      }

      // 执行安装
      // 实际场景中,这里调用bundleManager的安装接口
      // 或者通过应用市场的SDK触发安装

      // 安装成功
      this.installStates.set(moduleName, FeatureInstallState.INSTALLED);
      this.installProgress.set(moduleName, 100);
      this.emitProgress(moduleName, FeatureInstallState.INSTALLED, 100);

      console.info(`Feature ${moduleName} 安装成功`);
      return true;
    } catch (error) {
      this.installStates.set(moduleName, FeatureInstallState.INSTALL_FAILED);
      this.emitProgress(moduleName, FeatureInstallState.INSTALL_FAILED, 0, String(error));

      console.error(`Feature ${moduleName} 安装失败: ${error}`);
      return false;
    }
  }

  // 跳转到Feature
  async navigateToFeature(moduleName: string): Promise<boolean> {
    const feature = this.featureRegistry.find(f => f.name === moduleName);
    if (!feature) return false;

    // 确保已安装
    const state = this.getInstallState(moduleName);
    if (state !== FeatureInstallState.INSTALLED) {
      const installed = await this.installFeature(moduleName);
      if (!installed) return false;
    }

    // 跳转
    if (!this.context) return false;

    const want = {
      deviceId: '',
      bundleName: this.context.abilityInfo.bundleName,
      abilityName: feature.abilityName,
      moduleName: moduleName
    };

    try {
      await this.context.startAbility(want);
      return true;
    } catch (error) {
      console.error(`跳转Feature失败: ${error}`);
      return false;
    }
  }

  // 发送进度事件
  private emitProgress(
    moduleName: string,
    state: FeatureInstallState,
    progress: number,
    error?: string
  ): void {
    emitter.emit({ eventId: EVENT_FEATURE_INSTALL }, {
      data: {
        moduleName,
        state,
        progress,
        error
      }
    });
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

完整示例:Feature管理UI

// entry/src/main/ets/pages/FeatureListPage.ets
import { FeatureManager, FeatureInstallState, EVENT_FEATURE_INSTALL } from '@ohos/shared_common';
import { emitter } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct FeatureListPage {
  private featureManager: FeatureManager = FeatureManager.getInstance();
  @State features: Array<{
    name: string;
    displayName: string;
    description: string;
    estimatedSize: string;
    state: FeatureInstallState;
    progress: number;
  }> = [];

  aboutToAppear(): void {
    // 初始化FeatureManager
    this.featureManager.init(getContext(this) as common.UIAbilityContext);

    // 加载Feature列表
    this.refreshFeatures();

    // 监听安装进度
    emitter.on({ eventId: EVENT_FEATURE_INSTALL }, (eventData) => {
      this.refreshFeatures();
    });
  }

  aboutToDisappear(): void {
    emitter.off(EVENT_FEATURE_INSTALL);
  }

  refreshFeatures(): void {
    this.features = this.featureManager.getAllFeatures();
  }

  // 获取状态文字
  getStateText(state: FeatureInstallState): string {
    switch (state) {
      case FeatureInstallState.NOT_INSTALLED:
        return '未安装';
      case FeatureInstallState.INSTALLING:
        return '安装中...';
      case FeatureInstallState.INSTALLED:
        return '已安装';
      case FeatureInstallState.INSTALL_FAILED:
        return '安装失败';
      default:
        return '未知';
    }
  }

  // 获取按钮文字
  getButtonText(state: FeatureInstallState): string {
    switch (state) {
      case FeatureInstallState.NOT_INSTALLED:
        return '安装并打开';
      case FeatureInstallState.INSTALLING:
        return '安装中...';
      case FeatureInstallState.INSTALLED:
        return '打开';
      case FeatureInstallState.INSTALL_FAILED:
        return '重试';
      default:
        return '打开';
    }
  }

  build() {
    Column() {
      // 标题
      Text('更多功能')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      // Feature列表
      List() {
        ForEach(this.features, (feature: typeof this.features[0]) => {
          ListItem() {
            Row() {
              // 左侧信息
              Column() {
                Text(feature.displayName)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium)

                Text(feature.description)
                  .fontSize(14)
                  .fontColor('#999999')
                  .margin({ top: 4 })

                Text(`${feature.estimatedSize} · ${this.getStateText(feature.state)}`)
                  .fontSize(12)
                  .fontColor('#666666')
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              // 右侧按钮
              Button(this.getButtonText(feature.state))
                .fontSize(14)
                .height(36)
                .enabled(feature.state !== FeatureInstallState.INSTALLING)
                .onClick(async () => {
                  if (feature.state === FeatureInstallState.INSTALLED) {
                    // 已安装,直接打开
                    await this.featureManager.navigateToFeature(feature.name);
                  } else {
                    // 未安装,先安装再打开
                    const installed = await this.featureManager.installFeature(feature.name);
                    if (installed) {
                      await this.featureManager.navigateToFeature(feature.name);
                    }
                  }
                  this.refreshFeatures();
                })
            }
            .width('100%')
            .padding(16)
            .backgroundColor(Color.White)
            .borderRadius(12)
          }
          .margin({ bottom: 12 })
        })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#F5F5F5')
  }
}

踩坑与注意事项

坑1:Feature安装后跳转失败

Feature刚安装完,立即跳转可能失败。因为系统需要时间注册新安装的Ability,跳转太早会报"ability not found"。

解法:安装成功后等待一小段时间再跳转,或者先查询Ability是否已注册:

// 安装后等待Ability注册
async function waitAbilityReady(moduleName: string, abilityName: string): Promise<boolean> {
  const maxRetries = 5;
  const retryInterval = 500;    // 500ms

  for (let i = 0; i < maxRetries; i++) {
    try {
      const want = {
        action: 'action.system.home',
        entities: ['entity.system.home'],
        moduleName: moduleName,
        abilityName: abilityName
      };
      const abilities = await bundleManager.queryAbilityInfo(want);
      if (abilities.length > 0) return true;
    } catch (e) {
      // 还没注册,继续等待
    }
    await new Promise(resolve => setTimeout(resolve, retryInterval));
  }
  return false;
}

坑2:Feature依赖的HSP未安装

Feature模块依赖HSP共享模块,如果HSP没有随Entry安装,Feature安装后会运行失败。

解法:确保HSP的deliveryWithInstall: true,这样HSP会随Entry一起安装。Feature模块的oh-package.json5中声明的HSP依赖,必须和Entry一致。

坑3:系统自动卸载Feature导致体验中断

系统在存储不足时可能自动卸载不常用的Feature包。用户下次使用时需要重新下载,体验中断。

解法

  • 在Feature入口处检查安装状态,未安装时自动触发安装
  • 安装过程给用户明确的进度提示
  • 关键Feature可以设为deliveryWithInstall: true,随应用安装

坑4:Feature模块间互相依赖

Feature A依赖Feature B,但Feature B没安装。这种跨Feature依赖会导致复杂的安装链。

解法Feature模块之间不应该互相依赖。如果两个Feature有共享逻辑,把共享逻辑抽到HSP中。Feature只依赖HSP,不依赖其他Feature。

坑5:调试时Feature安装路径问题

开发调试时,Feature的HAP文件路径和发布时不同。调试时需要手动指定HAP路径,否则安装失败。

解法:调试时使用DevEco Studio的"Deploy"功能,IDE会自动处理所有模块的安装。命令行调试时,需要手动安装所有HAP:

# 安装Entry
hdc install entry/build/default/outputs/default/entry-default-signed.hap

# 安装HSP
hdc install shared_common/build/default/outputs/default/shared_common-default-signed.hsp

# 安装Feature
hdc install feature_live/build/default/outputs/default/feature_live-default-signed.hap

HarmonyOS 6适配说明

HarmonyOS 6对动态特性包做了以下增强:

  1. Feature模块预加载:HarmonyOS 6支持Feature模块的预加载(Preload)。应用可以在空闲时预先下载Feature包但不安装,用户触发时秒级安装。这解决了"首次使用需要等待下载"的体验问题。

  2. Feature模块卸载回调:当系统卸载Feature模块时,会通知应用,应用可以保存状态数据。用户下次使用时,可以恢复之前的操作状态。

  3. 多Feature并行安装:HarmonyOS 6支持同时下载和安装多个Feature包,大幅缩短批量安装时间。

  4. Feature包增量更新:Feature包更新时支持增量下载,只下载变化的部分,减少下载量。

  5. 离线Feature管理:新增bundleManager.getUninstalledModules()接口,可以查询已卸载但仍有残留数据的Feature模块,用于清理存储空间。

总结

Feature包是HarmonyOS应用架构的"减负"利器。核心功能随装随用,扩展功能按需加载——用户体验好,包体积小。

但Feature包不是万能的,它增加了架构复杂度。要不要拆、怎么拆,要看具体场景:

  • 适合拆:低频功能、体积大、相对独立(如直播、AI助手、社区)
  • 不适合拆:高频功能、体积小、强依赖主流程(如搜索、购物车)

记住:拆是为了更好的体验,不是为了拆而拆

维度 评价
学习难度 ⭐⭐⭐⭐ 概念不难,但安装流程、状态管理、模块通信需要仔细处理
使用频率 ⭐⭐⭐ 中大型项目必用,小型项目可不用
重要程度 ⭐⭐⭐⭐ 直接影响首次安装体验和用户留存
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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