新版本android_id,android手机唯一id方案总结

2023-05-16

google对隐私管理越来越严格了,华为也出了个OAID来保护用户隐私。对于生成android设备唯一id一直没有个绝对完美的方案,只能说做到尽量唯一吧,这里做一下总结。

一、设备系统版本为Android Q

从 Android Q 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。许多用例不需要不可重置的设备标识符。如果您的应用没有该权限,但您仍尝试查询标识符的相关信息,则平台的响应会因目标 SDK 版本而异:

如果应用以 Android Q 为目标平台,则会发生 SecurityException。

如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException。

注意:如果您的应用是设备所有者或配置文件所有者应用,那么即使您的应用以 Android Q 为目标平台,您也只需 READ_PHONE_STATE 权限即可访问不可重置的设备标识符。此外,如果您的应用具有特殊运营商权限,则无需任何权限即可访问这些标识符。

如果您的应用将不可重置的设备标识符用于广告跟踪或用户分析目的,请为这些特定用例创建 Android 广告 ID。要了解详情,请参阅唯一标识符的最佳做法。

待Android Q正式发布后再补充

二、设备系统版本为Android P及其以下

2. 友盟生成唯一id的方案

反编译了友盟统计analytics-6.1.4.jar,友盟生成唯一id的方案可以总结为:

SDK_INT<23:imei>mac地址(直接api获取)>android_id>serial number

SDK_INT=23:imei>mac地址(api获取,读取系统文件)>android_id>serial number

SDK_INT>23:imei>serial number>android_id>mac地址(api获取,读取系统文件)

反编译的代码如下,稍微修改了下方法名

public class DeviceIdUtil {

private static FileReader fileReader;

private static BufferedReader bufferedReader;

private static String getDeviceUniqueId(Context paramContext) {

String str = "";

if (Build.VERSION.SDK_INT < 23) {

str = getDeviceId(paramContext);

if (TextUtils.isEmpty(str)) {

str = getMacAddressFromWifiManager(paramContext);

if (TextUtils.isEmpty(str)) {

str = Settings.Secure.getString(paramContext.getContentResolver(), "android_id");

if (TextUtils.isEmpty(str)) {

str = getSerial();

}

}

}

} else if (Build.VERSION.SDK_INT == 23) {

str = getDeviceId(paramContext);

if (TextUtils.isEmpty(str)) {

str = getMacAddressFromNetworkInterface();

if (TextUtils.isEmpty(str)) {

if (a.d) {//反编译看代码默认是true

str = getMacAddressFromFile();

} else {

str = getMacAddressFromWifiManager(paramContext);

}

}

if (TextUtils.isEmpty(str)) {

str = Settings.Secure.getString(paramContext.getContentResolver(), "android_id");

if (TextUtils.isEmpty(str)) {

str = getSerial();

}

}

}

} else {

str = getDeviceId(paramContext);

if (TextUtils.isEmpty(str)) {

str = getSerial();

if (TextUtils.isEmpty(str)) {

str = Settings.Secure.getString(paramContext.getContentResolver(), "android_id");

if (TextUtils.isEmpty(str)) {

str = getMacAddressFromNetworkInterface();

if (TextUtils.isEmpty(str)) {

str = getMacAddressFromWifiManager(paramContext);

}

}

}

}

}

return str;

}

private static String getMacAddressFromFile() {

try {

String[] arrayOfString = {"/sys/class/net/wlan0/address", "/sys/class/net/eth0/address", "/sys/devices/virtual/net/wlan0/address"};

for (byte b1 = 0; b1 < arrayOfString.length; b1++) {

try {

String str = a(arrayOfString[b1]);

if (str != null) {

return str;

}

} catch (Throwable throwable) {

}

}

} catch (Throwable throwable) {

}

return null;

}

private static String a(String paramString) {

String str = null;

try {

fileReader = new FileReader(paramString);

bufferedReader = null;

if (fileReader != null) {

try {

bufferedReader = new BufferedReader(fileReader, 1024);

str = bufferedReader.readLine();

} finally {

if (fileReader != null) {

try {

fileReader.close();

} catch (Throwable throwable) {

}

}

if (bufferedReader != null) {

try {

bufferedReader.close();

} catch (Throwable throwable) {

}

}

}

}

} catch (Throwable throwable) {

}

return str;

}

private static String getMacAddressFromNetworkInterface() {

try {

Enumeration enumeration = NetworkInterface.getNetworkInterfaces();

while (enumeration.hasMoreElements()) {

NetworkInterface networkInterface = (NetworkInterface) enumeration.nextElement();

if ("wlan0".equals(networkInterface.getName()) || "eth0".equals(networkInterface.getName())) {

byte[] arrayOfByte = networkInterface.getHardwareAddress();

if (arrayOfByte == null || arrayOfByte.length == 0) {

return null;

}

StringBuilder stringBuilder = new StringBuilder();

for (byte b1 : arrayOfByte) {

stringBuilder.append(String.format("%02X:", new Object[]{Byte.valueOf(b1)}));

}

if (stringBuilder.length() > 0) {

stringBuilder.deleteCharAt(stringBuilder.length() - 1);

}

return stringBuilder.toString().toLowerCase(Locale.getDefault());

}

}

} catch (Throwable throwable) {

}

return null;

}

private static String getSerial() {

String str = "";

if (Build.VERSION.SDK_INT >= 9 && Build.VERSION.SDK_INT < 26) {

str = Build.SERIAL;

} else if (Build.VERSION.SDK_INT >= 26) {

try {

Class clazz = Class.forName("android.os.Build");

Method method = clazz.getMethod("getSerial", new Class[0]);

str = (String) method.invoke(clazz, new Object[0]);

} catch (Throwable throwable) {

}

}

return str;

}

private static String getDeviceId(Context paramContext) {

String str = "";

TelephonyManager telephonyManager = (TelephonyManager) paramContext.getSystemService("phone");

if (telephonyManager != null) {

try {

if (a(paramContext, "android.permission.READ_PHONE_STATE")) {

if (Build.VERSION.SDK_INT > 26) {

Class clazz = Class.forName("android.telephony.TelephonyManager");

Method method = clazz.getMethod("getImei", new Class[]{Integer.class});

str = (String) method.invoke(telephonyManager, new Object[]{method, Integer.valueOf(0)});

if (TextUtils.isEmpty(str)) {

method = clazz.getMethod("getMeid", new Class[]{Integer.class});

str = (String) method.invoke(telephonyManager, new Object[]{method, Integer.valueOf(0)});

if (TextUtils.isEmpty(str)) {

str = telephonyManager.getDeviceId();

}

}

} else {

str = telephonyManager.getDeviceId();

}

}

} catch (Throwable throwable) {

str = "";

}

}

return str;

}

private static String getMacAddressFromWifiManager(Context paramContext) {

try {

WifiManager wifiManager = (WifiManager) paramContext.getSystemService("wifi");

if (a(paramContext, "android.permission.ACCESS_WIFI_STATE")) {

WifiInfo wifiInfo = wifiManager.getConnectionInfo();

return wifiInfo.getMacAddress();

}

return "";

} catch (Throwable throwable) {

return "";

}

}

}

