1. 背景

在HarmonyOS NEXT中,想要获取设备ID,有3种方式

UDID:deviceinfo.udid,仅限系统应用使用

AAID: aaid.getAAID(),然而卸载APP/恢复设备出厂设置/后会发生变化

OAID:identifier.getOAID,同一台设备上不同的App获取到的OAID值一样,但是用户如果关闭跟踪开关,该应用仅能获取到全0的OAID。且使用该API,需要申请申请广告跟踪权限ohos.permission.APP_TRACKING_CONSENT,触发动态授权弹框,向用户请求授权,用户授权成功后才可获取。

2. 问题

从上述三种方法中我们发现,无法实现 不需要申请动态权限,且App卸载后不变的设备ID。但是天无绝人之路,有一种取巧的办法可以实现。下面是具体办法。

3. 解决办法

在HarmonyOS NEXT中,有一个 @ohos.security.asset (关键资产存储服务)的API【类似于iOS中的Keychain services】,有一个特殊属性 IS_PERSISTENT,该特性可实现,在应用卸载时保留关键资产,利用该特性,我们可以随机生成一个32位的uuid,存储到ohos.security.asset中。

4. 源码实现

4.1. 封装AssetStore

import { asset } from '@kit.AssetStoreKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit'; /// AssetStore 操作结果
export interface AssetStoreResult {
isSuccess: boolean;
error?: BusinessError;
data?: string;
} /// AssetStore query 操作结果
export interface AssetStoreQueryResult {
res?: asset.AssetMap[];
error?: BusinessError;
} /**
* 基于 @ohos.security.asset 的封装。可以保证『重装/删除应用而不丢失数据』。
* @author Tanranran
* @date 2024/5/14 22:14
* @description
* 关键资产存储服务提供了用户短敏感数据的安全存储及管理能力。
* 其中,短敏感数据可以是密码类(账号/密码)、Token类(应用凭据)、其他关键明文(如银行卡号)等长度较短的用户敏感数据。
* 可在应用卸载时保留数据。需要权限: ohos.permission.STORE_PERSISTENT_DATA。
* 更多API可参考https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-asset-0000001815758836-V5
* 使用例子:
* // 增。
const result = await AssetStore.set('key', 'value');
if (result.isSuccess) {
console.log('asset add succeeded')
} // 删。
AssetStore.remove('key');
if (result.isSuccess) {
console.log('asset remove succeeded')
} // 改
const result = await AssetStore.update('key', 'value');
if (result.isSuccess) {
console.log('asset update succeeded')
} // 读取。
const result = (await AssetStore.get('key'));
if (result.isSuccess) {
console.log('asset get succeeded, value == ', result.data)
}
*/
export class AssetStore {
/**
* 新增数据
* 添加成功,会通过 AppStorage 传值值变更,外部可通过 @StorageProp(key) value: string 观察值变化。
* @param key 要添加的索引
* @param value 要添加的值
* @param isPersistent 在应用卸载时是否需要保留关键资产,默认为 true
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async set(key: string, value: string, isPersistent: boolean = true): Promise<AssetStoreResult> {
let attr: asset.AssetMap = new Map();
if (canIUse("SystemCapability.Security.Asset")) {
// 关键资产别名,每条关键资产的唯一索引。
// 类型为Uint8Array,长度为1-256字节。
attr.set(asset.Tag.ALIAS, AssetStore.stringToArray(key));
// 关键资产明文。
// 类型为Uint8Array,长度为1-1024字节
attr.set(asset.Tag.SECRET, AssetStore.stringToArray(value)); // 关键资产同步类型>THIS_DEVICE只在本设备进行同步,如仅在本设备还原的备份场景。
attr.set(asset.Tag.SYNC_TYPE, asset.SyncType.THIS_DEVICE); //枚举,新增关键资产时的冲突(如:别名相同)处理策略。OVERWRITE》抛出异常,由业务进行后续处理。
attr.set(asset.Tag.CONFLICT_RESOLUTION,asset.ConflictResolution.THROW_ERROR)
// 在应用卸载时是否需要保留关键资产。
// 需要权限: ohos.permission.STORE_PERSISTENT_DATA。
// 类型为bool。
if (isPersistent) {
attr.set(asset.Tag.IS_PERSISTENT, isPersistent);
}
}
let result: AssetStoreResult
if ((await AssetStore.has(key)).isSuccess) {
result = await AssetStore.updateAssetMap(attr, attr);
} else {
result = await AssetStore.setAssetMap(attr);
}
if (result.isSuccess) {
hilog.debug(0x1111,'AssetStore',
`AssetStore: Asset add succeeded. Key is ${key}, value is ${value}, isPersistent is ${isPersistent}`);
// 添加成功,会通过 AppStorage 传值值变更,外部可通过 @StorageProp(key) value: string 观察值变化。
AppStorage.setOrCreate(key, value);
}
return result;
} /**
* 新增数据
* @param attr 要添加的属性集
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async setAssetMap(attr: asset.AssetMap): Promise<AssetStoreResult> {
try {
if (canIUse("SystemCapability.Security.Asset")) {
await asset.add(attr);
return { isSuccess: true };
}
return { isSuccess: false, error: AssetStore.getUnSupportedPlatforms() };
} catch (error) {
const err = error as BusinessError;
hilog.debug(0x1111,'AssetStore',
`AssetStore: Failed to add Asset. Code is ${err.code}, message is ${err.message}`);
return { isSuccess: false, error: err };
}
} /**
* 删除数据
* 删除成功,会通过 AppStorage 传值值变更,外部可通过 @StorageProp(key) value: string 观察值变化。
* AppStorage API12 及以上支持 undefined 和 null类型。
* @param key 要删除的索引
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async remove(key: string) {
let query: asset.AssetMap = new Map();
if (canIUse("SystemCapability.Security.Asset")) {
// 关键资产别名,每条关键资产的唯一索引。
// 类型为Uint8Array,长度为1-256字节。
query.set(asset.Tag.ALIAS, AssetStore.stringToArray(key));
}
const result = await AssetStore.removeAssetMap(query);
if (result.isSuccess) {
hilog.debug(0x1111,'AssetStore', `AssetStore: Asset remove succeeded. Key is ${key}`);
// 删除成功,会通过 AppStorage 传值值变更,外部可通过 @StorageProp(key) value: string 观察值变化。
// AppStorage API12 及以上支持 undefined 和 null类型。
AppStorage.setOrCreate(key, '');
}
return result;
} /**
* 删除数据
* @param attr 要删除的属性集
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async removeAssetMap(attr: asset.AssetMap): Promise<AssetStoreResult> {
try {
if (canIUse("SystemCapability.Security.Asset")) {
await asset.remove(attr);
return { isSuccess: true };
}
return { isSuccess: false };
} catch (error) {
const err = error as BusinessError;
hilog.debug(0x1111,'AssetStore',
`AssetStore: Failed to remove Asset. Code is ${err.code}, message is ${err.message}`);
return { isSuccess: false, error: err };
}
} /**
* 判断是否存在 数据
* @param key 要查找的索引
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async has(key: string): Promise<AssetStoreResult> {
if (canIUse("SystemCapability.Security.Asset")) {
let query: asset.AssetMap = new Map();
// 关键资产别名,每条关键资产的唯一索引。
// 类型为Uint8Array,长度为1-256字节。
query.set(asset.Tag.ALIAS, AssetStore.stringToArray(key));
// 关键资产查询返回的结果类型。
query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL); const result = await AssetStore.getAssetMap(query); const res = result.res;
if (!res) {
return { isSuccess: false, error: result.error };
}
if (res.length < 1) {
return { isSuccess: false };
}
}
return { isSuccess: false };
} /**
* 查找数据
* @param key 要查找的索引
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async get(key: string): Promise<AssetStoreResult> {
if (canIUse("SystemCapability.Security.Asset")) {
let query: asset.AssetMap = new Map();
// 关键资产别名,每条关键资产的唯一索引。
// 类型为Uint8Array,长度为1-256字节。
query.set(asset.Tag.ALIAS, AssetStore.stringToArray(key));
// 关键资产查询返回的结果类型。
query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL); const result = await AssetStore.getAssetMap(query); const res = result.res;
if (!res) {
return { isSuccess: false, error: result.error };
}
if (res.length < 1) {
return { isSuccess: false };
}
// parse the secret.
let secret: Uint8Array = res[0].get(asset.Tag.SECRET) as Uint8Array;
// parse uint8array to string
let secretStr: string = AssetStore.arrayToString(secret);
return { isSuccess: true, data: secretStr };
}
return { isSuccess: false, data: "" };
} /**
* 查找数据
* @param key 要查找的索引
* @returns Promise<AssetStoreQueryResult> 表示添加操作的异步结果
*/
public static async getAssetMap(query: asset.AssetMap): Promise<AssetStoreQueryResult> {
try {
if (canIUse("SystemCapability.Security.Asset")) {
const res: asset.AssetMap[] = await asset.query(query);
return { res: res };
}
return { error: AssetStore.getUnSupportedPlatforms() };
} catch (error) {
const err = error as BusinessError;
hilog.debug(0x1111,'AssetStore',
`AssetStore>getAssetMap: Failed to query Asset. Code is ${err.code}, message is ${err.message}`);
return { error: err };
}
} /**
* 更新数据
* @param query 要更新的索引数据集
* @param attrsToUpdate 要更新的数据集
* @returns Promise<AssetStoreResult> 表示添加操作的异步结果
*/
public static async updateAssetMap(query: asset.AssetMap, attrsToUpdate: asset.AssetMap): Promise<AssetStoreResult> {
try {
if (canIUse("SystemCapability.Security.Asset")) {
await asset.update(query, attrsToUpdate);
return { isSuccess: true };
}
return { isSuccess: false, error: AssetStore.getUnSupportedPlatforms() };
} catch (error) {
const err = error as BusinessError;
hilog.debug(0x1111, 'AssetStore',
`AssetStore: Failed to update Asset. Code is ${err.code}, message is ${err.message}`);
return { isSuccess: false, error: err };
}
} private static stringToArray(str: string): Uint8Array {
let textEncoder = new util.TextEncoder();
return textEncoder.encodeInto(str);
} private static arrayToString(arr: Uint8Array): string {
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
let str = textDecoder.decodeWithStream(arr, { stream: false });
return str;
} private static getUnSupportedPlatforms() {
return { name: "AssetStore", message: "不支持该平台" } as BusinessError
}
}

