PaxLee
PaxLee学无止境
返回列表
 Flutter 适配 HarmonyOS 完整教程:从零到上架的踩坑指南
FlutterHarmonyOS从零到上架的踩坑指南

Flutter 适配 HarmonyOS 完整教程:从零到上架的踩坑指南

发布于 2026年7月14日26 min read

Flutter 适配 HarmonyOS 完整教程:从零到上架的踩坑指南

> 面向已经会用 Flutter 发 Android / iOS 包的工程师。
> 读完本文,你应该能:搭好鸿蒙工具链 → 让现有项目编出第一份 HAP → 处理插件与业务兼容 → 打出可上架的 `.app`。

这不是「再写一个 Hello World」,而是基于真实 App 上架过程的实操路线。文中示例包名统一写成 com.example.myapp,请自行替换。

多语言版本: English · Русский


你会得到什么

  • 为什么官方 Flutter 不够用,必须换哪一条工具链
  • 第一步到第 N 步该装什么、改什么、跑什么命令
  • 插件怎么配才能编过(这是最长的一坑)
  • 业务怎么裁剪:登录、存储、WebView、通知、推送等
  • 调试 HAP vs 上架 APP 分别怎么出包
  • 签名与华为登录 clientId 如何避免上架后立刻翻车

先建立正确心智模型(读完再动手)

把现有 Flutter 应用「搬」到鸿蒙,不是加一个 flavor。它更接近「加了一条完整新平台工具链」:

认知现实
flutter build apk 一套就够官方 Flutter 没有 hap / ohos app 子命令
插件写好 Android 实现就行多数联邦插件 没有 OHOS 实现,要整链 override
改几行 Platform.isAndroid要系统做能力矩阵:Firebase / 通知 / 部分原生库要降级
打个 release 包就能上架调试用 .hap,应用市场要 .app,签名与 OAuth 还要对齐

可以用一句话记住:

OHOS Flutter Fork + ohos 原生工程 + 插件整链 override + Dart 能力裁剪 + 发布签名与华为登录对齐。

接下来按顺序做。不要跳步——很多人卡在第三天,是因为第 1~2 步没配对就去改业务代码。


总流程一览

第 1 步  安装 DevEco Studio,准备 HarmonyOS SDK 目录
第 2 步  安装 / 配置 OpenHarmony 版 Flutter(Fork)
第 3 步  为现有项目生成 / 核对 ohos 工程
第 4 步  写 local.properties,对齐 SDK 与 Flutter 路径
第 5 步  配置调试签名,能 SignHap
第 6 步  改造 pubspec:ohos 插件 + dependency_overrides
第 7 步  写平台抽象 AppPlatform,先让项目能分析通过
第 8 步  首次 flutter build hap --debug,真机安装
第 9 步  逐项适配业务(登录 / 存储 / WebView / TTS / 支付…)
第 10 步 配置发布签名,flutter build app --release 上架

下面逐步展开。


第 1 步:安装 DevEco,准备 SDK 目录