3. 项目里目前采用的方案(唯一id+本地存储)

因为app首次安装启动就要上报唯一id,判断是否是新用户等等,这些操作很可能是在获取权限之前,所以参考友盟和搜来的方案,对唯一id的生成方案做了优化,去掉了mac地址的获取,新增了伪id的生成,加上了存储唯一id到本地。

方案:首次启动就去生成唯一id(优先级:imei>serial number>android_id>伪imei),并存储到SharePreference中。

伪imei可参考

String m_szDevIDShort = "35" + //we make this look like a valid IMEI

Build.BOARD.length()%10+ Build.BRAND.length()%10 +

Build.CPU_ABI.length()%10 + Build.DEVICE.length()%10 +

Build.DISPLAY.length()%10 + Build.HOST.length()%10 +

Build.ID.length()%10 + Build.MANUFACTURER.length()%10 +

Build.MODEL.length()%10 + Build.PRODUCT.length()%10 +

Build.TAGS.length()%10 + Build.TYPE.length()%10 +

Build.USER.length()%10 ; //13 digits

优点:无需关心权限,绝大部分手机都能生成的唯一id,存储到sp中保证了无论是否授予权限,唯一id都不会变化(下面这种情况例外)

缺点:当用户首次安装,启动,卸载后重装,手动到权限管理赋予android.permission.READ_PHONE_STATE权限,再启动,此时生成的唯一id可能会发生变化

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

