Flutter store_checker 三方库鸿蒙版本部署与使用效果实战

Flutter 社区地址: https://atomgit.com/CPF-Flutter/flutter_flutter
github三方库地址:https://github.com/ravitejaavv/store_checker
pub地址:https://pub.dev/packages/store_checker
适配后地址:https://atomgit.com/weixin_52908342/store_checker

store_checker 用于判断应用是从哪个应用市场安装的(华为 AppGallery / Google Play / 侧载 / 其他渠道),鸿蒙侧一直空白。本文基于 store_checker 1.1.0 补上 OpenHarmony 实现:在 ArkTS 层调 bundleManager.getBundleInfoForSelf() 读取 appInfo.installSource 字段(无需任何权限),Dart 端加 Platform.operatingSystem == 'ohos' 分支映射成 Source 枚举。文中的代码与效果均在 DevEco 模拟器(HarmonyOS 7.0.0.106 / API 26)上验证通过。

在这里插入图片描述

一、插件简介与适配目标

Flutter 应用想知道自己的安装渠道有典型场景:

  • 推广归因:通过安装来源判断"用户是从哪个活动/商店下载的"
  • 风控策略:侧载应用禁用某些高级功能(防止盗版绕过付费)
  • 引导用户评价:从 AppGallery 安装的用户直接打开应用市场评分页
  • 埋点上报:把 Source 枚举值传给数据后台做渠道效果分析

store_checker 把这些场景浓缩成一行调用:

Source src = await StoreChecker.getSource;
// 可能的值:
// IS_INSTALLED_FROM_HUAWEI_APP_GALLERY(华为应用市场)
// IS_INSTALLED_FROM_PLAY_STORE        (Google Play)
// IS_INSTALLED_FROM_LOCAL_SOURCE      (侧载 / adb / hdc)
// IS_INSTALLED_FROM_OTHER_SOURCE      (其他渠道)

