最近做的一个项目中需要用到Android设备唯一码(UUID)来标识一台设备,
Android中设备唯一码有很多,如:MAC地址、IMEI号(DeviceId)、IMSI号、ANDROID_ID、序列号(SerialNumber)等,
但并不是所有设备上都能稳定获取到这些值。
最后项目中采用的是MAC地址。
先总结一些搜索得知的各种值的缺点,再说说最后采用MAC地址的解决方案吧。
1.MAC地址:
获取MAC地址的方法有两种:
(1). 通过Linux命令查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public String getMacAddress() { String macAddress = null ; String str = "" ; try { //linux下查询网卡mac地址的命令 Process pp = Runtime.getRuntime().exec( "cat /sys/class/net/wlan0/address" ); InputStreamReader ir = new InputStreamReader(pp.getInputStream()); LineNumberReader input = new LineNumberReader(ir); for (; null != str;) { str = input.readLine(); if (str != null ) { macAddress = str.trim(); // 去空格 break ; } } } catch (IOException ex) { ex.printStackTrace(); } return macAddress; } |
缺点:
在当前没打开WiFi的情况下获取得到的MAC地址值为空,即使在执行这段代码前是有打开过WiFi,而执行这段代码时WiFi状态是关闭的,也不能获取到MAC地址。
(2). 通过Android官方的WifiManager类获取
1
2
3
4
5
6
7
8
9
10
11
|
public String getMacAddress() { String macAddress = null ; WifiManager wifiManager = (WifiManager)MyApplication.getContext().getSystemService(Context.WIFI_SERVICE); WifiInfo info = ( null == wifiManager ? null : wifiManager.getConnectionInfo()); macAddress = info.getMacAddress(); return macAddress; } |
需要加入权限
1
|
< uses-permission android:name = "android.permission.ACCESS_WIFI_STATE" /> |
缺点:
这种方法虽然能在当前Wifi状态为关闭的情况下获取到MAC地址,但前提是在手机开机后要打开过一次Wifi,如果在某次开机后没打开过Wifi就调用这段代码,获取地址也是为空。
网上给出的解释是:WiFi的Mac address是一个被动资讯。一般在开机后,不会主动上报到系统裡。要待WiFi硬件启动后,才会把有关Mac address资料记载入系统去。
2.IMEI号(DeviceId)、IMSI号:
1
2
3
|
TelephonyManager mTelephonyMgr = (TelephonyManager) getSystemServic(Context.TELEPHONY_SERVICE); String imsi = mTelephonyMgr.getSubscriberId(); //获取IMSI号 String imei = mTelephonyMgr.getDeviceId(); //获取IMEI号 |
需要加入权限
1
|
< uses-permission android:name = "android.permission.READ_PHONE_STATE" /> |
缺点:
IMEI号(国际移动设备身份码)、IMSI号(国际移动设备识别码)这两个是有电话功能的移动设备才具有,也就是说某些没有电话功能的平板是获取不到IMEI和IMSI号的。且在某些设备上getDeviceId()会返回垃圾数据。
3.ANDROID_ID:
ANDROID_ID 是设备首次启动时由系统随机生成的一串64位的十六进制数字。
1
|
String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID); |
缺点:
①.设备刷机wipe数据或恢复出厂设置时ANDROID_ID值会被重置。
②.现在网上已有修改设备ANDROID_ID值的APP应用。
③.某些厂商定制的系统可能会导致不同的设备产生相同的ANDROID_ID。
④.某些厂商定制的系统可能导致设备返回ANDROID_ID值为空。
⑤.CDMA设备,ANDROID_ID和DeviceId返回的值相同
4.序列号SerialNumber:
从Android 2.3开始,通过android.os.Build.SERIAL方法可获取到一个序列号。没有电话功能的设备也都需要上给出此唯一的序列号。
1
|
String SerialNumber = android.os.Build.SERIAL; |
缺点:
在某些设备上此方法会返回垃圾数据。
解决方案:
一种比较折衷的办法,在获取MAC地址之前先判断当前WiFi状态,若开启了Wifi,则直接获取MAC地址,若没开启Wifi,则用代码开启Wifi,然后马上关闭,再获取MAC地址。
目前此方法测试成功,无论在哪种状态下都能正确取得设备的MAC地址(包括开机后未启动过Wifi的状态下),且在未开启Wifi的状态下,用代码开启Wifi并马上关闭,过程极短,不会影响到用户操作。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public String getMacAddress() { String macAddress = null ; WifiManager wifiManager = (WifiManager)MyApplication.getContext().getSystemService(Context.WIFI_SERVICE); WifiInfo info = ( null == wifiManager ? null : wifiManager.getConnectionInfo()); if (!wifiManager.isWifiEnabled()) { //必须先打开,才能获取到MAC地址 wifiManager.setWifiEnabled( true ); wifiManager.setWifiEnabled( false ); } if ( null != info) { macAddress = info.getMacAddress(); } return macAddress; } |
需要加入如下权限
1
2
3
|
< uses-permission android:name = "android.permission.ACCESS_WIFI_STATE" > </ uses-permission > < uses-permission android:name = "android.permission.CHANGE_WIFI_STATE" > </ uses-permission > < uses-permission android:name = "android.permission.WAKE_LOCK" > </ uses-permission > |
来自:http://www.goteny.com/develop/android/201412/452.html
极光推送的设备唯一性标识 RegistrationID
对于 App 云平台系统,如何唯一地识别移动设备是非常重要的。否则,每次用户在设备上卸载掉应用再重新安装,后端系统只能把这个用户当作一个全新的用户了。
Android 上识别设备的唯一性,因为这个圈太乱,所以设备本身的任何标识都是无法直接用作设备唯一标识的。iOS 是系统强力限制被唯一识别的,目前唯一可以部分满足条件是 IDFA,但需要你的 App 本身的确嵌入了广告。
很多开发者使用极光推送时,都有这个疑问:极光推送是如何来做设备的唯一性识别的。本文解析极光推送如何尽可能地来唯一识别设备。
极光推送对安装在设备上的 App 使用 RegistrationID 作为标识。极光推送要『尽可能』确保设备的唯一性,就是要使得 RegistrationID 尽可能唯一。
RegistrationID 的定义
关于 RegistrationID 极光官方文档有如下的定义:
集成了 JPush SDK 的应用程序在第一次 App 启动后,成功注册到 JPush 服务器时,JPush 服务器会给客户端返回唯一的该设备的标识 – RegistrationID。JPush SDK 会以广播的形式发送 RegistrationID 到应用程序。
有了这个标识,App 编程可以把这个 RegistrationID 保存到自己的应用服务器上,然后就可以根据 RegistrationID 来向设备推送消息或者通知。
RegistrationID 变化可能性
如果 App 不卸载,是直接覆盖安装,Android, iOS 上 RegistrationID 的值都不会变化。
如果 App 是卸载之后再次安装:
- Android 上 RegistrationID 基本不会变;
- iOS 上如果启用了 IDFA 变化可能性不大,如果未启用 IDFA 则每次安装 RegistrationID 都会变;
RegistrationID 生成规则解析
Android 平台
Android 上因为国内存在大量山寨设备的原因,正常的 IMEI, Mac Address, AndroidID 这些可以考虑用作唯一标识的值,都是不可以用的,因为这些值在一批设备中可能都是同一个值。
极光的基本思路是:
- 生成一个 DeviceID 保存到 Settings, External Storage。依赖本地存储,应用被卸载后重新安装这些存储里的 DeviceID 还在的话,就是同一个设备。这一条理论上解决 90% 的不变性问题。
- DeviceID 之外增加补充规则:综合根据 IMEI, MAC Address, AndroidID 这几个值来判断,是否可能是老设备。
具体的逻辑细节,也是根据实际运行情况,以及收集到的反馈不断调整的,大多数逻辑可在服务器端调整。
iOS平台
鉴于 iOS 系统设计上限制设备唯一标识,所以极光一直使用 Device Token 作为标识,也因为极光推送本身就是需要 Device Token 这个值才可能运作的。
iOS 9 版本之后,每次卸载后重装都会导致 Device Token 变化,所以对于极光后台来说,都只能被识别为新用户。
极光 SDK 新版本增加了 IDFA 选项,在集成初始化 SDK 时可选把 IDFA 这个值设置进来,这样极光后台就优先根据 IDFA 值来识别用户,从有一定的可能性应用被卸载后重装还能识别回老设备。
IDFA 是广告标识符,是 iOS 专门为广告跟踪唯一地识别用户而设计的。在 iOS 设备上,设备 -> 隐私 -> 广告这个页面,有一个设置项:限制广告跟踪。默认是未选中状态的,即是关闭状态,是不限制的。用户可以选中,从而限制广告跟踪。设置项之外还有一个按钮:还原广告标识符…。如果用户点击了这个按钮,则 IDFA 值会变化。
默认的情况下,没有限制广告跟踪,可以取到 IDFA 这个值。并且用户未点击『还原广告标识』时,这个值是不会变的。这样就达到了唯一地标识设备、跟踪到用户的目标。
但是,但是,请一定留意,IDFA 并不是一定可以启用的,是需要你的 App 的确有广告功能才可以用的,否则 Apple 在上架审核时有可能发现从而拒绝上架。
关于苹果 App 上架对 IDFA 的要求,可参考这里的说明:The Advertising Identifier (IDFA)
高级使用建议
因为 RegistrationID 是 JPush SDK 注册完成之后才得到的,所以调用 SDK API 来获取 RegistrationID 的值时需要稍注意,不是总能够立即得到。
比如 iOS 上建议在监听到 kJPFNetworkDidLoginNotification 这个通知后的代码里,来获取 RegistrationID 的值。
Android 上有写 Settings 的权限,是可以写数据到 Settings 里边的,相当于被当作一个永久存储点了。 外部存储一般指 SDCard,现在越来越多手机直接手机自带了,类似于 iPhone 的作法。
这 2 个点如果数据都被破坏了,就的确通过本地 DeviceID 存储无法确认为唯一设备了。但是,服务器端还可以通过 IMEI/AndroidID 这些灵活地确认到是否同一设备。
以上转自 http://blog.jiguang.cn/registrationid/