https://www.cnblogs.com/maohai/p/6454013.html

Android设备唯一码的获取

UTDID是集团无线设备统一ID方案,目的是给每一台设备一个ID,作为唯一标识。UTDID由客户端生成,并在设备中各个客户端之间共享。UTDID的生成中包含时间戳和随机数等,因此重新生成的UTDID值一定是会改变的,UTDID的稳定性强依赖于手机存储,UTDID方案是一个重在持久化存储的方案。

1.老UTDID方案面临的问题

1.1 权限问题

utdid开发手册中是强制需要下面3个权限的:

name="android.permission.WRITE_EXTERNAL_STORAGE" /> name="android.permission.WRITE_SETTINGS" /> name="android.permission.READ_PHONE_STATE" />

但是随着Android6.0的发布和targetSDK使用23编译,很多权限问题都暴露了出来:
1.WRITE_SETTINGS
targetSDK使用23以后,即使申请了WRITE_SETTINGS权限,再想写settings中的数据会抛出了如下异常。 
IllegalArgumentException: You cannot keep your settings in the secure settings. 
想写Setting.System中的数据也已经没有权限了,即使想修改Settings.Global也需要是系统应用。

2.READ_PHONE_STATE
targetSDK使用23以后,需要手动授权。imei作为utdid生成字段的其中一部分,在生成utdid时,如果无法获取就使用随机值代替。这个即使不授权问题也不大。

3.WRITE_EXTERNAL_STORAGE
targetSDK使用23以后,需要手动授权。在settings中没有写入utdid的情况下,如果没有WRITE_EXTERNAL_STORAGE权限,应用外部的utdid是无法获得的,内部也没有的情况下,那么utdid势必会重新生成了。

1.2 内、外存储可靠性

如果settings不能保存,那么应用外就需要寄希望于sdcard的存储。除了权限问题会导致sdcard中的数据无法取得外,三方的手机管理工具也会对sdcard中的数据做清除。(utdid外存储目录用AAA,BBB表示)
以360手机卫士为例:
360深度空间清理时,在可安全清理项->广告垃圾->将AAA和BBB识别为淘宝应用缓存和阿里网页广告,导致被删除掉。AAA和BBB文件夹下的所以文件和文件夹被会被删除。
个人感觉这些文件的识别是依赖360某些配置的下发,因为在断网并清除360缓存的情况下,是识别不到AAA和BBB的。(360安装包文件被混淆,网络数据被加密,很难知道真正的实现。)
应用内的存储可靠性就更不用说了,卸载应用或清除应用缓存就直接没有了。

2.Android中一些其它的唯一标识

Android系统中并没有可靠获取所有厂商设备唯一ID的方法,各个方法都有自己的使用范围和局限性,这也是目前流行的Android系统版本过多,设备也是来自不同厂商,且没有统一标准等原因造成的。

2.1 通过Android SDk获取标识

DEVICE_ID

假设我们确实需要用到真实设备的标识,可能就需要用到DEVICE_ID。
在以前,我们的Android设备是手机,这个DEVICE_ID可以同通过getSystemService(Context.TELEPHONY_SERVICE).getDeviceId()获取,它根据不同的手机设备返回IMEI,MEID或者ESN码,但它在使用的过程中会遇到很多问题:

非手机设备:最开始搭载Android系统都手机设备,而现在也出现了非手机设备:如平板电脑、电子书、电视、音乐播放器等。这些设备没有通话的硬件功能,系统中也就没有TELEPHONY_SERVICE,自然也就无法通过上面的方法获得DEVICE_ID。当设备为手机时,返回设备的唯一ID。手机制式为 GSM 时,返回手机的 IMEI 。手机制式为 CDMA 时,返回手机的 MEID 或 ESN 。非电话设备或者 Device ID 不可用时,返回 null .
权限问题:获取DEVICE_ID需要READ_PHONE_STATE权限,如果只是为了获取DEVICE_ID而没有用到其他的通话功能,申请这个权限一来大才小用,二来部分用户会怀疑软件的安全性。 (Android 6.0 以上需要用户手动赋予该权限)
厂商定制系统中的Bug:少数手机设备上,由于该实现有漏洞,会返回垃圾,如:zeros或者asterisks

MAC ADDRESS

wifi mac获取方法:

WifiManager wifiManager=(WifiManager) getSystemService(Context.WIFI_SERVICE);WifiInfo wifiInfo=wifiManager.getConnectionInfo();String mac=wifiInfo.getMacAddress();

这种方法比较通用,但是最近在Android 6.0系统上,这个方法失效了,返回了”02:00:00:00:00:00”的常量。这并不是一个BUG,在google的博客中找到如下一段话:

Most notably, Local WiFi and Bluetooth MAC addresses are no longer available. The getMacAddress() method of a WifiInfo object and the BluetoothAdapter.getDefaultAdapter().getAddress() method will both return 02:00:00:00:00:00 from now on.