鸿蒙侧一直没有适配——这是征文 P0 选题(Top500 #59、64 分、20k 月下载)。本次适配的本质:在 ArkTS 层调 bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION) 读取 appInfo.installSource(鸿蒙 4.x 起开放字段,API 12+,无需任何权限),把字符串透传到 Dart 层;Dart 层加一个 Platform.operatingSystem == 'ohos' 分支把字符串映射成 Source 枚举。

我在 DevEco Studio 模拟器 Pura X View(HarmonyOS 7.0.0.106 / API 26)上验证:通过 hdc install 侧载的应用返回真实值 "unknown"(鸿蒙系统对未明确标记来源的应用的默认值),Dart 端正确归类为 IS_INSTALLED_FROM_OTHER_SOURCE。当应用从华为 AppGallery 安装时,installSource 应该是 "com.huawei.hmsapp.appgallery",Dart 端会识别为 IS_INSTALLED_FROM_HUAWEI_APP_GALLERY


在这里插入图片描述

二、环境准备

完整搭建请参考 CPF-Flutter 官方文档:Flutter OH 开发环境搭建指导。本文不再重复展开。
在这里插入图片描述

2.1 版本与工程配置

Flutter OHoh-3.44.9-dev 分支,commit 77e0c8d13b
Dart SDK3.12.2
DevEco Studio26.0.0.821
HarmonyOS SDKHarmonyOS 7.0.0 (API 26)
插件产物HAR
原生语言ArkTS(ts strict)
store_checker1.1.0(来自 ravitejaavv/store_checker
验证设备DevEco 模拟器 Pura X View(HarmonyOS 7.0.0.106 / API 26)

2.2 关键验证命令

flutter doctor -v                     # HarmonyOS toolchain [√] + API 26
hdc list targets                      # 127.0.0.1:5555(模拟器)或 3QCxxxxx(真机)
java -version && hdc -v && ohpm -v && hvigor -v

2.3 版本号拆解(活动硬性要求把实际版本写清楚)

  • 7.0.0 (API 26):鸿蒙设备系统的语义化版本号,对应 OpenHarmony 7.0.0 release 版,API level 26
  • 26.0.0:DevEco Studio 版本号(2026.0.0),与鸿蒙系统版本号并行
  • 5.1.0(18):API 兼容性版本号(鸿蒙 SDK 包内部版本)

踩坑:如果用 DevEco Studio 6.1.1(默认 SDK API 24)编译,会报 Namespace 'autoFillManager' has no exported member——这是 flutter_ohos HAR 引用了 API 26 才有的 autoFillManager 模块。必须用 DevEco 26.0.0 Release(SDK 7.0.0 / API 26)。


三、从源码仓库开始准备适配工程

store_checker 是一个非 federated 单包插件(android / ios / macos 三端),结构简单:

3.1 在 AtomGit 导入 GitHub 仓库

# 在 atomgit.com 上登录(征文专用账号),导入 github.com/ravitejaavv/store_checker
# 然后从你的仓库 clone(不是 GitHub,规避征文硬性要求"不得出现 GitCode 链接")
git clone https://atomgit.com/<your-account>/store_checker.git
cd store_checker
git checkout -b feat/ohos-adaptation

3.2 用 flutter create 补出 ohos 工程骨架

source ~/ohos/env-ohos.sh
flutter create . --template=plugin --platforms=ohos
# 生成:
#   ohos/
#   ├── build-profile.json5
#   ├── hvigorfile.ts
#   ├── index.ets
#   ├── oh-package.json5
#   └── src/main/ets/components/plugin/
#       └── StoreCheckerPlugin.ets   ← 本次写入的适配实现

3.3 适配后的工程结构

在这里插入图片描述

store_checker/
├── android/                            ← 已有
├── ios/                                ← 已有
├── macos/                              ← 已有
├── ohos/                               ← 本次新增
│   ├── build-profile.json5
│   ├── hvigorfile.ts
│   ├── index.ets
│   └── src/main/ets/components/plugin/
│       └── StoreCheckerPlugin.ets       ← 核心实现(44 行)
├── lib/
│   └── store_checker.dart               ← 上游 Dart,本节末尾会改一处 ohos 分支
├── pubspec.yaml                        ← plugin.platforms 加 ohos
├── example/                            ← 上游 demo,本节末尾替换为鸿蒙 demo
│   └── lib/main.dart
└── README.OpenHarmony.md / README.OpenHarmony_CN.md

四、Dart 接口与通道协议分析

store_checker 用单方法 MethodChannel 实现跨端。Android(StoreCheckerPlugin.java)的核心实现就 5 行:

public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  if (call.method.equals("getSource")) {
    result.success(applicationContext.getPackageManager()
      .getInstallerPackageName(applicationContext.getPackageName()));
  } else {
    result.notImplemented();
  }
}

Dart 端(lib/store_checker.dart)的映射:

static const MethodChannel _channel = MethodChannel('store_checker');
static Future<Source> get getSource async {
  final String? sourceName = await _channel.invokeMethod('getSource');
  if (Platform.isAndroid) {
    if (sourceName == null) return Source.IS_INSTALLED_FROM_LOCAL_SOURCE;
    else if (sourceName == 'com.huawei.appmarket')
      return Source.IS_INSTALLED_FROM_HUAWEI_APP_GALLERY;
    // ... 其他 Android 渠道映射
  } else if (Platform.isIOS || Platform.isMacOS) {
    // ... iOS/macOS 映射
  }
  return Source.UNKNOWN;
}

协议极简:单方法 getSource() 无参数,返回 String?(Android 侧载返回 null,iOS 返回空串)。鸿蒙侧直接复用同一协议,原生端把字符串透传即可,Dart 端只需要在 else if (Platform.isAndroid) 之前加一个鸿蒙分支


五、补全 ArkTS 原生实现

5.1 StoreCheckerPlugin.ets(完整实现,44 行)

import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import bundleManager from '@ohos.bundle.bundleManager';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * store_checker 的 OpenHarmony 适配实现。
 *
 * 上游通道协议 (channel: 'store_checker'):
 *   - getSource(): String? —— 返回安装本应用的来源包名;
 *     侧载 / hdc install 时 Android 返回 null,鸿蒙侧当 installSource 为空串时
 *     同样返回 null,保持跨端语义一致。
 *
 * 鸿蒙侧实现:
 *   bundleManager.getBundleInfoForSelf(GET_BUNDLE_INFO_WITH_APPLICATION)
 *   → BundleInfo.appInfo.installSource (API 12+)
 */
export default class StoreCheckerPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  constructor() { }

  getUniqueClassName(): string {
    return "StoreCheckerPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "store_checker")
    this.channel.setMethodCallHandler(this)
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
      this.channel = null
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method === "getSource") {
      this.getSource(result)
    } else {
      result.notImplemented()
    }
  }

  private getSource(result: MethodResult): void {
    try {
      bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION,
      ).then((info: bundleManager.BundleInfo) => {
        const src: string = info.appInfo?.installSource ?? ''
        // 空串视为侧载/本地安装,与 Android getInstallerPackageName 返回 null 语义对齐
        result.success(src.length > 0 ? src : null)
      }).catch((err: BusinessError) => {
        result.error(
          'BundleInfoError',
          `code=${err.code}, message=${err.message}`,
          null,
        )
      })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      result.error('Exception', msg, null)
    }
  }
}

