想必大家在开发Android项目的时候,多多少少会遇到“如何惟一地标识一台Android设备”等类似的问题。不只是以前,即使是现在乃至可以预见的将来,这个问题都将一直存在。
如果大家使用搜索工具搜索的话,大家也能够找到很多的解决方案,但每种方式都多多少少有些缺陷。
我在这里呢,将向大家解释一下诸多常见方案的不足之处,以及推荐一种相对而言比较靠谱的方法。
首先先要说明一下Android设备的情况。我们大家都知道,在起初的时候,Android设备仅仅意味着是“手机”。如果情况一直是这样就好了。可是事实并不是这样的,我们知道,随着Google以及Android的发展壮大,Android系统在除了支持手机之外,也开始向其它领域发展。截止到Android 5.0的时候,Android系统已经能够支持诸如手持设备、可穿戴设备等,详细来说就是已经支持手机、平板、手表、眼镜、电视、盒子以及汽车。所有的这些情况将影响各个解决方案的实际效果。
好,下面我们来一一揭开各个解决方案不足之处的神秘面纱。
ANDROID_ID
ANDROID_ID存在于android.provider.Settings.Secure.ANDROID_ID.它可以通过下面的方式获取得到:
1 String android_id = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);
View Code
这一个64位的16进制字符串。它在设备首次启动时被创建并存储起来。但是在设备恢复出厂设置或者刷机之时,将不得存在,只能在再次启动时重新生成。这是它的劣势之一。然后,还有,在2.2之前,这种方式并不是百分百可靠的。其次,在主要手持设备生产商生产的流行设备中,还有一个可以被显著观测到的bug,那就是诸多设备拥有相同的ANDROID_ID。所以,ANDROID_ID并不是一个完美的解决方案。
DEVICEID
DEVICEID,顾名思义,“设备id”。是由手机运营商添加进手机中的。它可以通过以下方式获取到:
1 TelephonyManager tm = (TelephonyManager) getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);
2 String tmDevice = "" + tm.getDeviceId();
View Code
它的返回值将依据运营商的不同,可能返回手机的IMEI或MEID或ESN。由于它是手机运营商添加进去的,所以可能存在收下诸多问题:
- 非手机:对于不能打电话的平板、智能手表、电视甚至汽车等非手机设备,并不存在这种惟一的标识符。
- 持续性问题:在拥有DEVICEID的设备上,在经过刷机或者工厂设置后,这个值有可能没有被清理干净。如果是这种情况,我们可能将它们认为是同一设备。
- 优先级问题:想要获取deviceid需要READ_PHONE_STATE权限。如果你在应用中并未使用到电话,使用这个权限将会很令人不安。
- 已知Bug问题:这种方式的实现在有些机器上是有bug的,会返回一些垃圾信息,比如0或者"*".
所以,DEVICEID也不能完美的解决这个问题。
SIMSERIALNUMBER
SimSerialNumber也是由手机运营商放进去的,它的获取方式是这样的:
1 TelephonyManager tm = (TelephonyManager) getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);
2 String tmSerial = "" + tm.getSimSerialNumber();
View Code
它的不靠谱之处也尤如DeviceId。
SERIALNUMBER
它是2.3之后才引入的。它的值存在于android.os.Build.SERIAL.SerialNumber。但是对于不支持通话的设备,得先需要唯一的deviceid。
Mac地址
也许有人可能想到依据设备的WiFi或者蓝牙硬件来获取Mac地址来惟一标识设备。但是这种方式是不值得推荐的。因为它并非问题可以获取得到的,因为它需要设备WiFi或者蓝牙开着。所以,如果设备不打开蓝牙呢?如果设备没有打开WiFi而是使用的2G、3G或者4G呢?所以这也是一个不靠谱的方式。
UUID
以上几种标识Android设备的方式都不尽如人意思。难道就没有一种方式可以稳定、可靠且唯一地标识Androi硬件设备的吗?
诶,等一等,标识Android设备非得使用设备的硬件信息吗?难得使用非硬件就不能标识设备了吗?
好吧,答案是UUID。UUID(Universal Unique ID)是软件界的“通用唯一ID”, 是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字
UUID由以下几部分的组合:
- 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
- 时钟序列。
- 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,其格式为:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx (8-4-4-4-12),可以从cflib 下载CreateGUID() UDF进行转换。
对于一个开发人员而言,想要追踪应用的每一次安装,是非常普遍,也十分合乎情理的。要追踪应用的安装,你可以使用UUID来作为标识,在安装之后应用首次运行之时创建它。下面是一个叫作“Intallation”的类,带有一个静态方法id(Context context),返回应用的UUID。你可以将更多有关安装的数据写入”ISTALLATION”文件中。
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 }
Installation.java
它是通过软件产生的UUId来惟一地标识软件在该设备上的安装来标识这台设备,而非通过硬件信息。当然,这种方式是通过将UUID保存在本地文件中来实现的,如果你清空了本应用数据或者删除了该文件的话,应用将重新生成一个UUID,这也就意味着设备的惟一标识符发生了变化,这是一台新设备。
UUID是一个随机的数值,那么它万一发生了重复值呢?好吧,这是一串32位的十六进制的数字,它产生重复值的概率是16的32次方之一,自己去算概率!!!