新版本android_id,android手机唯一id方案总结 的相关文章

  • 如何快速自动发送FCM或APNS消息?

    我正在开发一项后端服务 通过 FCM 或 APNS 向移动应用程序发送推送通知 我想创建一个可以在一分钟内运行的自动化测试 并验证服务器是否可以成功发送通知 请注意 我不一定需要检查通知是否已送达 只需检查 FCM 或 APNS 是否已成功
  • 在包“android”中找不到属性“backgroundTint”的资源标识符

    我发现了一些视图 xml 属性 例如backgroundTint backgroundTintMode 但是当我使用它作为视图属性定义时 Eclipse 显示错误 No resource identifier found for attri
  • StrictMode 策略违规:我的应用程序中存在 android.os.strictmode.LeakedClosableViolation?

    Android 开发新手 第一次在我的应用程序上尝试 StrictMode 我注意到以下内容 并想知道这是否是我的应用程序或库中的问题 我不太清楚 谢谢你 D StrictMode StrictMode policy violation a
  • 卸载后 Web 应用程序不显示“添加到主屏幕”

    这是我第一次创建网络应用程序 我设法解决了这个问题 所以我得到了实际的 chrome 提示 将其添加到主屏幕 然后我从手机上卸载了该网络应用程序 因为我想将其展示给我的同事 但是 屏幕上不再出现提示 问题 这是有意为之的行为还是我的应用程序
  • 如何在android中获取Camera2 API的当前曝光

    In android hardware Camera旧的 我使用下面的代码获取当前曝光并获取它Camera Camera Parameters param mCamera getParameters currentExposure para
  • Android Activity 生命周期函数基础知识

    我正在测试这段代码 它显示活动所处的状态 public class Activity101Activity extends Activity String tag Lifecycle Called when the activity is
  • 找不到处理意图 com.instagram.share.ADD_TO_STORY 的活动

    在我们的 React Native 应用程序中 我们试图让用户根据视图 组件中的选择直接将特定图像共享到提要或故事 当我们尝试直接使用 com instagram share ADD TO FEED 进行共享时 它以一致的方式完美运行 但是
  • Android 模拟器插件无法初始化后端 EGL 显示

    我在 Cloudbees 上设置了 Jenkins 作业 并且可以在那里成功签出并编译我的 Android 项目 现在我想在 android 模拟器中运行一些 JUnit 测试并添加 Android 模拟器插件 我将 显示模拟器窗口 选项设
  • Android:捕获的图像未显示在图库中(媒体扫描仪意图不起作用)

    我遇到以下问题 我正在开发一个应用程序 用户可以在其中拍照 附加到帖子中 并将图片保存到外部存储中 我希望这张照片也显示在图片库中 并且我正在使用媒体扫描仪意图 但它似乎不起作用 我在编写代码时遵循官方的Android开发人员指南 所以我不
  • 是否有 ADB 命令来检查媒体是否正在播放

    我想使用 ADB 命令检查根植于终端的外部设备中是否正在播放音频 视频 我无法找到任何 ADB 命令 如果有 我尝试过 adb shell dumpsys media player 我想要一个命令来指定视频是否正在运行 您可以使用以下命令查
  • 你的CPU不支持NX

    我刚刚下载了 android studio 但是我遇到了一个问题 当我运行它时 它说你的 cpu 不支持 NX 我应该怎么办 NX 或实际上是 NX 处理器位 是处理器的一项功能 有助于保护您的 PC 免受恶意软件的攻击 当此功能未启用并且
  • Google 云端硬盘身份验证异常 - 需要许可吗? (v2)

    我一直在尝试将 Google Drive v2 添加到我的 Android 应用程序中 但无法获得授权 我收到 UserRecoverableAuthIOException 并显示消息 NeedPermission 我感觉 Google A
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 字符串数组文本格式化

    我有这个字符串 String text Address 1 Street nr 45 Address 2 Street nr 67 Address 3 Street nr 56 n Phone number 000000000 稍后将被使用
  • Android Studio - Windows 7 上的 Android SDK 问题

    我对 Google i o 2013 上发布的最新开发工具 Android Studio 有疑问 我已经成功安装了该程序并且能够正常启动 我可以导入现有项目并对其进行编辑 但是 当我尝试单击 SDK 管理器图标或 AVD 管理器图标时 或者
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 将两个文本视图并排放置在布局中

    我有两个文本视图 需要在布局中并排放置 并且必须遵守两条规则 Textview2 始终需要完整显示 如果布局中没有足够的空间 则必须裁剪 Textview1 例子 文本视图1 文本视图2 Teeeeeeeeeeeeeeeeeextview1
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