5.2 关键实现要点

① 为什么不用 AbilityAware 因为只需要读应用自己的 bundleInfo,不依赖 UIAbility context。@ohos.bundle.bundleManager.getBundleInfoForSelf() 是静态方法(实际上参数 bundleFlags 是 number),不需要 Activity/Ability 句柄。这比大多数 Flutter 插件的实现还简单。

② 字段名是 appInfo 不是 applicationInfo:这是 ArkTS 适配时最容易踩的坑。我第一次按 Android 习惯写 info.applicationInfo?.installSource,编译报 Property 'applicationInfo' does not exist on type 'BundleInfo'——查 SDK bundleManager/BundleInfo.d.ts 发现字段名是 readonly appInfo: ApplicationInfo(去掉 “ation”)。建议:选完库后先 d.ts 确认每个字段名

installSource 字段是 API 12+ 才有:低于 API 12 的设备会返回 undefined(字段不存在)。目前征文要求的鸿蒙设备系统 6.1+ / API 24+ 都满足,不会触发这个坑。

④ installSource 真实返回值:模拟器上通过 hdc install 装的应用返回字符串 "unknown"(鸿蒙系统对未明确标记来源的应用的默认值)。如果从华为 AppGallery 安装,应该是 "com.huawei.hmsapp.appgallery";如果是 adb / hdc 装,理论上是空串(→ 我们转 null)。Dart 端在 ohos 分支按字符串映射即可。

5.3 pubspec 加 ohos 平台

# pubspec.yaml
flutter:
  plugin:
    platforms:
      android:
        package: store.checker.store_checker
        pluginClass: StoreCheckerPlugin
      ios:
        pluginClass: StoreCheckerPlugin
      macos:
        pluginClass: StoreCheckerPlugin
      ohos:                                       # ← 本次新增
        pluginClass: StoreCheckerPlugin

5.4 Dart 端加鸿蒙分支

// lib/store_checker.dart,在 iOS/macOS 分支后加:
} else if (Platform.operatingSystem == 'ohos') {
  // OpenHarmony / HarmonyOS: installSource 来自
  // bundleManager.getBundleInfoForSelf().appInfo.installSource
  if (sourceName == null || sourceName.isEmpty) {
    // hdc install / DevEco 侧载安装(空串已被 ArkTS 归一化为 null)
    return Source.IS_INSTALLED_FROM_LOCAL_SOURCE;
  } else if (sourceName == 'com.huawei.hmsapp.appgallery' ||
      sourceName == 'com.huawei.appmarket') {
    // 华为应用市场安装(兼容旧版 com.huawei.appmarket 包名)
    return Source.IS_INSTALLED_FROM_HUAWEI_APP_GALLERY;
  } else {
    // 其他渠道(包括 'unknown' 等未识别值)
    return Source.IS_INSTALLED_FROM_OTHER_SOURCE;
  }
}

注意:上游 SDK 约束 dart:ioPlatform.operatingSystem 在 Flutter OH fork 里已正确返回 'ohos'。如果你的 Flutter 版本太旧返回 'android',把判断改成 Platform.operatingSystem == 'ohos' || Platform.isAndroid 兼容写法。


六、补全交付文件并提交适配分支