1.1 安装什么

  1. 从华为开发者网站安装 DevEco Studio(macOS 下常见路径:/Applications/DevEco-Studio.app
  2. 打开 DevEco,按向导下载 HarmonyOS SDK(当前工程实践多为 API 24 / HarmonyOS 6.1.1 一类版本,以你 DevEco 可选版本为准)
  3. 确认命令行工具可用:
# hdc:鸿蒙设备连接工具,一般在 DevEco 的 sdk/.../toolchains 下
hdc list targets

能看到已连接设备,或暂时为空但命令可执行,都说明工具链大致在。

1.2 关键坑:SDK 目录名必须符合 hvigor 约定

hvigor 6.x 一类构建常常要求本机存在类似结构:

~/Library/OpenHarmony/Sdk/HarmonyOS-6.1.1/
  ├── openharmony/
  │   ├── ets
  │   ├── native
  │   ├── toolchains
  │   └── ...
  └── hms/
      ├── ets
      ├── native
      └── ...

现象: 编译报找不到 HarmonyOS-6.1.1 / API 24。 原因: DevEco 内置 SDK 路径往往是 Contents/sdk/default/openharmony不等于上面这个目录名约定。

做法: 把 DevEco 内组件软链(或拷贝)到约定目录。可参考下面脚本逻辑自行落地:

#!/usr/bin/env bash
set -euo pipefail

```text
DEVECO_APP="${DEVECO_APP:-/Applications/DevEco-Studio.app}"
SDK_HOME="${HOME}/Library/OpenHarmony/Sdk"
SOURCE_OH="${DEVECO_APP}/Contents/sdk/default/openharmony"
SOURCE_HMS="${DEVECO_APP}/Contents/sdk/default/hms"
TARGET_SDK="${SDK_HOME}/HarmonyOS-6.1.1"

mkdir -p "${TARGET_SDK}/openharmony" "${TARGET_SDK}/hms"

for component in ets js native previewer toolchains; do
  ln -sfn "${SOURCE_OH}/${component}" "${TARGET_SDK}/openharmony/${component}"
done
for component in ets native previewer toolchains; do
  ln -sfn "${SOURCE_HMS}/${component}" "${TARGET_SDK}/hms/${component}"
done

echo "SDK ready: ${TARGET_SDK}" ls -la "${TARGET_SDK}"


> Windows 用户请按 DevEco 文档配置同等 SDK;核心是让 hvigor 能解析到带 `openharmony/`  `hms/` 的完整 SDK。

**本步完成标准:** `~/Library/OpenHarmony/Sdk/HarmonyOS-6.1.1`(或你项目 `compileSdkVersion` 对应的版本名)存在且含子目录。

---

## 第 2 步:安装 OpenHarmony 版 Flutter(Fork)

### 2.1 为什么必须换 Flutter

**在官方稳定版 Flutter 上执行:**

```bash
flutter build -h

你通常只能看到 apk / appbundle / ios / web……没有 hap,也没有鸿蒙含义的 app

OpenHarmony 社区维护的 Fork(常见来源:openharmony-tpc/flutter_flutter 等,请以官方文档当前推荐仓库为准)才会带上:

flutter build hap    # 出调试 / 安装用 HAP
flutter build app    # 出应用市场用的 APP 包

以及引擎侧能力:

  • Platform.operatingSystem == 'ohos'
  • TargetPlatform.ohos

2.2 怎么装

推荐用 FVM 或手动 clone,单独放一个目录,不要覆盖你日常 Android/iOS 用的官方 Flutter。

# 示例:用 FVM 装一份 OHOS 专用 Flutter(版本号以社区当前推荐为准)
# 装完后记住这个 bin/flutter 的绝对路径
export OHOS_FLUTTER="$HOME/fvm/versions/openharmony_tpc/xxx/bin/flutter"

$OHOS_FLUTTER --version
$OHOS_FLUTTER build -h | grep -E 'hap|app'

本步完成标准: $OHOS_FLUTTER build -h 能看到 hapapp 两个子命令。

2.3 以后所有鸿蒙命令都用这套 Flutter

养成习惯:

# ❌ 不要
flutter pub get
flutter build hap

```text
# ✅ 要
$OHOS_FLUTTER pub get
$OHOS_FLUTTER build hap --debug

IDE 里也建议为「鸿蒙构建」单独配置 Flutter SDK 路径,避免一键跑错引擎。

---

## 第 3 步:为现有项目准备 `ohos/` 工程

### 3.1 已有 Flutter 项目怎么加鸿蒙端

**典型结构会变成:**

```text
your_flutter_app/
├── android/
├── ios/
├── lib/
├── pubspec.yaml
└── ohos/                    # 新增:HarmonyOS Stage 工程
    ├── AppScope/
       └── app.json5        # bundleName、versionCode、versionName
    ├── entry/               # 主模块(类比 android/app)
    ├── build-profile.json5  # 签名、compileSdk、product
    ├── hvigorfile.ts
    ├── local.properties     # 本机 SDK / Flutter 路径(一般不提交明文秘密)
    └── oh-package.json5

创建方式取决于你使用的 OHOS Flutter 工具版本,常见是:

cd your_flutter_app
$OHOS_FLUTTER create --platforms=ohos .
# 或按当前 Fork 文档中的「为已有项目启用 ohos」命令

然后用 DevEco Studio 打开 ohos/ 目录(注意:打开的是 ohos 工程,不是简单当资源文件夹看一眼)。

3.2 核对三个关键字段

AppScope/app.json5

{
  "app": {
    "bundleName": "com.example.myapp",
    "vendor": "example",
    "versionCode": 100,
    "versionName": "1.0.0",
    "icon": "$media:app_icon",
    "label": "$string:app_name"
  }
}

build-profile.json5 的 product

{
  "app": {
    "products": [
      {
        "name": "default",
        "compileSdkVersion": "6.1.1(24)",
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.0.0(20)",
        "runtimeOS": "HarmonyOS",
        "signingConfig": "debug"   // 稍后改成你的调试 / 发布配置名
      }
    ]
  }
}

hvigorfile.ts 要挂上 Flutter 插件

import path from 'path'
import { appTasks } from '@ohos/hvigor-ohos-plugin'
import { flutterHvigorPlugin } from 'flutter-hvigor-plugin'

```text
export default {
  system: appTasks,
  plugins: [flutterHvigorPlugin(path.dirname(__dirname))],
}

**本步完成标准:** DevEco 能打开 `ohos/`,工程树完整,暂不要求一定编过。

---

## 第 4 步:配置 `local.properties`

在 `ohos/local.properties` 写本机路径(按你机器改):

```properties
hwsdk.dir=/Users/你的用户名/Library/OpenHarmony/Sdk
sdk.dir=/Users/你的用户名/Library/OpenHarmony/Sdk
flutter.sdk=/Users/你的用户名/path/to/openharmony_tpc/flutter
flutter.versionName=1.0.0
flutter.versionCode=100

说明:

  • flutter.sdk 指向 第 2 步那份 OHOS Fork 根目录(含 bin/flutter 的上一级)
  • versionName / versionCode 建议与 pubspec.yamlx.y.z+code、以及 AppScope/app.json5 三处保持一致

本步完成标准: 路径真实存在,flutter.sdk/bin/flutter --version 可执行。


第 5 步:配置调试签名(否则永远签不出包)

5.1 用 DevEco 生成自动调试签名

  1. DevEco 打开 ohos 工程
  2. FileProject StructureSigning Configs(具体菜单位置随版本略有差异)
  3. 勾选自动签名,登录华为账号,生成调试证书 / Profile
  4. 确认 build-profile.json5 里已经写入一套调试用的 signingConfigs,且 product 的 signingConfig 指向它

5.2 常见坑:找不到 signing/material

现象: SignHap 报 ENOENT,提示找不到 material原因: DevEco 把加密口令材料放在用户目录(如 ~/.ohos/config/material),工程默认路径对不上。 做法: 按 DevEco / 官方文档把 material 拷贝或软链到工程签名目录,或在 Signing Configs 里重新选正确路径。

5.3 先不要碰发布签

此阶段只用 调试签名,目标是:

$OHOS_FLUTTER build hap --debug

能产出已签名 HAP。发布签放到第 10 步。

本步完成标准: DevEco 或 CLI 能完成一次签名构建(哪怕还有其它编译错误,至少签名链路通)。


第 6 步:改造 pubspec.yaml(最长、最易错)

6.1 先认清问题

鸿蒙适配最耗时的不是写页面,而是 插件

  • pub.dev 上很多插件只有 android / ios / web 实现
  • 社区在 gitcode 等托管了 *_ohos 实现
  • 只在 dependencies 里加 foo_ohos 往往不够——解析器仍可能选到无 ohos 实现的主线 foo

正确姿势是两件套:

  1. 需要时声明 *_ohos
  2. dependency_overrides 钉死 foofoo_platform_interface(如有)到 ohos 分支

6.2 推荐写法模板

dependencies:
  flutter:
    sdk: flutter

```text
shared_preferences: ^2.5.4
  shared_preferences_ohos:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/shared_preferences/shared_preferences_ohos
      ref: br_shared_preferences-v2.5.4_ohos   # ref 以社区当前分支为准
path_provider: ^2.1.5
  path_provider_ohos:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_packages.git
      path: packages/path_provider/path_provider_ohos
      ref: br_path_provider-v2.1.5_ohos
permission_handler: ^12.0.1
  permission_handler_ohos:
    git:
      url: https://gitcode.com/CPF-Flutter/flutter_permission_handler.git
      path: permission_handler_ohos
      ref: br_v12.0.1_ohos
# 按你项目实际用到的再加:
  # audioplayers_ohos / open_file_ohos / file_picker_ohos / record ohos 分支 等
dependency_overrides:
  # 主包也要钉到 ohos 分支,否则实现选不中
  shared_preferences:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/shared_preferences/shared_preferences
      ref: br_shared_preferences-v2.5.4_ohos
path_provider:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_packages.git
      path: packages/path_provider/path_provider
      ref: br_path_provider-v2.1.5_ohos
permission_handler:
    git:
      url: https://gitcode.com/CPF-Flutter/flutter_permission_handler.git
      path: permission_handler
      ref: br_v12.0.1_ohos
# 有 platform_interface 的务必一起 override,例如:
  # record + record_platform_interface
  # share_plus + share_plus_platform_interface
  # webview_flutter + webview_flutter_platform_interface
  # audioplayers + audioplayers_platform_interface

**拉取依赖时务必用 OHOS Flutter:**

```bash
$OHOS_FLUTTER pub get

6.3 如何判断还要 override 哪些包

  1. 把你现有 pubspec.yaml 里所有带原生实现的插件列出来
  2. 逐个在 gitcode / openharmony-sig / openharmony-tpc / 社区仓搜索 *_ohosbr_*_ohos
  3. flutter build hap 报缺实现、MissingPluginException、或分析器报平台相关错误时,就对这个包补 override
  4. 主包和 platform_interface 用同一族 ref,不要混版本

6.4 特殊雷:TargetPlatform.ohos 让穷尽 switch 编译失败

Fork Flutter 给枚举多了 TargetPlatform.ohos。第三方若写:

switch (Theme.of(context).platform) {
  case TargetPlatform.android:
  case TargetPlatform.iOS:
  // ...
  case TargetPlatform.linux:
    ...
  // 没有 ohos → 分析期 / 编译期直接挂
}

典型受害者包括某些播放器控件库(如 chewie)。

改法:

  1. vendor(拷贝)该库到 third_party/xxx
  2. 把穷尽 switch 改成 if + 默认分支
  3. dependency_overrides 指到本地 path

示例:

Widget build(BuildContext context) {
  final TargetPlatform platform = Theme.of(context).platform;
  if (platform == TargetPlatform.iOS) {
    return const CupertinoControls(/* ... */);
  }
  if (platform == TargetPlatform.macOS ||
      platform == TargetPlatform.windows ||
      platform == TargetPlatform.linux) {
    return const MaterialDesktopControls();
  }
  // Android / ohos / 其他 → Material
  return const MaterialControls();
}

本步完成标准: $OHOS_FLUTTER pub get 成功;dart analyze 不再因 TargetPlatform.ohos 报未穷尽。


第 7 步:写平台抽象,避免 if (ohos) 散落全项目

建议一开工就建好平台抽象,后面业务适配都走它。

7.1 条件导出(兼容 Web 编译)

// lib/utils/app_platform.dart
export 'app_platform_stub.dart'
    if (dart.library.io) 'app_platform_io.dart';

7.2 IO 实现

// lib/utils/app_platform_io.dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';

```dart
abstract final class AppPlatform {
  AppPlatform._();
static bool get isOhos => Platform.operatingSystem == 'ohos';
  static bool get isWeb => kIsWeb;
  static String get operatingSystem => Platform.operatingSystem;
/// 按你业务真实情况改这些开关
  static bool get supportsFirebase =>
      Platform.isAndroid ||
      Platform.isIOS ||
      Platform.isMacOS ||
      Platform.isWindows;
static bool get supportsLocalNotifications =>
      Platform.isAndroid ||
      Platform.isIOS ||
      Platform.isLinux ||
      Platform.isWindows;
static bool get supportsInAppWebView =>
      Platform.isAndroid ||
      Platform.isIOS ||
      isOhos;
}

### 7.3 Web stub

```dart
// lib/utils/app_platform_stub.dart
import 'package:flutter/foundation.dart';

```dart
abstract final class AppPlatform {
  AppPlatform._();
static bool get isOhos => false;
  static bool get isWeb => kIsWeb;
  static String get operatingSystem => 'web';
  static bool get supportsFirebase => true;
  static bool get supportsLocalNotifications => false;
  static bool get supportsInAppWebView => false;
}

### 7.4 能力矩阵怎么定(先定策略,再写代码)

| 能力 | 鸿蒙怎么处理 | 原因(常见) |
|------|--------------|--------------|
| Firebase | 不初始化 | 官方生态不覆盖 |
| 本地通知 | 业务层直接 return | 插件可能装了但行为不完整 |
| 内嵌 WebView | 可用,走 fork + 适配层 | API 可能滞后主线 |
| 推送 | 换华为推送等 | 不能照搬 FCM |
| 部分离线原生库(如自管 so) | 强制降级在线方案 | so / 引擎未适配 |
| 三方登录 | 评估账号体系;华为推荐 Account Kit | Google / QQ 等不一定可用 |

**初始化通知时写成:**

```dart
Future<void> initializeNotifications() async {
  if (!AppPlatform.supportsLocalNotifications) {
    debugPrint('skip local notifications on ${AppPlatform.operatingSystem}');
    return;
  }
  // Android / iOS 初始化...
}

本步完成标准: 主工程能 import AppPlatform,启动路径上对不支持能力做了 early-return,避免一跑就崩。


第 8 步:第一次真正构建并装到真机

8.1 环境变量

export DEVECO_APP="/Applications/DevEco-Studio.app"
export DEVECO_SDK_HOME="$DEVECO_APP/Contents/sdk"
export OHOS_BASE_SDK_HOME="$HOME/Library/OpenHarmony/Sdk"
export OHOS_FLUTTER="$HOME/path/to/ohos-flutter/bin/flutter"
export PATH="$DEVECO_APP/Contents/tools/hvigor/bin:$DEVECO_APP/Contents/tools/ohpm/bin:$PATH"

cd your_flutter_app
$OHOS_FLUTTER pub get

8.2 构建 debug HAP

$OHOS_FLUTTER build hap --debug

成功后大致在:

build/ohos/hap/entry-default-signed.hap

8.3 安装并启动

hdc list targets
hdc -t <deviceId> install -r build/ohos/hap/entry-default-signed.hap
hdc -t <deviceId> shell aa start -a EntryAbility -b com.example.myapp

EntryAbility / com.example.myapp 换成你 module.json5 / app.json5 里的真实值。

8.4 这一步常见失败怎么拆

失败点先查
找不到 SDK第 1、4 步路径
签名失败第 5 步 Signing Configs / material
插件 MissingPlugin / 原生编译失败第 6 步 overrides
Dart 分析 TargetPlatform.ohos第 6.4 节 vendor 补丁
装上了白屏 / 启动崩第 7 步能力裁剪是否漏了 Firebase 等

本步完成标准: 真机能打开 App 主界面(功能可以不全,但能起来)。


第 9 步:业务适配教程(按优先级做)

主界面起来后,不要并行改十个模块。建议按这个顺序:

9.1 启动链路 / 崩溃点裁剪
9.2 本地存储
9.3 网络与账号登录
9.4 WebView / 文件路径
9.5 支付与深链
9.6 音视频 / TTS / 推送等垂类能力

9.1 启动链路:先让它不崩

排查 main.dart / 启动 Service:

Future<void> bootstrap() async {
  if (AppPlatform.supportsFirebase) {
    await initFirebase();
  }
  if (AppPlatform.supportsLocalNotifications) {
    await initNotifications();
  }
  // 其它各平台都需要的初始化...
}

原则:凡是 Android 上默认会 init、但鸿蒙没有对等服务的 SDK,必须闸住。

9.2 本地存储:给 SP 一层兼容兜底

部分 ohos 实现写入成功后,返回值解析可能抛 RangeError,业务会误判失败。建议统一封装:

Future<bool> setOhosCompatible(
  String key,
  Future<bool> Function() action,
) async {
  try {
    return await action();
  } catch (e) {
    if (AppPlatform.isOhos && e is RangeError) {
      debugPrint('OHOS preferences write quirk, treat as success: $key');
      return true;
    }
    rethrow;
  }
}

```dart
Future<bool> setString(String key, String value) {
  return setOhosCompatible(key, () => prefs.setString(key, value));
}

**路径也不要照搬 Android:**

```dart
Future<Directory> getAppDirectory() async {
  if (AppPlatform.isOhos) {
    final dir = Directory(
      '${Directory.systemTemp.path}${Platform.pathSeparator}my_app_cache',
    );
    if (!dir.existsSync()) {
      await dir.create(recursive: true);
    }
    return dir;
  }
  if (Platform.isIOS) {
    return getApplicationDocumentsDirectory();
  }
  return getApplicationSupportDirectory();
}

9.3 华为账号登录(推荐路径)

鸿蒙上 Google 登录通常不可用;华为侧推荐 Account Kit。典型分层:

  1. ArkTS 插件:调 Account Kit,走 MethodChannel 回传 authCode / idToken
  2. Dart Service:调 Channel,做 state 校验
  3. 登录页:仅 AppPlatform.isOhos 展示华为按钮
  4. 后端:用授权码 / ID Token 换会话

原生注册(极重要): 自研插件不会进 GeneratedPluginRegistrant,必须手动挂:

// entry/src/main/ets/entryability/EntryAbility.ets
import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos'
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'
import HuaweiLoginPlugin from '../plugins/HuaweiLoginPlugin'

```text
export default class EntryAbility extends FlutterAbility {
  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    GeneratedPluginRegistrant.registerWith(flutterEngine)
    flutterEngine.getPlugins()?.add(new HuaweiLoginPlugin())
  }
}

**Dart 侧 Channel 示意:**

```dart
class HuaweiLoginService {
  static const _channel = MethodChannel('com.example.myapp/huawei_login');

```dart
Future<Map<String, String>?> auth() async {
    final state = _randomState();
    final raw = await _channel.invokeMethod<Map<dynamic, dynamic>>(
      'authById',
      {
        'forceLogin': false,
        'state': state,
        'nonce': _randomState(),
        'idTokenSignAlgorithm': 'PS256',
      },
    );
    if (raw == null) return null;
    if ((raw['errorCode'] as int? ?? -1) != 0) return null;
    if (raw['state'] != null && raw['state'] != state) return null;
    return {
      'authorizationCode': '${raw['authCode'] ?? ''}',
      'idToken': '${raw['idToken'] ?? ''}',
      'openId': '${raw['openID'] ?? ''}',
      'unionId': '${raw['unionID'] ?? ''}',
    };
  }
}

**UI:**

```dart
if (AppPlatform.isOhos)
  HuaweiLoginButton(onTap: controller.loginWithHuawei),

还有一个上架级大坑: module.json5 里 Ability metadata 的 client_id,必须等于当前签名 ProfileappIdentifier。调试签和发布签不是同一个 ID。做法见第 10 步。

module.json5 示意:

"metadata": [
  { "name": "client_id", "value": "从当前 p7b Profile 读到的 appIdentifier" },
  { "name": "app_id", "value": "同上" }
]

9.4 WebView:用适配层挡住主线 API 差异

鸿蒙 fork 的 webview_flutter 可能没有 pub.dev 最新 API(例如某些 loadFileWithParams)。业务侧统一封装:

class LocalHtmlLoader {
  static Future<void> loadFile({
    required WebViewController controller,
    required Uri fileUri,
  }) async {
    if (fileUri.scheme != 'file') {
      await controller.loadRequest(fileUri);
      return;
    }
    // 各平台走各自实现;ohos 兜底 loadFile
    await controller.loadFile(fileUri.toFilePath());
  }
}

9.5 支付与深链

  1. module.json5 配好 querySchemes、Ability skills / uri(微信、支付宝等按你接的 SDK 来)
  2. 某支付 SDK 若 ArkTS 路由缺页,可能需要补 stub 才能先编过
  3. 产品上可以先只开已验证闭环的支付(例如先微信),另一条等 SDK 完整再开

9.6 音视频 / TTS / 其它原生库

策略模板:

Future<void> speak(String text) async {
  if (AppPlatform.isOhos) {
    // 不要加载未适配的离线 so
    return speakWithCloudTts(text);
  }
  return speakWithOfflineEngine(text);
}

设置页直接隐藏不可用引擎,避免用户点了崩溃。

本步完成标准: 登录、基础读写、关键业务页在真机可走通;不支持的能力有明确降级,而不是静默失败。


第 10 步:发布签名与上架 .app

10.1 HAP 和 APP 分别干什么

产物命令用途
.hapflutter build hap --release真机安装、内测
.appflutter build app --release应用市场上架

很多人用 release HAP 去交商店——不对。商店要 APP 包。

10.2 申请并配置发布证书

  1. 在 AppGallery Connect 创建应用,包名与 bundleName 一致
  2. 申请 发布 证书与 Profile(注意类型是 release / app_gallery)
  3. 在 DevEco Signing Configs 配一套 release(或你自定义名如 agcRelease
  4. build-profile.json5 的 product.signingConfig 改成这套

建议至少准备两套配置:

名称何时用
debug 签日常真机
release 签打上架包

10.3 切到发布签后,立刻同步华为登录 clientId

切换签名后:

  1. 从当前 Profile(.p7b)读出 appIdentifier
  2. 写回 module.json5client_id / app_id
  3. 如有资源字符串 huawei_client_id,一并改
  4. 后端 OAuth 配置也改成 发布 这一套

否则会出现:调试包华为登录正常,市场上架包所有人登不上

可以把「切签 + 同步 clientId」做成脚本,禁止手改。

10.4 打正式 APP

# 1)确认 signingConfig 已是 release
# 2)确认 client_id 已与 release Profile 对齐
# 3)确认 pubspec / AppScope / local.properties 版本一致

```text
$OHOS_FLUTTER build app --release \
  --build-name=1.0.0 \
  --build-number=100

**成功类似:**

```text
✓ Built build/ohos/app/ohos-default-signed.app

用商店后台上传该 .app。建议在本地再复制一份带版本号的归档,方便回滚对比。

10.5 上架前验收清单

  • 版本号三处一致(pubspec / AppScope / 构建参数)
  • 使用 release 签名打的 .app
  • 华为登录在 release 包上实测通过(debug 通过不算)
  • 微信支付 / 深链跳回在 release 包验证
  • 已降级能力符合产品预期(不是「忘了做」)
  • 隐私合规、权限声明与应用市场资料一致

额外坑位速查(适配时对照)

A. 支付宝等商用 SDK 缺路由实现页

现象: ohpm 依赖装了,但 route 指向的 .ts / 页面不存在,ArkTS 编译失败。 思路: 构建前生成最小 stub 页面,让路由能解析;产品层可先隐藏未闭环支付。

B. 鸿蒙依赖「回流」弄坏 Android

例如接入某社交 SDK 后,Android Manifest Merger 因 usesCleartextTraffic 冲突失败。解法:

<application
    android:usesCleartextTraffic="false"
    tools:replace="android:usesCleartextTraffic">

教训: 为鸿蒙加的依赖也会影响其它平台,要做全矩阵回归。

C. 插件进了 Registrant,业务却不能当作可用

GeneratedPluginRegistrant 注册了通知插件 ≠ 鸿蒙通知已可用。以你在 Dart 写的 supports* 为准做产品承诺。

D. 长期双分支合并成本

如果主开发在 dev、鸿蒙在长期适配分支,记得:

  • 定期合并主线,否则三个月后功能差一截
  • 合并后重点回归:登录、支付、WebView、存储、插件注册代码是否被冲掉

推荐你照着执行的「最小成功路径」

把目标拆成三个里程碑,不要一天求全:

里程碑 A:环境通

  1. DevEco + SDK 目录约定完成
  2. OHOS Flutter 能 build hap
  3. ohos/ 工程能被 DevEco 打开
  4. 调试签名可用

里程碑 B:包能装

  1. pubspec overrides 让工程编过
  2. AppPlatform + 启动裁剪,真机不秒崩
  3. build hap --debug 安装启动成功

里程碑 C:能上架

  1. 关键业务在鸿蒙可闭环(至少登录 + 主功能)
  2. release 签名 + clientId 对齐
  3. build app --release 产出 .app 并过商店校验

结语

Flutter 适配 HarmonyOS,难点不在「多写几个 Widget」,而在于:

  1. 换工具链(Fork Flutter + DevEco + SDK 目录)
  2. 重建插件供应链(整链 dependency_overrides
  3. 重定义能力边界(什么支持、什么降级)
  4. 上架签名与账号体系对齐(尤其是华为登录 clientId)
按本文 **第 1 步 → 第 10 步** 顺序推进,每步都有「完成标准」,会少走很多回头路。
若卡在某一步:先确认是 **环境 / 签名 / 依赖 / 业务裁剪** 哪一类,再对症查,不要在错误层级上反复改业务代码。

祝你顺利打出第一份可上架的鸿蒙包。