Flutter 三方库 app_tracking_transparency 的鸿蒙适配教程

本文配套仓库:https://atomgit.com/oh-flutter/app_tracking_transparency(TAG:2.0.7-ohos-1.0.0-beta.1,分支:feat/ohos_app_tracking_transparency_2.0.7)。本文解决的是另一件事:从上游 GitHub 仓库开始,把 app_tracking_transparency 完整适配到 OpenHarmony / HarmonyOS 平台,并在模拟器上验证。

app_tracking_transparency 是 GitHub 上 deniza 开发的一个 Flutter 插件(MIT 协议,2.0.7),封装了 iOS 14+ 的 ATT(App Tracking Transparency)能力:查询跟踪授权状态、弹出系统跟踪授权弹窗、读取广告标识符(IDFA)。它原本只有 iOS 平台实现——Android 上没有 ATT 概念,接口由 Dart 侧直接返回 notSupported 或空字符串;唯独没有鸿蒙。而鸿蒙 Flutter 应用如果有一套跨平台的合规采集逻辑,需要一个语义对齐的鸿蒙实现,让同一份调用代码在三个平台上都能跑通。本文完整走一遍社区三方库适配的标准流程:把上游仓库同步到 AtomGit,拉到宿主机,建适配分支,用命令自动补全 ohos 目录,补全 Dart 与 ArkTS 两侧实现,补齐适配说明文件后提交分支与 TAG,最后用仓库自带的 example 在鸿蒙模拟器上验证。

一、环境搭建

鸿蒙 Flutter 开发环境(ohos 版 SDK、DevEco Studio、签名配置)的完整搭建步骤,官方指南已经写得很细,直接照做即可:

Flutter OHOS 开发环境搭建指南

适配工作比单纯使用多一项要求:终端里 flutter 命令必须指向 ohos 版 SDK,因为后文自动补全 ohos 目录靠的是它提供的 flutter create --platforms ohos 能力。环境装好后用 flutter devices 确认能识别鸿蒙设备(真机或模拟器均可)。本文实测使用的环境:

版本
Flutter(ohos 版)3.41.10-ohos-1.0.1
编译 SDK5.1.0(18)
实测设备HarmonyOS Emulator 模拟器(HarmonyOS 7.0.0.105 / API 26)

二、适配过程

2.1 将上游仓库同步到 AtomGit

鸿蒙 Flutter 三方库社区(oh-flutter 组织)托管在 AtomGit,上游项目在 GitHub,适配的第一步是把上游代码完整迁入 AtomGit 上为它新建的目标仓库 oh-flutter/app_tracking_transparency。做法是把上游克隆下来,添加 AtomGit 远端后整库推送,保留全部 commit 历史与 TAG,后续上游发新版时也能用同样的方式增量同步:

# 克隆上游仓库,目录名与目标仓库保持一致
git clone https://github.com/deniza/app_tracking_transparency.git app_tracking_transparency
cd app_tracking_transparency

# 关联 AtomGit 目标仓库
git remote add atomgit https://atomgit.com/oh-flutter/app_tracking_transparency.git

# 推送全部分支与 TAG
git push atomgit --all
git push atomgit --tags

推送完成后,打开 AtomGit 上目标仓库的页面,能看到与上游一致的提交历史和源码目录:

在这里插入图片描述

图一:同步完成后 AtomGit 目标仓库的代码页

2.2 拉取代码到宿主机

从目标仓库把代码拉到本地,后续所有操作都在这份代码上进行:

git clone https://atomgit.com/oh-flutter/app_tracking_transparency.git
cd app_tracking_transparency

此时的目录是上游的原始结构。app_tracking_transparency 是标准的单包插件,所有平台实现与 Dart 接口都在仓库根目录:

app_tracking_transparency/
├── example/                # 上游自带示例工程
├── images/                 # README 配图
├── ios/                    # iOS 平台实现(ATT 授权弹窗 + IDFA)
├── lib/
│   └── app_tracking_transparency.dart   # Dart 接口:TrackingStatus 枚举与三个静态接口
├── test/
├── CHANGELOG.md
├── LICENSE                 # MIT
├── README.md
└── pubspec.yaml

注意看:这个库比常见插件更"瘦"——目录里只有 ios/,连 android/ 都没有,pubspec 的插件注册节点里也只有 iOS 一个平台。Android 上之所以接口还能调用,是 Dart 侧用 defaultTargetPlatform 判断后直接返回 notSupported 或空字符串兜底的。所以本次适配要做的不只是"把 iOS 的原生实现翻译成 ArkTS",而是按社区适配约定,为鸿蒙定义一套与 Android 对齐的返回语义。这一点在 2.4 节展开。

