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可能会发生变化

新版本android_id,android手机唯一id方案总结相关推荐

  1. 几种获取Android手机唯一id的方法

    一般都是用IMEI 获取方法 String imei =((TelephonyManager) context.getSystemService(TELEPHONY_SERVICE)).getDevi ...

  2. 获取android设备唯一ID和用途

    获取android设备唯一ID和用途 编者:李国帅 qq:9611153 微信lgs9611153 时间:2021/5/16 获取android设备唯一ID: 在android9及之前,我们还是可以获 ...

  3. Android开发——Android手机屏幕适配方案总结

    0. 前言 Android的屏幕适配,即使得某一元素在Android不同尺寸.不同分辨率的手机上具备相同的显示效果,这个问题一直以来都是我们Android开发者不得不面对的问题.本文参考了很多前人的博 ...

  4. android 获取唯一Id,小小总结一下。仅供参考

    1.获取imei: 前言: 因传统的移动终端设备标识如国际移动设备识别码(IMEI)等已被部分国家认定为用户隐私的一部分, 并存在被篡改和冒用的风险,所以在Android 10及后续版本中非厂商系统应 ...

  5. Android 获取唯一Id

    git:GitHub - gzu-liyujiang/Android_CN_OAID: 安卓设备唯一标识解决方案,可完全替代移动安全联盟统一 SDK 闭源方案.包括国内手机厂商的开放匿名标识(OAID ...

  6. Android 手机功耗测试方案

    极力推荐Android 开发大总结文章:欢迎收藏 程序员Android 力荐 ,Android 开发者需要的必备技能 本篇文章主要介绍 Android 开发中的部分 功耗 知识点,通过阅读本篇文章,您 ...

  7. 2021年Android设备唯一ID总结(Android11.0以下)

    一.Iemi号与DeviceId Android 10.0以下都可以获取并保证设备唯一性(记的动态申请READ_PHONE_STATE权限),Android10.0以上被限制无法获取.官方文档如下: ...

  8. 最全详解Android设备UDID还是唯一ID?

    这篇文章主要介绍了Android设备UDID还是唯一ID?我觉得挺不错的,现在分享给大家,也给大家做个参考. 我想为我的 Android应用程序生成android设备唯一ID,以根据用户设备udid创 ...

  9. 各品牌Android手机之系统UI篇

    原文链接: http://www.iplaysoft.com/choose-android-phone-software.html 但是我们发现,我们买回来的 Windows 电脑使用起来并没有太大的 ...

最新文章

  1. 机器学习:多分类的logistic回归
  2. AB1601某些io口不支持较高频率信号的输入
  3. python大数据运维库_大数据集群运维(10)Pycharm下安装模块
  4. 日语过级 JLPT简介
  5. 百度地图gif图标_华为手机误删照片怎么找回?手机怎么快速制作GIF动图
  6. IBM总架构师寇卫东:话说程序员的职业生涯-IT程序人生-职业生涯规划
  7. Linux的实际操作:文件目录类的实用指令(echo head tail)
  8. 4-10:TCP协议之面向字节流和粘包问题
  9. Hough 圆变换----Matlab实现
  10. js 异步执行_JS Asynchronous — JS 异步编程极简史
  11. 用python做逻辑回归_python实现逻辑回归
  12. K8s稳居容器榜首,Docker冲顶技术热词,微服务应用热度不减,2021云原生开发者现状
  13. SAP Serial Number
  14. PcShare过360服务监控
  15. Udacity 自动驾驶工程师学习笔记(二)——深度学习(1)
  16. 重庆SEO优化高手更新网站文章的窍门
  17. 中国信通院的星火链主链支持与以太链(测试网)交互
  18. 计算机键盘音乐好汉歌,好汉歌(刘欢演唱的歌曲)_百度百科
  19. HR最讨厌的几种求职者·
  20. STM32F103C8T6硬件SPI控制6针/7针0.96寸OLED显示屏

热门文章

  1. 计及碳捕集电厂低碳特性的含风电电力系统源–荷多时间尺度调度方法(Matlab代码实现)
  2. WC!咱平时使用的PDF,原来这么不安全?
  3. 数学建模算法与应用:预测模型(3)案例: SARS 疫情对经济指标影响
  4. 虚拟机CentOS6.5修改静态IP(NAT模式)+报错:Bringing up interface eth0:  Error: Unknown connection
  5. 什么是5G CPE?
  6. 又一所“省会大学”,来了!
  7. iOS中SDK的简单封装与使用
  8. 《Java编程思想》读书笔记
  9. DEDE自动调用轮播图/幻灯片
  10. Python备份Mysql脚本_python备份mysql脚本