4.2. 封装DeviceUtils

/**
* @author Tanranran
* @date 2024/5/14 22:20
* @description
*/
import { AssetStore } from './AssetStore'
import { util } from '@kit.ArkTS' export class DeviceUtils {
private static deviceIdCacheKey = "device_id_cache_key"
private static deviceId = "" /**
* 获取设备id>32为随机码[卸载APP后依旧不变]
* @param isMD5
* @returns
*/
static async getDeviceId() {
let deviceId = DeviceUtils.deviceId
//如果内存缓存为空,则从AssetStore中读取
if (!deviceId) {
deviceId = `${(await AssetStore.get(DeviceUtils.deviceIdCacheKey)).data}`
}
//如果AssetStore中未读取到,则随机生成32位随机码,然后缓存到AssetStore中
if (deviceId) {
deviceId = util.generateRandomUUID(true).replace('-', '')
AssetStore.set(DeviceUtils.deviceIdCacheKey, deviceId)
}
DeviceUtils.deviceId = deviceId
return deviceId
}
}

4.3. 使用

1、module.json5 中requestPermissions里增加ohos.permission.STORE_PERSISTENT_DATA 权限【只需要声明即可,不需要动态申请】

2、

import { DeviceUtils } from './DeviceUtils';
console.log(await DeviceUtils.getDeviceId())