在这里插入图片描述

图二:clone 完成后的仓库原始目录

2.3 创建适配分支并补全 ohos 目录结构

先建适配分支。社区约定分支名为 feat/ohos_<库名>_<版本号>,版本号取自上游 pubspec 里的 version: 2.0.7

git checkout -b feat/ohos_app_tracking_transparency_2.0.7

接下来一条命令补全 ohos 目录。flutter create --platforms ohos . 是 ohos 版 Flutter SDK 提供的能力:读取当前目录 pubspec 的包名与插件声明,自动生成鸿蒙平台目录并追加注册节点:

flutter create --platforms ohos .

命令执行后,仓库里多出一个 ohos/ 目录,pubspec.yaml 也被自动改了两处。生成的目录结构(剔除构建产物)如下:

ohos/
├── src/main/ets/components/plugin/
│   └── AppTrackingTransparencyPlugin.ets   # 插件模板:ArkTS 实现入口,此刻是空的
├── src/main/module.json5             # 模块配置
├── BuildProfile.ets                  # 构建时生成的版本信息
├── index.ets                         # 插件导出入口
├── oh-package.json5                  # 包描述:name / version / main / 对引擎 HAR 的依赖
├── build-profile.json5               # 构建配置
└── hvigorfile.ts                     # hvigor 构建脚本

各文件的职责:index.ets 把插件类导出给引擎侧;oh-package.json5 声明这是一个依赖 @ohos/flutter_ohos(引擎 HAR 包)的 ArkTS 包;module.json5 是模块清单;AppTrackingTransparencyPlugin.ets 是 flutter create 生成的插件模板,只有空壳生命周期方法,真正要写的代码全在这个文件里,2.4 节补全它。pubspec.yaml 自动追加的注册节点:

flutter:
  plugin:
    platforms:
      ios:
        pluginClass: AppTrackingTransparencyPlugin
      ohos:                                 # flutter create 自动新增
        package: com.example.app_tracking_transparency
        pluginClass: AppTrackingTransparencyPlugin

flutter create 还会把 Dart SDK 约束放宽一档(上游 >=2.12.0 <3.0.0,生成后为 >=2.12.0 <4.0.0),以兼容当前 ohos 版 SDK 的 Dart 3.11。

在这里插入图片描述

图三:命令输出与生成的 ohos 目录

2.4 在插件文件中补全 ohos 实现

先看改动全景。整个适配在插件侧只改了一个 Dart 文件、一个 pubspec 注册节点,外加新增的 ohos 目录:

文件改动内容
lib/app_tracking_transparency.dart加法新增 Platform 导入、_isOhos 判断、两个失败语义封装、三个接口的鸿蒙分支
pubspec.yamlflutter create 自动改追加 ohos 注册节点,放宽 SDK 约束
ohos/(新增目录)加法ArkTS 插件 AppTrackingTransparencyPlugin.ets:三个方法各自返回约定常量

原有 iOS 平台实现一行不动,全部改动都是加法。

Dart 侧。核心是在三个公开接口入口处加鸿蒙分支,并抽两个私有封装统一失败语义:

import 'dart:io' show Platform;

/// 鸿蒙平台判据:鸿蒙 Flutter 引擎的 Platform.operatingSystem 返回 'ohos'
static bool get _isOhos =>
    !kIsWeb && Platform.operatingSystem == 'ohos';

/// 鸿蒙侧统一封装:失败语义与原平台对齐(状态类接口失败返回 notSupported)
static Future<TrackingStatus> _invokeOhosStatus(String method) async {
  try {
    final int status =
        (await _channel.invokeMethod<int>(method))!;
    return TrackingStatus.values[status];
  } on PlatformException {
    return TrackingStatus.notSupported;
  }
}

/// 鸿蒙侧统一封装:失败语义与 Android 对齐(广告标识符失败返回空串)
static Future<String> _invokeOhosIdentifier(String method) async {
  try {
    return (await _channel.invokeMethod<String>(method))!;
  } on PlatformException {
    return "";
  }
}

三个接口的鸿蒙分支——通道与 iOS 完全复用,只是方法调用的目的地从 iOS 原生代码换成了 ArkTS 插件:

static Future<TrackingStatus> get trackingAuthorizationStatus async {
  if (_isOhos) {
    return _invokeOhosStatus('getTrackingAuthorizationStatus');
  }
  // ...原有 iOS 分支保持不变...
}

static Future<TrackingStatus> requestTrackingAuthorization() async {
  if (_isOhos) {
    return _invokeOhosStatus('requestTrackingAuthorization');
  }
  // ...
}

static Future<String> getAdvertisingIdentifier() async {
  if (_isOhos) {
    return _invokeOhosIdentifier('getAdvertisingIdentifier');
  }
  // ...
}

三个实现决策说明:

  1. 为什么用 Platform.operatingSystem == 'ohos' 判断:鸿蒙 Flutter 引擎在 Platform.operatingSystem 上返回字符串 'ohos',比依赖 defaultTargetPlatform 枚举更稳(后者在部分引擎版本上没有 ohos 取值);配合 !kIsWeb 排除 Web 端。
  2. 为什么通道与方法名与 iOS 完全一致app_tracking_transparency 通道和三个方法名是上游既定契约,ArkTS 插件按同名通道注册即可对接,Dart 侧不需要新通道,跨平台调用方代码零改动。
  3. 为什么 catch 后返回 notSupported / 空字符串:状态类接口失败返回 notSupported 与 iOS 失败路径语义一致;标识符失败返回空串与 Android 的"无标识符"语义一致。即使 ArkTS 侧异常,调用方拿到的也是可预期的返回值,而不是未捕获异常。

ArkTS 侧。flutter create 生成的模板只有空壳,补全后的完整实现:

import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';

export default class AppTrackingTransparencyPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  getUniqueClassName(): string {
    return "AppTrackingTransparencyPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "app_tracking_transparency");
    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 {
    switch (call.method) {
      case 'getTrackingAuthorizationStatus':
        // HarmonyOS has no ATT (App Tracking Transparency) concept.
        // Per the community adaptation convention, return a fixed
        // authorized value: TrackingStatus.authorized is index 3
        // of the Dart enum in lib/app_tracking_transparency.dart.
        result.success(3);
        break;
      case 'requestTrackingAuthorization':
        // No system dialog is shown; return authorized immediately.
        result.success(3);
        break;
      case 'getAdvertisingIdentifier':
        // Aligned with the Android semantics: no advertising
        // identifier is available, return an empty string.
        result.success("");
        break;
      default:
        result.notImplemented()
    }
  }
}

关键点说明:

  • 生命周期两接口onAttachedToEngine 用引擎给的 BinaryMessenger 注册同名通道并挂上 handler;onDetachedFromEngine 反注册并置空,避免引擎销毁后悬挂回调。
  • 三个方法各拿什么getTrackingAuthorizationStatusrequestTrackingAuthorization 返回 3——这是 Dart 侧 TrackingStatus 枚举里 authorized 的下标,ArkTS 侧回传整数、Dart 侧 TrackingStatus.values[3] 还原,两侧按枚举下标对齐;getAdvertisingIdentifier 返回空字符串,与 Android 语义一致。
  • 为什么直接同步回值、不调系统能力:鸿蒙没有 ATT 授权弹窗与 IDFA 等价概念,按社区适配约定,状态固定返回 authorized(调用方跨平台逻辑无需写分支),标识符返回空串。没有异步系统调用,也就天然满足 MethodResult 只许回复一次的约束。
  • default 分支必须 notImplemented:未识别的方法名回 notImplemented,Dart 侧会抛 MissingPluginException 语义的异常而不是永久悬挂 Future。

2.5 补全适配说明文件并提交分支

适配完成的仓库还需要四份说明文件,让社区与使用方了解这个鸿蒙版的来龙去脉:

文件作用
README.OpenSource开源登记:名称、协议、版本、上游地址、维护者(社区入库必查)
README.OpenHarmony.md面向社区的英文适配说明
README.OpenHarmony_CN.md面向社区的中文适配说明
CHANGELOG.OpenHarmony.md鸿蒙适配版本的变更记录

README.OpenSource 实际内容(JSON 数组格式):

[
  {
    "Name": "app_tracking_transparency",
    "License": "MIT License",
    "License File": "LICENSE",
    "Version Number": "2.0.7",
    "Owner": "qiaomu8559968@126.com",
    "Upstream URL": "https://github.com/deniza/app_tracking_transparency",
    "Description": "A Flutter plugin to display the iOS tracking authorization dialog and request permission to collect data. This repository adds OpenHarmony platform support on top of the original library."
  }
]

CHANGELOG.OpenHarmony.md 实际内容:

# 变更记录

## [2.0.7-ohos-1.0.0-beta.1]
- 适配 OpenHarmony 平台,新增 ohos 目录与 Dart 侧平台分支
- 依赖 ohos 版 Flutter SDK 3.41.10-ohos-1.0.1
- 模拟器验证环境:HarmonyOS Emulator(HarmonyOS 7.0.0.105 / API 26)

提交适配 commit 并打 TAG。TAG 命名规则是 原库版本-ohos-适配版本-beta.x,原库版本 2.0.7,首个鸿蒙适配版本即 2.0.7-ohos-1.0.0-beta.1

git add .
git commit -m "feat: adapt app_tracking_transparency for the OpenHarmony platform"
git push atomgit feat/ohos_app_tracking_transparency_2.0.7

# 打 TAG 并推送
git tag 2.0.7-ohos-1.0.0-beta.1
git push atomgit 2.0.7-ohos-1.0.0-beta.1

在这里插入图片描述
在这里插入图片描述

图四:适配 commit 与 TAG 的提交记录

三、在 Demo 中验证适配效果

3.1 使用仓库自带的 example

上游自带的 example 是最合适的验证载体,改造方式是把示例页重做成三接口演示页:三个操作卡片分别触发 trackingAuthorizationStatusrequestTrackingAuthorization()getAdvertisingIdentifier(),页面下方用调用日志区实时打印每次调用的返回值,另加一张平台说明卡标注鸿蒙侧语义。演示页改造单独成 commit:

git commit -m "feat: rework example app as a dedicated app_tracking_transparency demo for OpenHarmony"

构建与安装(签名配置的实踩见 4.1 的 Q2):

cd example
flutter pub get
flutter build hap --debug

# 产物在 example/ohos/entry/build/default/outputs/default/ 下
hdc install -r entry-default-signed.hap

# bundleName 见 example/ohos/AppScope/app.json5
hdc shell aa start -a EntryAbility -b com.he2apps.app_tracking_transparency_example

应用启动后的主界面——initState 里自动查询了一次授权状态,日志区已经能看到第一条返回:

在这里插入图片描述

图五:example 在鸿蒙模拟器上的主界面

3.2 自建工程时以 AtomGit 链接方式引入

不改示例、直接在自己的工程里用时,pubspec 用 git 依赖并锁定 TAG:

dependencies:
  app_tracking_transparency:
    git:
      url: https://atomgit.com/oh-flutter/app_tracking_transparency.git
      ref: 2.0.7-ohos-1.0.0-beta.1

然后 flutter pub get 即可。TAG 与框架版本对照:

TAG2.0.7-ohos-1.0.0-beta.1
分支feat/ohos_app_tracking_transparency_2.0.7
实测框架Flutter 3.41.10-ohos-1.0.1(stable)

兼容性说明:本文在 stable(3.41.10-ohos-1.0.1)实测通过;canary 引擎对宿主工程 compatibleSdkVersion 有额外要求,使用时以社区环境指南为准。

3.3 调用接口并观察模拟器运行效果

最小调用代码就是三个静态接口,逐一在模拟器上验证。

接口一:查询授权状态。点击"查询授权状态"卡片:

final TrackingStatus status =
    await AppTrackingTransparency.trackingAuthorizationStatus;

日志区打印 getTrackingAuthorizationStatus → authorized (index 3),返回的是枚举 authorized

在这里插入图片描述

图六:查询授权状态返回 authorized(index 3)

接口二:请求跟踪授权。点击"请求跟踪授权"卡片:

final TrackingStatus status =
    await AppTrackingTransparency.requestTrackingAuthorization();

日志区打印 requestTrackingAuthorization → authorized (index 3)。与 iOS 不同,鸿蒙上没有系统弹窗,调用立即返回授权结果——这正是适配约定的语义:调用方的后续逻辑(授权通过才采集)不需要写平台分支:

在这里插入图片描述

图七:请求授权无弹窗、直接返回 authorized(index 3)

接口三:获取广告标识符。点击"获取广告标识符"卡片:

final String id = await AppTrackingTransparency.getAdvertisingIdentifier();

日志区打印 getAdvertisingIdentifier → "<空字符串>"。与 Android 一致,鸿蒙上没有 IDFA 等价物,返回空字符串:

在这里插入图片描述

图八:获取广告标识符返回空字符串