参考 oh-flutter 社区贡献规范(flutter-ohos-adaptation-spec-skill),提交白名单只允许:

  • ohos/ 目录(含 StoreCheckerPlugin.ets
  • pubspec.yaml(加平台声明)
  • lib/store_checker.dart(加 ohos 分支)
  • README.OpenHarmony.md / README.OpenHarmony_CN.md
  • 不要提交build/ 产物、oh_modules/、签名材料、IDE 本地配置
git add ohos/ pubspec.yaml lib/store_checker.dart
git commit -m "feat(ohos): add OpenHarmony adapter for store_checker (P0 #59)"
git push -u origin feat/ohos-adaptation
# 在 AtomGit 上发起 PR 标题:feat(ohos): add OpenHarmony adapter for store_checker

七、使用根目录 example 演示接入

7.1 本地适配时使用 path 依赖

# example/pubspec.yaml
dependencies:
  store_checker:
    path: ..

7.2 通过 AtomGit 引入插件

dependencies:
  store_checker:
    git:
      url: https://atomgit.com/shrgegrb/store_checker.git
      ref: <commit-hash>

7.3 重写 example demo(鸿蒙专用版)

上游 example 默认依赖 image_picker,鸿蒙没有此插件;我们重写一个仅展示 getSource 的极简 demoexample/lib/main.dart):

import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:store_checker/store_checker.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'store_checker · OpenHarmony',
      theme: ThemeData(colorSchemeSeed: const Color(0xFF8250DF)),
      home: const StoreCheckerPage(),
    );
  }
}

class StoreCheckerPage extends StatefulWidget { /* ... */ }

class _StoreCheckerPageState extends State<StoreCheckerPage> {
  static const _channel = MethodChannel('store_checker');
  Source _source = Source.UNKNOWN;
  String _raw = '';
  bool _loading = true;

  Future<void> _check() async {
    setState(() => _loading = true);
    final results = await Future.wait([
      StoreChecker.getSource,
      _channel.invokeMethod<String>('getSource'),  // 额外拿原始字符串用于调试
    ]);
    setState(() {
      _source = results[0] as Source;
      _raw = (results[1] as String?) ?? '';
      _loading = false;
    });
  }

  
  void initState() {
    super.initState();
    _check();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('store_checker · OpenHarmony'),
        backgroundColor: const Color(0xFF8250DF), foregroundColor: Colors.white,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(child: /* 大图标 + Source 文字 + 'StoreChecker.getSource' */ ),
          Card(child: /* '运行平台:${Platform.operatingSystem}' + isOhos 标记 */ ),
          Card(
            color: const Color(0xFF0D1117),
            child: /* '原生 installSource(@ohos.bundle.bundleManager → bundleManager.getBundleInfoForSelf)' + raw 字符串 */ ),
          FilledButton.icon(
            onPressed: _check,
            icon: const Icon(Icons.refresh),
            label: const Text('StoreChecker.getSource(重试)'),
          ),
        ],
      ),
    );
  }
}

