【HarmonyOS NEXT】获取卸载APP后不变的设备ID

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: AssetStoreResultif ((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 stringlet 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).replaceAll(new RegExp('-', "gm"), '')AssetStore.set(DeviceUtils.deviceIdCacheKey, deviceId)}DeviceUtils.deviceId = deviceIdreturn 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())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/13272.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI+agent智能助手】使用Dify新手小白也能分分钟构建属于自己的 AI 原生应用

最近有读者看了我这篇文章 【人工智能】字节版GPTs「扣子」coze免费使用chatGPT4模型操作步骤 &#xff0c; 私信我有没有本地私有化 智能体推荐。 这里推荐一款&#xff0c; Dify支持私有化部署&#xff0c;新手小白也能分分钟构建属于自己的 AI 原生应用。 官方地址&#…

如何将Git仓库中的文件打包成zip文件?

要将Git仓库中的文件打包成zip文件&#xff0c;您可以使用git archive命令。这个命令允许您将任何git可访问的树或提交导出成一个归档文件。以下是一些基本的步骤&#xff1a; 打开命令行或终端。切换到您的Git仓库的目录。执行git archive命令。 git archive --formatzip --o…

3D Tiles资源大全

本文汇总整理3D Tiles相关的各种资源&#xff0c;包括查看器、生成器、示例数据集、教程、演示等。 1、3D Tiles特色演示 注意&#xff1a;这些演示是基于 CesiumJS 1.87.1 Release 发布的&#xff0c;其中包括对 3D Tiles Next 扩展的实验性支持。这些演示中显示的大多数功能现…

Linux进程(三) --- 状态和优先级

运行&#xff0c;阻塞&#xff0c;挂起 运行 (Running) 当一个进程处于运行状态时&#xff0c;它正在使用CPU执行指令。进程在以下两种情况下可能被认为是运行状态&#xff1a; 实际运行&#xff08;Running on CPU&#xff09;&#xff1a; 进程当前正在CPU上执行。可运行&…

如何利用R包进行主成分分析和可视化

一. 使用R包“FactoMineR”进行主成分分析&#xff08;PCA&#xff09; 基本步骤如下&#xff1a; 安装和加载包&#xff1a;如果尚未安装&#xff0c;首先安装“FactoMineR”包&#xff0c;然后加载它&#xff1a; install.packages("FactoMineR")library(FactoM…

在springboot项目中自定义404页面

今天点击菜单的时候不小心点开了一个不存在的页面&#xff0c;然后看到浏览器给的一个默认的404页面 后端的程序员都觉得这页面太丑了&#xff0c;那么怎么能自定义404页面呢&#xff1f; 很简单&#xff0c;在我们的springboot的静态资源目录下创建一个error包&#xff0c;然…

ue引擎游戏开发笔记(41)——行为树的建立(2)--丰富ai行为:巡逻后返回原处

1.需求分析&#xff1a; 就敌人ai而言&#xff0c;追踪到敌人有可能丢失目标&#xff0c;丢失目标后应该能返回原来位置&#xff0c;实现这一功能。 2.操作实现&#xff1a; 1.思路&#xff1a;利用clear value函数&#xff0c;禁用掉当前的追踪功能&#xff0c;执行之后的返…

积温空间分布数据、气温分布数据、日照数据、降雨量分布、太阳辐射数据、地表径流数据、土地利用数据、npp数据、ndvi数据

引言 积温是某一时段内逐日平均气温之和,它是研究植物生长、发育对热量的要求和评价热量资源的一种指标,是影响植物生长的重要因素之一&#xff0c;对指导农业生产和生态建设具有非常重要的意义。作为重要的气候资源&#xff0c;积温与其它资源的区别在于存在很大的地域差异和时…

Python | Leetcode Python题解之第91题解码方法

题目&#xff1a; 题解&#xff1a; class Solution:def numDecodings(self, s: str) -> int:n len(s)# a f[i-2], b f[i-1], c f[i]a, b, c 0, 1, 0for i in range(1, n 1):c 0if s[i - 1] ! 0:c bif i > 1 and s[i - 2] ! 0 and int(s[i-2:i]) < 26:c aa,…