适配完成度总结:三个接口在鸿蒙上全部全链路可用;授权状态与请求授权两个接口固定返回 authorized(受生态制约,鸿蒙无 ATT 概念,但语义正确且跨平台逻辑无需分支);广告标识符返回空字符串,与 Android 各平台行为一致。所有返回值都有明确定义,调用方可以按统一逻辑处理。

四、常见问题

4.1 适配过程中的问题

Q1:flutter create --platforms ohos . 之后跑 flutter analyze 报一堆 sdk_version_sincesuper-parameters

这是上游 example 的 SDK 约束太老导致的:example/pubspec.yaml 里是 sdk: ">=2.12.0 <3.0.0",而 ohos 版 SDK 的 Dart 3.11 analyzer 会按 3.x 语言版本做检查,老约束下新语法告警全冒出来。把 example 的约束升到 >=3.0.0 <4.0.0 后 analyze 即通过。主库的约束 flutter create 已自动放宽为 >=2.12.0 <4.0.0,无需手动改。

Q2:flutter build hap 提示"请通过 DevEco Studio 打开 ohos 工程后配置调试签名"?

构建签名 HAP 需要证书与 Profile。实踩步骤:先拿设备 UDID——hdc shell bm get --udid,注意输出带 udidofcurrentdeviceis: 前缀,要清洗后再用;然后到 ~/.ohos/config/ 下找 DevEco 自动签名生成的证书组(default_<项目名>_<hash>.cer/.p12/.p7b),Profile(p7b)里绑定了 bundleName 与设备 UDID,选匹配的那组;最后把签名配置临时注入 example/ohos/build-profile.json5(加密后的 storePassword/keyPassword 串在同一台机器上跨项目可用),或直接用 DevEco Studio 打开 example/ohos 工程走 File > Project Structure 的自动签名。签名配置不入库,提交前记得还原 build-profile.json5 与 app.json5。

Q3:运行时报 MissingPluginException,怎么排查?

三处按序检查:pubspec.yaml 的 plugin.platforms 下有没有 ohos 节点(没有就重跑 flutter create --platforms ohos .);ArkTS 侧注册的通道名与 Dart 侧 MethodChannel('app_tracking_transparency') 是否完全一致;改完有没有重新 flutter pub get 并重新构建 HAP(热重载不会重装插件)。

Q4:ArkTS 侧 MethodResult 有什么使用禁忌?

每个 onMethodCall 对每个调用只允许 result.success/failure/notImplemented 之一执行一次,重复回复会崩溃。本库三个方法都是同步返回常量,天然满足;如果适配的是有异步回调的系统能力(如订阅类接口),要加"只回一次"的标志位保护。

4.2 使用过程中的问题

Q1:鸿蒙上为什么永远返回 authorized,也不弹授权弹窗?

ATT(App Tracking Transparency)是 iOS 14+ 的隐私机制,鸿蒙没有对应概念。按社区适配约定,鸿蒙侧固定返回 authorized(枚举下标 3),且不弹任何弹窗。这样"先请求授权、通过后才采集"的跨平台代码在鸿蒙上可以直接走通,不需要 Platform.isIOS 分支。

Q2:鸿蒙上 getAdvertisingIdentifier() 为什么返回空字符串?

与 Android 的语义对齐:Android 上该接口本来就返回空字符串(上游 Dart 侧兜底),鸿蒙侧遵循同样约定。如果业务在鸿蒙上确实需要广告标识符(OAID),应接入鸿蒙系统广告服务相关接口,那不在本库职责范围内。

Q3:Android 上调用是什么行为?会不会和鸿蒙不一致?

一致。Android 上状态类接口返回 notSupported、标识符返回空字符串;鸿蒙上状态类接口返回 authorized(比 Android 的 notSupported 更"可用",这是刻意的语义选择,让合规采集逻辑在鸿蒙可用)、标识符返回空字符串。跨平台判断请以 TrackingStatus 枚举值而非平台判断来分流。

五、结语

回顾整个链路:同步上游到 AtomGit、clone 到宿主机、建分支并 flutter create --platforms ohos 补全目录、Dart 与 ArkTS 两侧补实现、补齐四份说明文件、提交分支与 TAG,最后在 example 里逐接口验证。插件使用中发现接口行为问题,请到鸿蒙仓库 Issue反馈(管适配层);原库本身的逻辑问题请到上游仓库 Issue反馈。接口用法与业务实战见姊妹篇《给鸿蒙 App 增加跟踪授权与广告标识符查询能力 —— app_tracking_transparency 的鸿蒙使用指南》

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:

Logo

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

更多推荐