序号 | 原仓 | 版本 | 适配仓 | 文章解读 |
---|---|---|---|---|
1 | https://pub.dev/packages/fluttertoast | 8.2.12 | https://gitcode.com/nutpi/FlutterToast | https://www.nutpi.net/thread?topicId=1575 |
2 | https://pub.dev/packages/flutter_udid | 4.0.0 | https://gitcode.com/nutpi/flutter_udid | https://www.nutpi.net/thread?topicId=1574 |
3 | https://pub.dev/packages/flutter_exit_app | 1.1.4 | https://gitcode.com/nutpi/flutter_exit_app | https://www.nutpi.net/thread?topicId=1577 |
4 | https://pub.dev/packages/flutter_phone_direct_caller | 2.2.1 | https://gitcode.com/nutpi/flutter-phone-direct-caller | |
5 | https://pub.dev/packages/flutter_native_contact_picker | 0.0.10 | https://gitcode.com/nutpi/flutter_native_contact_picker | https://www.nutpi.net/thread?topicId=1578 |
6 | https://pub.dev/packages/torch_light | https://gitcode.com/nutpi/flutter_torch_light | https://www.nutpi.net/thread?topicId=1579 |
在数字化浪潮的推动下,跨平台开发框架如 Flutter 凭借其高效、便捷的特性,成为了开发者们的宠儿。而鸿蒙系统的崛起,更是为跨平台开发注入了新的活力。为了助力开发者在鸿蒙生态中快速实现 Flutter_udid 获取设备标识符功能,本文将深入浅出地为大家解析如何适配 Flutter_udid 三方库至鸿蒙平台。
开放匿名设备标识符(Open Anonymous Device Identifier, OAID,以下简称 OAID):是一种非永久性设备标识符,基于开放匿名设备标识符,OAID 是基于华为自有算法生成的 32 位类 UUID(Universally Unique Identifier)标识符,格式为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。
OAID 会在下述场景中发生变化:
在模块的 module.json5 文件中,申请广告跟踪权限ohos.permission.APP_TRACKING_CONSENT[1],该权限为 user_grant 权限,当申请的权限为 user_grant 权限时,reason,abilities 标签必填,配置方式参见requestPermissions 标签说明[2],示例代码如下所示:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.APP_TRACKING_CONSENT",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryFormAbility"
],
"when": "inuse"
}
}
]
}
}
应用在需要获取 OAID 信息时,应通过调用 requestPermissionsFromUser 接口获取对应权限。注意:其中 context 的获取方式参见各类 Context 的获取方式[3]。示例代码如下所示:
import { identifier } from '@kit.AdsKit';
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
function requestOAIDTrackingConsentPermissions(context: common.Context): void {
// 进入页面时,向用户请求授权广告跨应用关联访问权限
const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
try {
atManager.requestPermissionsFromUser(context, ["ohos.permission.APP_TRACKING_CONSENT"]).then((data) => {
if (data.authResults[0] === 0) {
hilog.info(0x0000, 'testTag', '%{public}s', 'succeeded in requesting permission');
identifier.getOAID((err: BusinessError, data: string) => {
if (err.code) {
hilog.error(0x0000, 'testTag', '%{public}s', `get oaid failed, error: ${err.code}${err.message}`);
} else {
const oaid: string = data;
hilog.info(0x0000, 'testTag', '%{public}s', `succeeded in getting oaid by callback , oaid: ${oaid}`);
}
});
} else {
hilog.error(0x0000, 'testTag', '%{public}s', 'user rejected');
}
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', '%{public}s', `request permission failed, error: ${err.code}${err.message}`);
})
} catch (err) {
hilog.error(0x0000, 'testTag', '%{public}s', `catch err->${err.code}, ${err.message}`);
}
}
今天我们一起来看一下如何适配与使用这个三方库
flutter_udid
是一个 Flutter 插件,用于在 iOS、Android、Mac、Windows 和 Linux 平台上检索跨应用重新安装的持久性唯一设备标识符 (UDID)。它的主要特点是提供一种方法来生成和存储设备的唯一标识符,即使在卸载应用后,仍然能保持一致。今天我们会适配支持鸿蒙。
适配 OpenHarmony 平台的详细使用指导可以参考:Flutter 使用指导文档[4]
在项目中使用该插件库时,只需在 pubspec.yaml
文件的 dependencies
中新增如下配置:
dependencies:
flutter_udid:
git:
url: "https://gitcode.com/nutpi/flutter_udid.git"
path: ""
然后在项目根目录运行 flutter pub get
,即可完成依赖添加
接下来是具体的适配过程。
确保已经配置好了 Flutter 开发环境,具体可参考 Flutter 配置指南[5]。同时,从 官方插件库[6] 下载待适配的三方插件。本指导书, 以适配 fflutter_udid4.0.0[7] 为例
下载并解压插件后,我们会看到以下目录结构:
在插件目录下,打开 Terminal,执行以下命令来创建一个鸿蒙平台的 Flutter 模块:
flutter create . --template=plugin --platforms=ohos
步骤:
flutter create . --template=plugin --platforms=ohos
创建一个 ohos 平台的 flutter 模块。在项目根目录的 pubspec.yaml
文件中,添加鸿蒙平台的相关配置:
name: flutter_udid
description:PlugintoretrieveapersistentUDIDacrossreinstallsoniOSandAndroid
version:4.0.0
homepage:https://github.com/GigaDroid/flutter_udid
environment:
sdk:">=3.0.0 <4.0.0"
flutter:">=3.10.0"
dependencies:
flutter:
sdk:flutter
crypto:^3.0.0
dev_dependencies:
flutter_test:
sdk:flutter
flutter_lints:^4.0.0
flutter:
plugin:
platforms:
android:
package:de.gigadroid.flutter_udid
pluginClass:FlutterUdidPlugin
ios:
pluginClass:FlutterUdidPlugin
macos:
pluginClass:FlutterUdidPlugin
windows:
pluginClass:FlutterUdidPluginCApi
linux:
pluginClass:FlutterUdidPlugin
ohos:
pluginClass:FlutterUdidPlugin
使用 DevEco Studio 打开鸿蒙项目。
在 ohos
目录内的 oh-package.json5
文件中添加 libs/flutter.har
依赖,并创建 .gitignore
文件,添加以下内容以忽略 libs
目录:
/node_modules
/oh_modules
/local.properties
/.preview
/.idea
/build
/libs
/.cxx
/.test
*.har
/BuildProfile.ets
/oh-package-lock.json5
oh-package.json5
文件内容如下:
{
"name": "flutter_udid",
"version": "1.0.0",
"description": "Plugin to retrieve a persistent UDID accross reinstalls on HarmonyOS and OpenHarmony",
"main": "index.ets",
"author": "nutpi",
"license": "Apache-2.0",
"dependencies": {
"@ohos/flutter_ohos": "file:./libs/flutter.har"
},
"modelVersion": "5.0.4"
}
在 ohos
目录下创建 index.ets
文件,导出配置:
import FlutterUdidPlugin from './src/main/ets/de/gigadroid/flutter_udid/FlutterUdidPlugin'
export default FlutterUdidPlugin
文件结构和代码逻辑可以参考安卓或 iOS 的实现,鸿蒙的 API 文档可以参考 :https://gitcode.com/openharmony-sig/flutter_packages/tree/master/packages/path_provider/path_provider_android
ohos 的 api 可以参考:https://gitcode.com/openharmony/docs
以下是 FlutterUdidPlugin.ets
文件的代码示例:
import {
MethodCallHandler,
FlutterPlugin,
BinaryMessenger,
FlutterPluginBinding,
AbilityPluginBinding,
MethodCall,
MethodChannel,
MethodResult,
Log
} from'@ohos/flutter_ohos';
import { common } from'@kit.AbilityKit';
import { identifier } from'@kit.AdsKit';
import { UIAbility } from'@kit.AbilityKit';
import { requestPermissions } from'./RequestPermissions';
const TAG: string = "FlutterUdidPlugin";
exportdefaultclass FlutterUdidPlugin implements MethodCallHandler, FlutterPlugin {
private channel: MethodChannel | null = null
private applicationContext: common.Context | null = null
private ability: UIAbility | null = null;
constructor(context?: common.Context) {
if (context) {
this.applicationContext = context;
}
}
static registerWith(): void {
}
getUniqueClassName(): string {
return TAG;
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.onAttachedToEngine1(binding.getApplicationContext(), binding.getBinaryMessenger());
}
private onAttachedToEngine1(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationContext = applicationContext;
this.channel = new MethodChannel(messenger, "flutter_udid")
this.channel.setMethodCallHandler(this)
}
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.ability = binding.getAbility()
}
onDetachedFromAbility(): void {
this.ability = null;
}
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method == "getUDID") {
this.requestPermissions().then((data: boolean) => {
this.getUDID().then((udid: string | null) => {
if (udid == null || udid == "") {
result.error("UNAVAILABLE", "UDID not available.", null)
} else {
result.success(udid)
}
})
})
} else {
result.notImplemented()
}
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.applicationContext = null;
this.channel?.setMethodCallHandler(null)
}
private async getUDID(): Promise<string | null> {
try {
returnawait identifier.getOAID()
} catch (err) {
returnnull
}
}
private async requestPermissions(): Promise<boolean> {
if (!this.ability) {
Log.i(TAG, "Could not launch BarcodeScanner because the plugin is not attached to any ability")
returnfalse
}
try {
const results = await requestPermissions(this.ability!.context)
return results ? results : false
} catch (e) {
}
returnfalse
}
}
以下是 requestPermissions.ets
文件的代码示例:
import bundleManager from'@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from'@ohos.abilityAccessCtrl';
import { BusinessError } from'@kit.BasicServicesKit';
exportconst appTrackingConsentPermission: Array<Permissions> = ['ohos.permission.APP_TRACKING_CONSENT'];
asyncfunction checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
exportasyncfunction checkPermissions(permissions: Array<Permissions>): Promise<boolean> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
exportasyncfunction requestPermissions(
context: Context
) {
const hasAppTrackingConsentPermission: boolean = await checkPermissions(appTrackingConsentPermission);
if (!hasAppTrackingConsentPermission) {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
try {
let data = await atManager.requestPermissionsFromUser(context, appTrackingConsentPermission)
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] !== 0) {
// 用户拒绝授权
returnfalse;
}
}
// 用户授权,可以继续访问目标操作
returntrue;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
returnfalse;
}
} else {
// Permissions already exist. Call the callback with success.
returntrue;
}
}
在 ohos
目录下创建 index.ets
文件,导出 FlutterToastPlugin
:
import FlutterUdidPlugin from './src/main/ets/de/gigadroid/flutter_udid/FlutterUdidPlugin'
export default FlutterUdidPlugin
在最开始使用
flutter create . --template=plugin --platforms=ohos
创建模版的时候,也会配套创建 Example
使用 Deveco Studio
打开 example > ohos
目录,单击 File > Project Structure > Project > Signing Configs
,勾选 Automatically generate signature
,等待自动签名完成。然后运行以下命令:
flutter pub get
flutter build hap --debug
如果应用正常启动,说明插件适配成功。如果没有,欢迎大家联系坚果派一起支持。
通过以上步骤,我们成功地将 Flutterudid 三方库适配到了鸿蒙平台。这个过程涉及到了解插件的基本信息、配置开发环境、创建鸿蒙模块、编写原生代码以及测试验证等多个环节。希望这篇博客能够帮助到需要进行 FlutterToast 鸿蒙适配的开发者们,让大家在鸿蒙生态的开发中更加得心应手。
坚果派由坚果等人创建,团队拥有若干华为 HDE,以及若干其他领域的三十余位万粉博主运营。专注于分享的技术包括 HarmonyOS/OpenHarmony,ArkUI-X,元服务,服务卡片,华为自研语言,BlueOS 操作系统、团队成员聚集在北京、上海、广州、深圳、南京、杭州、苏州、宁夏等地。 聚焦“鸿蒙原生应用”、“智能物联”和“AI 赋能”、“人工智能”四大业务领域,依托华为开发者专家等强大的技术团队,以及涵盖需求、开发、测试、运维于一体的综合服务体系,赋能文旅、媒体、社交、家居、消费电子等行业客户,满足社区客户数字化升级转型的需求,帮助客户实现价值提升。 目前上架鸿蒙原生应用 18 款,三方库 72 个。
地址:
本文档旨在详细说明在一个 Flutter 插件中,鸿蒙(HarmonyOS)原生侧如何处理获取 OAID(开放匿名设备标识符)时所需的应用跟踪权限(ohos.permission.APP_TRACKING_CONSENT
)。我们将基于 FlutterUdidPlugin.ets
和 RequestPermissions.ets
这两个关键文件进行分析。
在许多场景下,应用需要一个设备标识符来进行数据分析、广告投放等。在鸿蒙系统中,OAID 是推荐使用的非永久性、可重置的设备标识符。然而,获取 OAID 需要用户明确授予“应用跟踪”权限。
我们的 Flutter 插件 flutter_udid
旨在提供一个跨平台的接口来获取设备标识符。在鸿蒙侧,这意味着我们需要:
ohos.permission.APP_TRACKING_CONSENT
权限。RequestPermissions.ets
RequestPermissions.ets
文件封装了检查和请求 ohos.permission.APP_TRACKING_CONSENT
权限的逻辑。其核心功能分为两部分:检查权限和请求权限。
checkPermissions
和 checkAccessToken
)checkPermissions(permissions: Array<Permissions>): Promise<boolean>
: 这是暴露给外部调用的函数,用于检查传入的权限列表(在此场景下主要是 appTrackingConsentPermission
)是否已被授予。checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus>
: 这是实际执行权限检查的内部函数。它执行以下步骤:abilityAccessCtrl.createAtManager()
创建权限管理实例。bundleManager.getBundleInfoForSelf()
获取当前应用的 tokenId
(accessTokenId)。这是检查特定应用权限所必需的。atManager.checkAccessToken(tokenId, permission)
来检查指定 tokenId
的应用是否已被授予 permission
。GrantStatus
)。checkPermissions
函数最终会比较 checkAccessToken
返回的状态是否为 PERMISSION_GRANTED
,并返回一个布尔值。// RequestPermissions.ets (简化示例)
import bundleManager from'@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from'@ohos.abilityAccessCtrl';
exportconst appTrackingConsentPermission: Array<Permissions> = ['ohos.permission.APP_TRACKING_CONSENT'];
asyncfunction checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
// ... 获取 tokenId
// ... 调用 atManager.checkAccessToken(tokenId, permission)
// ... 返回 grantStatus
}
exportasyncfunction checkPermissions(permissions: Array<Permissions>): Promise<boolean> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
requestPermissions
)requestPermissions(context: Context): Promise<boolean>
: 这是主要的权限请求函数。abilityAccessCtrl.AtManager
实例。atManager.requestPermissionsFromUser(context, appTrackingConsentPermission)
。这个方法会触发系统弹窗,请求用户授权。requestPermissionsFromUser
返回一个包含授权结果(authResults
)的对象。这是一个数字数组,0
代表授予,非 0
代表拒绝。authResults
,如果所有请求的权限都被授予(即所有结果都是 0
),则返回 true
;否则返回 false
。BusinessError
。checkPermissions
检查是否已获得 appTrackingConsentPermission
。true
。// RequestPermissions.ets (简化示例)
exportasyncfunction requestPermissions(
context: Context
): Promise<boolean> {
const hasPermission: boolean = await checkPermissions(appTrackingConsentPermission);
if (!hasPermission) {
let atManager = abilityAccessCtrl.createAtManager();
try {
let data = await atManager.requestPermissionsFromUser(context, appTrackingConsentPermission);
// ... 检查 data.authResults ...
// ... 返回 true 或 false
} catch (error) {
// ... 处理错误,返回 false
}
} else {
returntrue;
}
}
FlutterUdidPlugin.ets
FlutterUdidPlugin.ets
是 Flutter 与鸿蒙原生代码交互的桥梁。它通过 MethodChannel
接收来自 Flutter 端的调用。
当 Flutter 端调用 getUDID
方法时,onMethodCall
函数会被触发:
this.requestPermissions()
。注意,这个 requestPermissions
是 FlutterUdidPlugin
类内部的方法,它会进一步调用我们之前分析的 RequestPermissions.ets
中的 requestPermissions
函数。FlutterUdidPlugin
内部的 requestPermissions
方法需要确保 this.ability
(当前的 UIAbility 实例) 不为 null
,因为请求权限需要一个 Context
,通常从 Ability
获取。RequestPermissions.ets
中的 requestPermissions(this.ability!.context)
。this.requestPermissions()
返回一个 Promise,其结果是一个布尔值 data
,表示权限是否被授予。.then((data: boolean) => { ... })
),无论权限是否成功授予(虽然逻辑上应该只在授予后获取,但当前代码结构是在 then 回调中直接调用 getUDID
),都会调用 this.getUDID()
。getUDID()
方法使用 @kit.AdsKit
中的 identifier.getOAID()
来异步获取 OAID。.then((udid: string | null) => { ... })
):udid
为 null
或空字符串,通过 result.error()
返回错误给 Flutter 端。udid
,通过 result.success(udid)
将 OAID 返回给 Flutter 端。// FlutterUdidPlugin.ets (简化示例)
import { requestPermissions as requestAppPermissions } from'./RequestPermissions';
import { identifier } from'@kit.AdsKit';
exportdefaultclass FlutterUdidPlugin implements MethodCallHandler, FlutterPlugin {
// ... 其他成员和方法 ...
private ability: UIAbility | null = null;
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method == "getUDID") {
this.requestPermissions().then((granted: boolean) => {
// 注意:原始代码在这里没有显式检查 granted 的值
// 理想情况下,应该检查 granted 是否为 true
// if (granted) { ... } else { result.error(...) }
this.getUDID().then((udid: string | null) => {
if (udid) {
result.success(udid);
} else {
result.error("UNAVAILABLE", "UDID not available.", null);
}
});
});
} else {
result.notImplemented();
}
}
privateasync getUDID(): Promise<string | null> {
try {
returnawait identifier.getOAID();
} catch (err) {
returnnull;
}
}
privateasync requestPermissions(): Promise<boolean> {
if (!this.ability) {
returnfalse;
}
try {
// 调用 RequestPermissions.ets 中的函数
returnawait requestAppPermissions(this.ability!.context);
} catch (e) {
returnfalse;
}
}
}
通过 RequestPermissions.ets
封装鸿蒙的权限检查和请求逻辑,FlutterUdidPlugin.ets
可以在需要时调用这些功能,确保在获取 OAID 之前,应用已经获得了用户的应用跟踪许可。这种分层设计使得权限管理逻辑清晰、可复用,并简化了主插件逻辑。
注意: 示例代码中的 onMethodCall
在调用 getUDID
前并未严格检查 requestPermissions
的返回结果。在实际应用中,建议在确认权限被授予 (granted === true
) 后才执行获取 OAID 的操作,并在权限被拒绝时向 Flutter 返回相应的错误信息。
flutter pub get
flutter build hap --debug
flutter config --ohos-sdk=/Users/jianguo/Library/OpenHarmony/Sdk
flutter config --ohos-sdk=""
参考资料
[1]
ohos.permission.APP_TRACKING_CONSENT: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/permissions-for-all-user-V14#ohospermissionapp_tracking_consent
[2]
requestPermissions 标签说明: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/declare-permissions-V14#在配置文件中声明权限
[3]
各类 Context 的获取方式: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V14/js-apis-inner-application-context-V14
[4]
Flutter 使用指导文档: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/07_plugin/ohos%E5%B9%B3%E5%8F%B0%E9%80%82%E9%85%8Dflutter%E4%B8%89%E6%96%B9%E5%BA%93%E6%8C%87%E5%AF%BC.md
[5]
Flutter 配置指南: https://gitcode.com/openharmony-sig/flutter_flutter/blob/master/README.md
[6]
官方插件库: https://pub.dev/
[7]
fflutter_udid4.0.0: https://pub-web.flutter-io.cn/packages/flutter_udid/versions/4.0.0
[8]
开发 package: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/开发package.md
[9]
开发 plugin: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/开发plugin.md