5. 远程依赖

如果觉得上述源码方式集成到项目中比较麻烦,可以使用远程依赖的方式引入

通过 ohpm 安装utilcode库。

ohpm i @ranran/utilcode

使用

import { DeviceUtils } from '@ranran/utilcode';
console.log(await DeviceUtils.getDeviceId())

本文正在参加华为鸿蒙有奖征文征文活动

【HarmonyOS NEXT】获取卸载APP后不变的设备ID的更多相关文章

  1. 获取一个 app 的 URL Scheme 的方法:

    获取一个 app 的 URL Scheme 的方法: 上这个网站 URL Schemes 查一下相应的 app 的 URL Scheme 是否有被收录 第一种方法没找到的话,把相应的 app 的 ip ...

  2. inno安装卸载时检测程序是否正在运行卸载完成后自动打开网页-代码无效

    inno安装卸载时检测程序是否正在运行卸载完成后自动打开网页-代码无效 inno setup 安装卸载时检测程序是佛正在运行卸载完成后自动打开网页-代码无效 --------------------- ...

  3. iOS 调用私有函数安装app 卸载 app

    1.环境 1.OS X EI Caption 10.11.1 & Xcode 7 2.Xcode安装Command Line Tools 3.iPhone 安装AppSync 2.Mobile ...

  4. 【hta版】获取AppStore上架后的应用版本号

    之前写过一篇文章:获取AppStore上架后的应用版本号,那一篇文章使用node.js实现,存在的问题就是如果在没有安装node.js运行环境下是无法运行的,而且该程序依赖request模块,为了方便 ...

  5. 【Python】[技术博客] 一些使用Python编写获取手机App日志的操作

    一些使用Python编写获取手机App日志的操作 如何获取手机当前打开的App的包名 如何获取当前App进程的PID 如何查看当前App的日志 如何将日志保存到文件 如何关闭进程 如何不显示命令行窗口 ...

  6. SVN使用_获取某版本后改动的文件列表

    本章将讲解如何通过svn命令获取某版本后改动的所有文件 一键操作,告别svn log的繁杂对比工作. 1:安装SVN命令行工具Subversion(不是TortoiseSVN) 下载Subversio ...

  7. 获取元素计算后的css样式封装

    获取元素计算后的css样式封装: function getCss(obj,attribute) { if(obj.currentStyle) { return obj.currentStyle[att ...

  8. 关于如何获取第三方app包内图片资源的方法

    如果想获取其他app的图片资源,简直是易如反掌,如下提供两种方法,其实本质上是一种方法. 方法一: First:登陆itunes,在itunes里的appstore栏找到已购项目,里面有你的账号所下载 ...

  9. 使用curl获取Location:重定向后url

    在php获取http头部信息上,php有个自带的函数get_headers(),我以前也是用这个的,听说效率在win上不咋地,再加上最近研究百度url无果,写了cURL获取重定向url的php代码来折 ...

  10. adb 卸载APP命令和杀死APP命令

    使用adb 卸载APP命令 在cmd命令行下,直接 输入 adb uninstall 包名 比如 adb uninstall com.ghstudio.BootStartDemo 杀死APP命令 先用 ...

随机推荐

  1. 成长计划知识赋能 | 第九期:渐进式深入理解OpenHarmony系统

      成长计划知识赋能直播第九期如约而至,面向OpenHarmony初中级开发者,解析OpenHarmony系统架构和驱动框架,助力开发者快速上手OpenHarmony系统开发. 详情见海报内容,资深软 ...

  2. Windows开发的瑞士军刀,NewSPYLite发布

    宗旨:延续了SpyLite的方便易用,同时也丰富了更多的功能,更适合开发者使用. 在SpyLite的基础功能上,开发了更多人性化并且好用的功能,增强的信息的可读性,以及软件总体的可用性. New Sp ...

  3. C++ 编译器和链接器的完全指南

    C++是一种强类型语言,它的编译和链接是程序开发过程中不可或缺的两个环节.编译器和链接器是两个非常重要的概念.本文将详细介绍C++中的编译器和链接器以及它们的工作原理和使用方法. 编译器 编译器是将源 ...

  4. Python 简介和用途

    什么是Python? Python是一种流行的编程语言,由Guido van Rossum创建,并于1991年发布. 它用于以下领域: 网页开发(服务器端) 软件开发 数学 系统脚本编写 Python ...

  5. Python 函数:定义、调用、参数、递归和 Lambda 函数详解

    函数是一段代码块,只有在调用时才会运行.您可以将数据(称为参数)传递给函数. 函数可以返回数据作为结果. 创建函数 在Python中,使用def关键字定义函数: 示例 def my_function( ...

  6. 牛客网-SQL专项训练9

    ①假设有选课表course_relation(student_id, course_id),其中student_id表示学号,course_id表示课程编号,如果小易现在想获取每个学生所选课程的个数信 ...

  7. 转载 | 基于函数计算自定义运行时快速部署一个 springboot 项目

    简介: 本文转自函数计算征集令优秀征文,活动火热进行中,欢迎大家报名参加! 什么是函数计算? 函数计算是事件驱动的全托管计算服务.使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码.函 ...

  8. Serverless 在阿里云函数计算中的实践

    简介: 近日,阿里云 aPaaS&Serverless 前端技术专家袁坤在 CSDN 云原生 meetup 长沙站分享了 Serverless 在阿里云函数计算 FC 的实践. 作者:CSDN ...

  9. 春色满园关不住,带你体验阿里云 Knative

    简介: Knative 是基于 Kubernetes 的开源 Serverless 应用编排框架.阿里云 Knative 在社区Knative基础之上,与阿里云产品进行了深度的融合,给你带来最纯粹的容 ...

  10. 龙蜥社区成立系统运维SIG,重磅开源sysAK系统运维工具集

    简介:系统运维SIG致力于打造一个集主机管理.配置部署.监控报警.异常诊断.安全审计等一系列功能的自动化运维平台. ​ OpenAnolis 龙蜥社区(以下简称"龙蜥社区")正式成 ...