可以考虑使用NetworkInterface.getHardwareAddress。其原理和cat /sys/class/net/wlan0/address是一模一样的,但是这个是上层API,不需要自己处理底层数据,在Android 6.0上测试通过。

NetworkInterface networkInterface = NetworkInterface.getByName("wlan0");byte[] mac = networkInterface.getHardwareAddress();

问题:
1.如果重启手机后,Wifi没有打开过,是无法获取其Mac地址的。(可以考虑授予CHANGE_WIFI_STATE权限,开关一次wifi刷一下。)
2.有一些定制系统的目录并不一样。 例如三星的目录为"cat /sys/class/net/eth0/address",所以是否对所以机型都有效有待验证。(需要适配)
3.网上也有反映mac变更问题,是不是刷mac或者wifi故障导致,也不确定。
4.并不是所有的设备都有Wifi硬件,硬件不存在自然也就得不到这一信息。(这个还好)
5.需要 ACCESS_WIFI_STATE 权限。(这个还好)

设备序列号(Serial Number, SN)

获取办法:
String serialNum = android.os.Build.SERIAL;
装有SIM卡的设备获取办法:getSystemService(Context.TELEPHONY_SERVIEC).getSimSerialNumber();

注意对CDMA设备,返回的是一个空值。
在Android 2.3可以通过android.os.Build.SERIAL获取,非手机设备可以通过该接口获取。
在少数的一些设备上,会返回垃圾数据。对于没有通话功能的设备,它可能会返回一个固定的值。

ANDROID_ID

在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被恢复出厂设置后该值会被重置。可以通过下面的方法获取:

import android.provider.Settings;   String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID); 

ANDROID_ID可以作为设备标识,但需要注意:
它在Android <=2.1 or Android >=2.3的版本是可靠、稳定的,但在2.2的版本并不是100%可靠的

厂商定制系统的Bug:不同的设备可能会产生相同的ANDROID_ID:9774d56d682e549c。(摩托罗拉好像出现过这个问题)
厂商定制系统的Bug:有些设备返回的值为null。
设备差异:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。
并且,如果某个Andorid手机被Root过的话,这个ID也可以被改变。

2.2 通过Linux命令获取标识

cpu号

文件路径:/proc/cpuinfo
通过Adb shell 查看:adb shell cat /proc/cpuinfo
但是在Nexus4的Android6.0手机上Serial为0000000000000000

rickydeMacBook-Pro:bin ricky$ adb shell cat /proc/cpuinfoProcessor   : ARMv7 Processor rev 2 (v7l)processor   : 0BogoMIPS    : 13.53processor   : 1BogoMIPS    : 13.53processor   : 2BogoMIPS    : 13.53processor   : 3BogoMIPS    : 13.53Features    : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt CPU implementer : 0x51CPU architecture: 7CPU variant : 0x0CPU part    : 0x06fCPU revision    : 2Hardware    : QCT APQ8064 MAKORevision    : 000bSerial      : 0000000000000000

mac地址

文件路径: /sys/class/net/wlan0/address
通过Adb shell 查看:adb shell cat /sys/class/net/wlan0/address

rickydeMacBook-Pro:bin ricky$ adb shell  cat /sys/class/net/wlan0/address10:68:3f:49:93:7d

3.Android获取手机制作商,系统版本等

我们有时候会需要获取当前手机的系统版本来进行判断,或者需要获取一些当前手机的硬件信息。

3.1 android.os.Build类

android.os.Build类中,包括了这样的一些信息。我们可以直接调用 而不需要添加任何的权限和方法。

android.os.Build.BOARD:获取设备基板名称android.os.Build.BOOTLOADER:获取设备引导程序版本号android.os.Build.BRAND:获取设备品牌android.os.Build.CPU_ABI:获取设备指令集名称(CPU的类型)android.os.Build.CPU_ABI2:获取第二个指令集名称android.os.Build.DEVICE:获取设备驱动名称android.os.Build.DISPLAY:获取设备显示的版本包(在系统设置中显示为版本号)和ID一样android.os.Build.FINGERPRINT:设备的唯一标识。由设备的多个信息拼接合成。android.os.Build.HARDWARE:设备硬件名称,一般和基板名称一样(BOARD)android.os.Build.HOST:设备主机地址android.os.Build.ID:设备版本号。android.os.Build.MODEL :获取手机的型号 设备名称。android.os.Build.MANUFACTURER:获取设备制造商android:os.Build.PRODUCT:整个产品的名称android:os.Build.RADIO:无线电固件版本号,通常是不可用的 显示unknownandroid.os.Build.TAGS:设备标签。如release-keys 或测试的 test-keys android.os.Build.TIME:时间android.os.Build.TYPE:设备版本类型  主要为"user""eng".android.os.Build.USER:设备用户名 基本上都为android-buildandroid.os.Build.VERSION.RELEASE:获取系统版本字符串。如4.1.22.22.3等android.os.Build.VERSION.CODENAME:设备当前的系统开发代号,一般使用REL代替android.os.Build.VERSION.INCREMENTAL:系统源代码控制值,一个数字或者git hash值android.os.Build.VERSION.SDK:系统的API级别 一般使用下面大的SDK_INT 来查看android.os.Build.VERSION.SDK_INT:系统的API级别 数字表示

