Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 让 launch_app_store 打开 AppGallery

这篇文章记录一个很适合新手理解的 Flutter 鸿蒙适配过程:原插件的目标是打开 Google Play 或 Apple App Store,而鸿蒙设备没有这两个商店。我们不把 Android 的 Intent 代码硬搬到 ArkTS,而是把同一个 Dart launch() API 映射为鸿蒙的应用市场搜索入口。

本文使用 launch_app_store 2.0.0、Flutter OH 3.47.4-ohos-1.0.4 和 HarmonyOS API 26,从代码分析、平台注册、Ability 获取、Want 跳转、HAP 构建、签名、安装到真机回退,完整走一遍。

一、最终效果

图 1:真机启动适配后的 Demo。

图 2:点击按钮后进入 AppGallery 相关页面,说明鸿蒙 Ability 跳转已经生效。

图 3:从应用市场返回 Flutter 页面,Dart 层显示“调用完成”。

验证点真机结果
Flutter3.47.4-ohos-1.0.4
HarmonyOS7.0.0.105,API 26
HAPsigned HAP 构建通过
点击调用Flutter 页面调用 LaunchAppStore.launch()
系统跳转进入 AppGallery 相关页面
返回应用Flutter 页面显示调用完成

二、先理解原插件做什么

原库的公开 API 只有一个核心入口:

await LaunchAppStore.launch(
  androidAppId: 'com.jbferreira.kural',
  iOSAppId: '585027354',
);

参数含义:

参数原平台用途鸿蒙处理
androidAppIdGoogle Play 包名作为 AppGallery 搜索关键词
iOSAppIdApp Store 数字 ID鸿蒙端不使用
writeReview是否打开评论入口鸿蒙端不使用
showToastAndroid/iOS 提示鸿蒙端不使用
toastMessage提示文字鸿蒙端不使用

适配时最重要的判断是:鸿蒙不能保证 Android 包名一定对应 AppGallery 详情页。因此本次使用 AppGallery 搜索地址,把包名作为关键词传过去。这样不会伪造一个不存在的详情链接,用户仍然可以在应用市场中继续查找。

三、准备 Flutter 3.47 和鸿蒙设备

本文实际环境:

工具版本
Flutter OH3.47.4-ohos-1.0.4
Dart3.13.3
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试设备127.0.0.1:5555,HarmonyOS 7.0.0.105

没有 Flutter 鸿蒙环境时,先看 Flutter OH 环境搭建指南。

export PATH=/Users/von/Developer/flutter-oh-3.47.4/bin:$PATH
flutter --version
hdc list targets

确认版本和设备后再继续。公开文章截图中不要出现完整设备序列号、证书路径和密码。

四、拉取代码和核对适配分支

本文使用的 AtomGit 信息:

执行:

git clone https://atomgit.com/oh-flutter/launch_app_store.git
cd launch_app_store
git switch feat/ohos_launch_app_store_2.0.0
git log -1 --oneline

这里要特别注意仓库目录名和 Dart 包名都是 launch_app_store,但包的上游主页仍然指向 launch_review。写文章时要把“包名、仓库名、上游地址”分别写清楚,避免读者搜索不到代码。

五、先分析原来的调用链

上游 Dart 代码使用固定通道:

static const MethodChannel _channel =
    MethodChannel('launch_app_store');

调用时发送:

await _channel.invokeMethod('launch', {
  'android_id': androidAppId,
  'ios_id': iOSAppId,
  'write_review': writeReview,
  'is_ios_beta': isiOSBeta,
  'toast_message': toastMessage,
  'show_toast': showToast,
});

原 Android 实现会根据包名拼接 Google Play 地址,iOS 实现会拼接 App Store 地址。鸿蒙适配不能继续调用这两个商店,所以保留通道名和方法名,只替换接收端行为:

Flutter LaunchAppStore.launch()
  -> MethodChannel('launch_app_store')
  -> LaunchAppStorePlugin.onMethodCall()
  -> UIAbilityContext.startAbility()
  -> AppGallery 搜索页

这样业务代码不用因为平台变化而分成三套调用方式。

六、声明 OHOS 插件平台

打开 pubspec.yaml,在 Android 和 iOS 平台旁边增加:

flutter:
  plugin:
    platforms:
      android:
        package: com.jbferreira.launchappstore
        pluginClass: LaunchAppStorePlugin
      ohos:
        pluginClass: LaunchAppStorePlugin
      ios:
        pluginClass: LaunchAppStorePlugin

pluginClass 对应:

ohos/src/main/ets/components/plugin/LaunchAppStorePlugin.ets

入口文件:

import LaunchAppStorePlugin from './src/main/ets/components/plugin/LaunchAppStorePlugin';

export default LaunchAppStorePlugin;

到这一步只完成了“让 Flutter 知道有 OHOS 插件”。真正的系统跳转还没有实现。

七、为什么需要 AbilityAware

Flutter 插件的 MethodChannel 可以收到 Dart 调用,但 startAbility() 需要当前前台 UIAbility 的上下文。插件不能直接使用一个全局 context,否则页面切换后可能还拿着旧 Ability。

ArkTS 类增加 AbilityAware:

export default class LaunchAppStorePlugin
  implements FlutterPlugin, MethodCallHandler, AbilityAware {
  private ability: UIAbility | null = null;

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.ability = binding.getAbility();
  }

  onDetachedFromAbility(): void {
    this.ability = null;
  }
}

