Android系统以及设备都有不少的“标识”号,好比常见的IMEI,SerizalNumber,UUID等概念,可是这些都存在必定程度上的不可靠性,到底如何标记一台Android设备?html
文章内容多来自:java
1)http://www.cnblogs.com/lvcha/p/3721091.htmlandroid
2)http://android-developers.blogspot.com/2011/03/identifying-app-installations.htmlapp
尤为第二篇,乃是网上许多文章的根源所在,第一篇文章则是在2)上得补充,整理得本文。框架
1. DEVICE_IDless
假设咱们确实须要用到真实设备的标识,可能就须要用到DEVICE_ID。在之前,咱们的Android设备是手机,这个DEVICE_ID能够同经过TelephonyManager.getDeviceId()获取,它根据不一样的手机设备返回IMEI,MEID或者ESN码,但它在使用的过程当中会遇到不少问题:dom
1)非手机设备: 若是只带有Wifi的设备或者音乐播放器没有通话的硬件功能的话就没有这个DEVICE_IDide
2)权限: 获取DEVICE_ID须要READ_PHONE_STATE权限,但若是咱们只为了获取它,没有用到其余的通话功能,那这个权限有点大才小用ui
3)bug:在少数的一些手机设备上,该实现有漏洞,会返回垃圾,如:zeros(全是0)或者asterisks(星号)的产品this
2. MAC ADDRESS
咱们也能够经过手机的Wifi或者蓝牙设备获取MAC ADDRESS做为DEVICE ID,可是并不建议这么作,由于:
1)硬件限制:并非全部的设备都有Wifi和蓝牙硬件,硬件不存在天然也就得不到这一信息。
2)获取的限制:若是Wifi没有打开过,是没法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。
3. Serial Number
在Android 2.3能够经过android.os.Build.SERIAL获取,非手机设备能够经过该接口获取。没有电话功能的设备被要求必须提供这样一个序列号。
4. ANDROID_ID
ANDROID_ID是设备第一次启动时产生和存储的64bit的一个数,当设备被wipe后该数重置ANDROID_ID彷佛是获取Device ID的一个好选择,但它也有缺陷:
1)它在Android <=2.1 or Android >=2.3的版本是可靠、稳定的,但在2.2的版本并非100%可靠的
2)在主流厂商生产的设备上,有一个很常常的bug,就是每一个设备都会产生相同的ANDROID_ID:9774d56d682e549c
3)厂商定制系统的Bug:有些设备返回的值为null。
4)设备差别:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。
5. Installtion ID : UUID
以上四种方式都有或多或少存在的必定的局限性或者bug,在这里,有另一种方式解决,就是使用UUID,该方法无需访问设备的资源,也跟设备类型无关。
这种方式是经过在程序安装后第一次运行后生成一个ID实现的(注意:是本身生成,而非系统主动生成的!),但该方式跟设备惟一标识不同,它会由于不一样的应用程序而产生不一样的ID,而不是设备惟一ID。所以常常用来标识在某个应用中的惟一ID(即Installtion ID),或者跟踪应用的安装数量。很幸运的,Google Developer Blog提供了这样的一个框架:
1 public class Installation { 2 private static String sID = null; 3 private static final String INSTALLATION = "INSTALLATION"; 4 5 public synchronized static String id(Context context) { 6 if (sID == null) { 7 File installation = new File(context.getFilesDir(), INSTALLATION); 8 try { 9 if (!installation.exists()) 10 writeInstallationFile(installation); 11 sID = readInstallationFile(installation); 12 } catch (Exception e) { 13 throw new RuntimeException(e); 14 } 15 } 16 return sID; 17 } 18 19 private static String readInstallationFile(File installation) throws IOException { 20 RandomAccessFile f = new RandomAccessFile(installation, "r"); 21 byte[] bytes = new byte[(int) f.length()]; 22 f.readFully(bytes); 23 f.close(); 24 return new String(bytes); 25 } 26 27 private static void writeInstallationFile(File installation) throws IOException { 28 FileOutputStream out = new FileOutputStream(installation); 29 String id = UUID.randomUUID().toString(); 30 out.write(id.getBytes()); 31 out.close(); 32 } 33 }
综合以上所述,为了实如今设备上更通用的获取设备惟一标识,咱们能够实现这样的一个类,为每一个设备产生惟一的UUID,以ANDROID_ID为基础,在获取失败时以TelephonyManager.getDeviceId()为备选方法,若是再失败,使用UUID的生成策略。
重申下,如下方法是生成Device ID,在大多数状况下Installtion ID可以知足咱们的需求,可是若是确实须要用到Device ID,那能够经过如下方式实现:
1 import android.content.Context; 2 import android.content.SharedPreferences; 3 import android.provider.Settings.Secure; 4 import android.telephony.TelephonyManager; 5 6 import java.io.UnsupportedEncodingException; 7 import java.util.UUID; 8 9 public class DeviceUuidFactory { 10 protected static final String PREFS_FILE = "device_id.xml"; 11 protected static final String PREFS_DEVICE_ID = "device_id"; 12 13 protected static UUID uuid; 14 15 /** 16 * Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely" 17 * to be unique across all Android devices. Much more so than ANDROID_ID is. 18 * 19 * The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on 20 * TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back 21 * on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a 22 * usable value. 23 * 24 * In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID 25 * may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2 26 * to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on 27 * a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation. 28 * 29 * Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT 30 * change after a factory reset. Something to be aware of. 31 * 32 * Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly. 33 * 34 * @see http://code.google.com/p/android/issues/detail?id=10603 35 * 36 * @return a UUID that may be used to uniquely identify your device for most purposes. 37 */ 38 public DeviceUuidFactory(Context context) { 39 if( uuid ==null ) { 40 synchronized (DeviceUuidFactory.class) { 41 if( uuid == null) { 42 final SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0); 43 final String id = prefs.getString(PREFS_DEVICE_ID, null ); 44 45 if (id != null) { 46 // Use the ids previously computed and stored in the prefs file 47 uuid = id; 48 } else { 49 final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); 50 51 // Use the Android ID unless it's broken, in which case fallback on deviceId, 52 // unless it's not available, then fallback on a random number which we store 53 // to a prefs file 54 try { 55 if (!"9774d56d682e549c".equals(androidId)) { 56 uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8")); 57 } else { 58 final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId(); 59 uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID(); 60 } 61 } catch (UnsupportedEncodingException e) { 62 throw new RuntimeException(e); 63 } 64 65 // Write the value out to the prefs file 66 prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString()).commit(); 67 68 } 69 } 70 } 71 } 72 } 73 }
经过这种方式生成的设备标志号在必定程度上能够比较稳定的标记一台Android设备。这个产生标记符的方法有如下几点要注意:
1)因为如今Android手机已经广泛升级到4.0以上,2.3如下支持有限,因此ANDROID_ID通常可用,即以ANDROID_ID为种子产生UUID基本可用;
2)使用DeviceID为种子产生UUID并不是好的选择,Android平板等不具有通讯功能的设备就不能拿到DeviceID;
3)加入有一个需求是但愿用户的设备不管在安装多少次应用以后均可以惟一的肯定该设备,则可用的方式是什么呢?咱们得同时考虑ANDROID_ID,Serizal Number以及DEVICE_ID,其中任何一个均可以标识设备~~SO,最好的选择是不是将这三个拼接起来做为UUID的种子呢?其中,咱们能够排除掉不合法的DEVICE_ID等;
检查非法性:
1)序列号自己字符是否重复:好比全是000000000;
2)是否为Null,用于没法拿到相关数据;
3)是否包含*号;
若是这三点中得任意一点知足,则表示该数据非法,能够丢弃使用,不然,可字符拼接做为UUID的种子,若是三种数据都非法,才能够随机生成UUID做为设备标识符。