UTDID是集团无线设备统一ID方案,目的是给每一台设备一个ID,作为唯一标识。UTDID由客户端生成,并在设备中各个客户端之间共享。UTDID的生成中包含时间戳和随机数等,因此重新生成的UTDID值一定是会改变的,UTDID的稳定性强依赖于手机存储,UTDID方案是一个重在持久化存储的方案。
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势必会重新生成了。
如果settings不能保存,那么应用外就需要寄希望于sdcard的存储。除了权限问题会导致sdcard中的数据无法取得外,三方的手机管理工具也会对sdcard中的数据做清除。(utdid外存储目录用AAA,BBB表示)
以360手机卫士为例:
360深度空间清理时,在可安全清理项->广告垃圾->将AAA和BBB识别为淘宝应用缓存和阿里网页广告,导致被删除掉。AAA和BBB文件夹下的所以文件和文件夹被会被删除。
个人感觉这些文件的识别是依赖360某些配置的下发,因为在断网并清除360缓存的情况下,是识别不到AAA和BBB的。(360安装包文件被混淆,网络数据被加密,很难知道真正的实现。)
应用内的存储可靠性就更不用说了,卸载应用或清除应用缓存就直接没有了。
Android系统中并没有可靠获取所有厂商设备唯一ID的方法,各个方法都有自己的使用范围和局限性,这也是目前流行的Android系统版本过多,设备也是来自不同厂商,且没有统一标准等原因造成的。
假设我们确实需要用到真实设备的标识,可能就需要用到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
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 权限。(这个还好)
获取办法:
String serialNum = android.os.Build.SERIAL;
装有SIM卡的设备获取办法:getSystemService(Context.TELEPHONY_SERVIEC).getSimSerialNumber();
注意对CDMA设备,返回的是一个空值。
在Android 2.3可以通过android.os.Build.SERIAL获取,非手机设备可以通过该接口获取。
在少数的一些设备上,会返回垃圾数据。对于没有通话功能的设备,它可能会返回一个固定的值。
在设备首次启动时,系统会随机生成一个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也可以被改变。
文件路径:/proc/cpuinfo
通过Adb shell 查看:adb shell cat /proc/cpuinfo
但是在Nexus4的Android6.0手机上Serial为0000000000000000
rickydeMacBook-Pro:bin ricky$ adb shell cat /proc/cpuinfo
Processor : ARMv7 Processor rev 2 (v7l)
processor : 0
BogoMIPS : 13.53
processor : 1
BogoMIPS : 13.53
processor : 2
BogoMIPS : 13.53
processor : 3
BogoMIPS : 13.53
Features : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt
CPU implementer : 0x51
CPU architecture: 7
CPU variant : 0x0
CPU part : 0x06f
CPU revision : 2
Hardware : QCT APQ8064 MAKO
Revision : 000b
Serial : 0000000000000000
文件路径: /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/address
10:68:3f:49:93:7d
我们有时候会需要获取当前手机的系统版本来进行判断,或者需要获取一些当前手机的硬件信息。
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:无线电固件版本号,通常是不可用的 显示unknown
android.os.Build.TAGS:设备标签。如release-keys 或测试的 test-keys
android.os.Build.TIME:时间
android.os.Build.TYPE:设备版本类型 主要为"user" 或"eng".
android.os.Build.USER:设备用户名 基本上都为android-build
android.os.Build.VERSION.RELEASE:获取系统版本字符串。如4.1.2 或2.2 或2.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级别 数字表示
在Android系统中,/system/build.prop中含有大量系统相关的信息:
rickydeMacBook-Pro:bin ricky$ adb shell cat /system/build.prop
# begin build properties
# autogenerated by buildinfo.sh
ro.build.id=MDB08M
ro.build.display.id=cm_mako-userdebug 6.0 MDB08M c28ecd9956 test-keys
ro.build.version.incremental=c28ecd9956
ro.build.version.sdk=23
ro.build.version.preview_sdk=0
ro.build.version.codename=REL
ro.build.version.all_codenames=REL
ro.build.version.release=6.0
ro.build.version.security_patch=2015-11-01
ro.build.version.base_os=
ro.build.date=2015年 11月 23日 星期一 17:03:23 CST
ro.build.date.utc=1448269403
ro.build.type=userdebug
ro.build.user=moonlight
ro.build.host=moonlight-roms
ro.build.tags=test-keys
ro.build.flavor=cm_mako-userdebug
ro.product.brand=google
ro.product.name=occam
ro.product.board=MAKO
... ...
如果有root权限,修改/system/build.prop文件内容,系统相关的信息就会被改变(总会有那么些无聊的人)。因此设备信息的安全级别不高,但是可以作为参考。此外,可以上传一些手机root相关信息做参考(虽然也不是100%有效)。
可以提供手机root相关信息,作为build.prop有效性的一个参考值。
在/system/build.prop中的Build.TAGS字段设备标签。如release-keys 或测试的 test-keys。
test-keys为root手机或第三方ROM
ro.build.type字段设备版本类型。如:user或userdebug等。
查看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"
查看/system/app/ 下是否存root后常用软件。Kinguser.apk、Superuser.apk等,如:
private boolean hasSuperuserApk() {
return new File("/system/app/Superuser.apk").exists();
}
执行su命令,推荐使用new ProcessBuilder().command("su").start() 代替Runtime.getRuntime().exec()实现。Runtime.getRuntime().exec()的执行会有些bug,可以参考(Determine if running on a rooted device),记得要Process.destroy()。执行su命令,会唤起root授权对话框,在数据上报的场景是不建议使用,体验非常不好。
设备唯一标识码还是以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设备硬件层次跟踪