3.2 build.prop中获取当前系统属性

在Android系统中,/system/build.prop中含有大量系统相关的信息:

rickydeMacBook-Pro:bin ricky$ adb shell cat /system/build.prop# begin build properties# autogenerated by buildinfo.shro.build.id=MDB08Mro.build.display.id=cm_mako-userdebug 6.0 MDB08M c28ecd9956 test-keysro.build.version.incremental=c28ecd9956ro.build.version.sdk=23ro.build.version.preview_sdk=0ro.build.version.codename=RELro.build.version.all_codenames=RELro.build.version.release=6.0ro.build.version.security_patch=2015-11-01ro.build.version.base_os=ro.build.date=2015年 1123日 星期一 17:03:23 CSTro.build.date.utc=1448269403ro.build.type=userdebugro.build.user=moonlightro.build.host=moonlight-romsro.build.tags=test-keysro.build.flavor=cm_mako-userdebugro.product.brand=googlero.product.name=occamro.product.board=MAKO... ...

如果有root权限,修改/system/build.prop文件内容,系统相关的信息就会被改变(总会有那么些无聊的人)。因此设备信息的安全级别不高,但是可以作为参考。此外,可以上传一些手机root相关信息做参考(虽然也不是100%有效)。

3.3 关于手机root相关信息采集

可以提供手机root相关信息,作为build.prop有效性的一个参考值。

3.3.1 build.prop中的字段描述

在/system/build.prop中的Build.TAGS字段设备标签。如release-keys 或测试的 test-keys。
test-keys为root手机或第三方ROM
ro.build.type字段设备版本类型。如:user或userdebug等。

3.3.2 su文件是否存在

查看su文件是否存在,可以参考下面代码的检索路径:

"/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su","/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"

3.3.3 apk检查

查看/system/app/ 下是否存root后常用软件。Kinguser.apk、Superuser.apk等,如:

private boolean hasSuperuserApk() {    return new File("/system/app/Superuser.apk").exists();}

3.3.4 执行su命令

执行su命令,推荐使用new ProcessBuilder().command("su").start() 代替Runtime.getRuntime().exec()实现。Runtime.getRuntime().exec()的执行会有些bug,可以参考(Determine if running on a rooted device),记得要Process.destroy()。执行su命令,会唤起root授权对话框,在数据上报的场景是不建议使用,体验非常不好。

4.总结

设备唯一标识码还是以utdid做标识,但是在Android6.0+系统上,外存储权限越来越难获取和越来越不可靠的情况下,除考虑加入LocalSocket和Broadcast等机制做多应用间的utdid同步(问题也很明显)外,必须依赖网络,构建设备ID库来提升设备标识的可靠性。
因此需要考虑在服务器上建立utdid与各设备数据间的对应关系,通过做大规模的适配和数据上报,来解决问题。通过可获得手机参数做服务器请求,服务器的utdid与各设备数据间的对应关系表来寻找最匹配utdid值。
可以考虑的数据关系体系是以Wifi Mac地址、设备序列号、ANDROID_ID为主要基准,配合android.os.Build中手机基本信息为参考(用手机root相关信息采集做修正),DEVICE_ID(用READ_PHONE_STATE权限做修正),常用ip地址等。如果可以的话,还可以参考手机号码、业务登录账号等。目前只是一些初步想法,可行性还有待实际数据验证,方案还在探索阶段。

参考文档:

获取Android设备唯一标识码
Android 手机上获取物理唯一标识码
Getting Unique Device ID of an Android Smartphone 
android 利用反射获取当前系统sdk版本等属性
Android6.0 动态权限申请步骤以及需要注意的一些坑
分享一些Android设备root检测的思路
Android设备硬件层次跟踪


更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. 文章:开发者眼中的Android手机平台
  3. Android(安卓)调用系统相机,解决回调的resultCode一直都是0的问题
  4. [知识] ADB工具(Android(安卓)Debug Bridge) 详解,使用方法
  5. Android(安卓)adb工具的使用
  6. QT5.x应用在Mac OS X和Windows平台的发布过程
  7. Android系统设计中的设计模式分析之一
  8. Android(安卓)adb 命令学习记录
  9. 明早 10 点,Let’s talk Android!

随机推荐

  1. android中的通信机制总结1:使用handler来
  2. Android学习笔记(一)---一定要看的 Android
  3. Android(安卓)彻底组件化方案实践
  4. Android(安卓)API Guides---Building Web
  5. Android(安卓)LifecycleObserver & Sched
  6. Android-手机归属地和座机归属地查询
  7. 转:APK程序卸载二次确认的实现
  8. SDK频频升级,android开发面临挑战如何抉择
  9. Android调用照相机拍照
  10. Android(安卓)吊炸天的酷炫Menu菜单交互,