Flutter 中的 FlutterLogo 小部件:全面指南

Flutter 中的 FlutterLogo 小部件&#xff1a;全面指南 在 Flutter 应用中&#xff0c;FlutterLogo 是一个展示 Flutter 官方图标的小部件。它不仅可以作为一个应用启动时的占位符&#xff0c;也可以作为装饰性图标使用&#xff0c;以展示对 Flutter 的支持。本文将详细介绍 F…

统计学中的新进展与研究领域

统计学领域一直处于不断发展和演变之中&#xff0c;涌现出许多新的研究方向和方法。以下是一些统计学领域的最新研究进展和热点&#xff1a; 1. **贝叶斯统计&#xff1a;** 贝叶斯统计作为一种概率推断的方法&#xff0c;在近年来受到越来越多的关注。随着计算技术的不断进步…

C++——STL容器——List

1. 前言 List也是STL容器的一种&#xff0c;是C提供的链表结构的容器。C中所提供的list是双向带头循环链表&#xff0c;我们这篇文章通过自己模拟实现来学习list的使用。 为了避免和库中的命名冲突&#xff0c;也为了封装的考虑&#xff0c;我们将我们的list放入一个命名空间之…

微服架构基础设施环境平台搭建 -(七)Kubesphere pod内安装vimping命令

微服架构基础设施环境平台搭建 -&#xff08;七&#xff09;Kubesphere pod安装vim&ping命令 在K8s集群运维过程&#xff0c;需要进入pod容器内通过ping来测试网络是否通畅&#xff0c;或使用vim进行编辑文件&#xff0c;但是pod容器默认情况下是不支持ping、vim命令的&…

前端面试题日常练-day05 【面试题】

题目 希望这些选择题能够帮助您进行前端面试的准备&#xff0c;答案在文末。 1. 下列哪个 CSS 属性用于设置元素的背景颜色&#xff1f; A) color B) font-size C) background-color D) text-align2. 在 JavaScript 中&#xff0c;以下哪个函数可以用于将字符串转换为小写&a…

2024.05.15学习记录

1、完成Ts重构Axios项目中更多功能的开发 2、刷题&#xff1a;二叉树&#xff08;代码回忆录&#xff09; 3、复习diff算法源码解读

关于RK3588平台使用配置Qt、QtCreator、Gstreamer环境的一点记录

最近在做Qt工程代码平台适配的过程中&#xff0c;遇到了一些问题&#xff0c;记录一下。 问题一、主窗体无法透明 首先发现自己的Qt工程的主窗体的透明度无法控制的问题&#xff0c;要么全透明&#xff0c;要么不透明&#xff0c;后来查阅了一些关于linux和linux图形界面的相…

C语言 | Leetcode C语言题解之第91题解码方法

题目&#xff1a; 题解&#xff1a; int numDecodings(char* s) {int n strlen(s);// a f[i-2], b f[i-1], c f[i]int a 0, b 1, c;for (int i 1; i < n; i) {c 0;if (s[i - 1] ! 0) {c b;}if (i > 1 && s[i - 2] ! 0 && ((s[i - 2] - 0) * 10…

Flutter 中的 MaterialApp 小部件:全面指南

Flutter 中的 MaterialApp 小部件&#xff1a;全面指南 MaterialApp 是 Flutter 中用于创建整个 Material Design 风格的应用程序的小部件。它提供了一套丰富的组件和默认设置&#xff0c;以确保应用遵循 Material Design 的指南。本文将详细介绍 MaterialApp 的用途、属性、使…

C++|树形关联式容器(set、map、multiset、multimap)介绍使用

目录 一、关联式容器介绍 1.1概念 1.2键值对 1.3树形结构的关联式容器 1.3.1pair模板介绍 1.3.2make_pair的介绍 二、set的介绍和使用 2.1set介绍 2.2set使用 2.2.1构造 2.2.2容量 2.2.3修改 三、map的介绍和使用 3.1map介绍 3.2map使用 3.2.1构造 3.2.2容量 …

MyBatis-Plus 2万字面试题详解

目录 什么是MyBatis-Plus? MyBatis-Plus与MyBatis有什么区别? MyBatis-Plus的主要优点有哪些?