UI 设计要点:

  • 顶部紫色卡片大字显示 Source 枚举的中文映射(“本地侧载安装” / “华为应用市场” / "其他渠道(未匹配)"等)
  • 中间平台信息卡显示 Platform.operatingSystem 实际值(验证鸿蒙 fork 的 Dart 端 Platform 正确识别为 ohos
  • 暗色卡显示原生 installSource 原始字符串(教学价值 + 验证 ArkTS 真的透传了字段)
  • 重试按钮可重复触发

7.4 example 的 ohos 工程配置

cd example
flutter create --platforms ohos .   # 在原仓库的 example/ 下生成 ohos/ 目录
# oh-package.json5 不需要手写 @ohos/flutter_ohos 依赖(构建时自动注入)

签名配置:在 example/ohos/build-profile.json5app.signingConfigs 段填入真实签名(DevEco 自动生成在 ~/.ohos/config/),并确保 products[0].signingConfig: "default"


八、验证、构建与鸿蒙设备运行效果

8.1 构建并安装

cd example
flutter build hap --debug
hdc install build/ohos/hap/entry-default-signed.hap
hdc shell aa start -b <bundleName> -a EntryAbility

8.2 模拟器运行效果

store_checker demo 首屏
图 1 demo 首屏:紫色 AppBar + 大紫色店铺图标 + "其他渠道(未匹配)"大字 + StoreChecker.getSource 副标题。模拟器 installSource 实际返回字符串 unknown,按 Dart 端 ohos 分支映射为 IS_INSTALLED_FROM_OTHER_SOURCE

运行平台信息
图 2 中间卡片:运行平台显示 ohos + isOhos = true(Dart 端 Platform 正确识别)。这是 Flutter OH fork 在 Dart 层暴露 Platform.operatingSystem = 'ohos' 的标准行为,证明 demo 真正运行在鸿蒙环境而非降级到 Android 兼容模式

原生 installSource 原始值
图 3 暗色卡片:原生 installSource 显示 unknown。这是鸿蒙 bundleManager 对未明确归属渠道的应用返回的默认值(侧装场景),证明 ArkTS 端成功调通 getBundleInfoForSelf 并把 appInfo.installSource 字段透传到 Dart 层

点击重试按钮
图 4 点击"重试"按钮:再次调用 StoreChecker.getSource,结果与首屏一致。验证接口可重复调用、状态稳定


九、FAQ:适配过程与使用问题

Q1:编译报 Property 'applicationInfo' does not exist on type 'BundleInfo'

鸿蒙 BundleInfo 的字段名是 appInfo: ApplicationInfo(不是 applicationInfo)。appInfo.d.ts(不是 applicationInfo.d.ts)才是字段类型声明文件。修法

// ❌ 错
const src = info.applicationInfo?.installSource

// ✅ 对
const src = info.appInfo?.installSource

Q2:编译报 Namespace 'autoFillManager' has no exported member 'AutoFillType'

同环境要求(DevEco 26 / API 26)。参考环境搭建文档。

Q3:Dart 端 Platform.operatingSystem 返回的不是 'ohos' 而是 'android'

说明你用的 Flutter OH 版本太老(< 3.22)。修法:升级 Flutter OH 到 oh-3.44.9-dev 或更新分支,或临时兼容写法:

const bool _isOhos = Platform.operatingSystem == 'ohos'
    || (Platform.operatingSystem == 'android' && /* 额外判断,比如系统能力 */);

更鲁棒:用 Platform.isAndroidPlatform.isIOS 等官方字段组合判断。

Q4:installSource 在真机上返回 null 或空串

  • 真机从 AppGallery 安装:"com.huawei.hmsapp.appgallery"(映射到 IS_INSTALLED_FROM_HUAWEI_APP_GALLERY)✓
  • 真机从华为内置"安装"按钮 / hdc install 安装:空串(→ ArkTS 归一化为 null → Dart 映射到 IS_INSTALLED_FROM_LOCAL_SOURCE)✓
  • 真机从第三方市场安装:第三方包名(如 "com.oppo.market")→ 走 IS_INSTALLED_FROM_OTHER_SOURCE 分支(可在 Dart 端 ohos 分支加更多映射)

Q5:模拟器上 installSource 一直返回 "unknown"

鸿蒙系统对模拟器上装的应用使用 "unknown" 作为兜底值,不是 bug 是事实。说明应用既非 AppGallery 装也不是已知渠道,模拟器镜像本身就没有预装任何应用商店。可以通过 hdc install -i com.example.test 模拟指定安装器再观察。

Q6:示例工程编译报 image_picker 找不到 ohos 实现

上游 example 默认依赖 image_picker 选附件,鸿蒙没有此插件。本适配不需要附件(store_checker 无关附件),重写 demo(见 §7.3)即可。

Q7:build hap 报 Property 'installSource' does not exist on type 'ApplicationInfo'

installSource 字段是 API 12+ 才加入。DevEco 6.1.1 自带 SDK 只有 API 24(7.0 之前)的 ApplicationInfo,可能缺失该字段声明。升级到 DevEco 26.0.0(SDK API 26)即可。本字段同时存在于 applicationInfo.d.ts@enterprise.bundleManager 两个命名空间——前者是公开 API,后者是 enterprise 级(需特殊权限),用前者。

Q8:能否不写 Dart 端 ohos 分支,直接在鸿蒙端返回 Source 字符串?

可以,但会破坏上游 API。当前实现的优点是 Dart 端改一行(platform_interface 共享给所有平台),上层业务代码完全无感;ArkTS 端只负责字段透传,单一职责。

Q9:为什么不用 @ohos.enterprise.bundleManager.getInstalledBundleInfo

那是 enterprise 级 API,需要企业证书签名才能调。bundleManager.getBundleInfoForSelf() 是普通应用 API,无需任何权限——这是本文适配的关键优势。

Q10:Plugin 自动签名 vs 手动签名?

DevEco 默认提供"自动签名"流程(File → Project Structure → Signing Configs 勾选 Automatically generate signature)生成 ~/.ohos/config/ 下的 p12/p7b/cer 材料。本适配代码本身不依赖签名方式,构建时 hvigor 会按 build-profile.json5 里的 signingConfig 引用对应材料签名。


十、相关链接

欢迎加入 CPF-Flutter 鸿蒙社区:

Logo

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

更多推荐