调用时先检查:

if (this.ability === null) {
  result.error(
    'NO_ABILITY',
    'A foreground UIAbility is required.',
    null,
  );
  return;
}

这一步对新手很重要:如果不做生命周期判断,首次 Demo 可能能跑,页面重建、后台返回或多引擎场景就容易出现空指针。

八、把包名转换为 AppGallery 搜索地址

鸿蒙端接收到 Dart 参数后读取 android_id:

const packageName = call.argument('android_id') as string | undefined;
const keyword = packageName && packageName.length > 0
  ? packageName
  : '应用市场';

const uri =
  `https://appgallery.huawei.com/search?keyword=${encodeURIComponent(keyword)}`;

这里没有把 Android 包名直接拼成一个假详情页,而是放进搜索参数。原因有两个:

  1. 同一个包名不一定在 AppGallery 上架。
  2. 不同地区的应用市场页面和详情 URL 可能不同。

最后使用当前 Ability 的 context 打开 URI:

try {
  await this.ability.context.startAbility({ uri: uri });
  result.success(null);
} catch (error) {
  result.error('START_ABILITY_FAILED', `${error}`, null);
}

如果设备没有可处理该 URI 的 Ability,Dart 层会收到 PlatformException,Demo 会把错误码和错误信息显示出来。

九、准备新手 Demo

Demo 页面只保留一个按钮,避免把原 Android/iOS 的无关参数全部堆到页面上:

Future<void> _launch() async {
  try {
    await LaunchAppStore.launch(
      androidAppId: 'com.jbferreira.kural',
    );
    if (!mounted) return;
    setState(() {
      _status = '调用完成';
      _detail = '平台已处理应用市场入口';
    });
  } on PlatformException catch (error) {
    if (!mounted) return;
    setState(() {
      _status = error.code;
      _detail = error.message ?? '当前平台没有对应能力';
    });
  }
}

这个 Demo 的判断标准不是“按钮点击后页面一定停留在 Flutter”。打开系统应用市场后,Flutter Activity 可能退到后台;返回 Flutter 后再检查页面状态,才能证明调用链完整结束。

十、构建和签名

先进行无签名构建:

flutter pub get
flutter analyze
cd example
flutter test
flutter build hap --debug --no-codesign

需要安装真机时,在 DevEco Studio 中打开 example/ohos,进入 File -> Project Structure -> Signing Configs,勾选自动生成调试签名:

flutter build hap --debug

安装:

hdc list targets
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo

本文没有把签名证书和密码提交到仓库。读者如果使用自己的包名,需要同步修改 AppScope/app.json5 中的 bundleName,并重新生成签名。

十一、真机验证步骤

启动 Demo 后按顺序操作:

  1. 确认首页显示“尚未调用”。
  2. 点击“尝试打开应用市场”。
  3. 确认系统进入 AppGallery 相关页面。
  4. 按返回键回到 Flutter 页面。
  5. 确认页面显示“调用完成”。

真机结果:AppGallery UIExtension 进入前台,返回 Flutter 后页面显示“调用完成”。这证明 startAbility() 成功处理了 URI,Dart Future 也完成了返回。

如果设备没有可用网络,AppGallery 页面可能只显示空白或加载状态,但这不影响对“是否调用了 Ability”的判断。文章中应该把“Ability 启动成功”和“应用市场网络内容加载成功”分开记录。

十二、常见问题

Q1:点击后没有离开 Flutter 页面

先检查设备是否在线,以及 aa dump -a 中是否出现应用市场相关 Ability。再确认 URI 是 https://,不要把一个空字符串传给 startAbility()。

Q2:为什么不直接打开 Android 应用详情页

因为 Android 包名不保证在 AppGallery 有对应详情页。搜索页可以让用户继续查找,适配结果更可靠。

Q3:为什么回到 Flutter 后状态显示“调用完成”

startAbility() 返回成功只代表系统接受了 URI 并启动了目标 Ability,不代表目标页面内的网络内容一定加载完成。Demo 的“调用完成”只描述系统跳转成功。

Q4:为什么需要 AbilityAware

插件生命周期可能早于或晚于 UIAbility。保存并清理当前 Ability,可以避免页面切换后使用失效 context。

Q5:构建提示兼容 SDK 不足

Flutter 3.47 的 @ohos/flutter_ohos 依赖要求较高兼容版本。把 example/ohos/build-profile.json5 的 compatibleSdkVersion 和 targetSdkVersion 对齐到 API 26,再重新构建。

十三、复现命令

git clone https://atomgit.com/oh-flutter/launch_app_store.git
cd launch_app_store
git switch feat/ohos_launch_app_store_2.0.0
export PATH=/Users/von/Developer/flutter-oh-3.47.4/bin:$PATH
flutter pub get
flutter analyze
cd example
flutter test
flutter build hap --debug --no-codesign

十四、总结

这次适配改动了三层:pubspec.yaml 注册 OHOS,Dart 保持原有 launch() 通道,ArkTS 使用当前 UIAbility 的 context 调用 startAbility()。真正需要新手关注的是适配过程中的语义选择:Android 包名不能直接当成鸿蒙详情页 URL,应该转换为 AppGallery 搜索关键词;系统跳转成功也不能等同于网络页面加载成功。

十五、参考资料与仓库地址

本文适配仓库

上游和包信息

Flutter 和鸿蒙资料

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