英语原文地址:http://android-developers.blogspot.hk/2011/03/identifying-app-installations.html

在 Android 群组中(Google Group),时不时能听到开发者提出关于获取可靠,稳定,唯一的设备标识符的问题的抱怨。这让我们很担心,因为我们认为追踪这些标识符并不是一个好的想法,并且我们认为有更好的方法来解决开发者的问题。

跟踪安装

开发者想要跟踪他们的应用的每一次安装是非常普遍且合理的行为。调用 TelephonyManager.getDeviceId()) 来获取值并识别设备看上去是可靠的做法。但是这个方法有几个问题:首先,这个方法运行并不可靠(见下文)。其次,当方法可用时,该方法返回的值会在擦除设备(”恢复出厂设置”)后依旧保持一致。所以,当你的用户重置了他的设备并交给另外一个人后,该方法最终可能会令你产生严重的错误。

为了追踪应用安装,你只需在应用安装后的第一次运行时创建一个 UUID 作为标识符。以下是一个叫做 Installation 的简单类,其中包含了一个叫做 Installation.id(Context context) 的静态方法。你可以发挥你的想象力,向 INSTALLATION 文件中写入更多在安装后特有的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Installation {
    private static String sID = null;
    private static final String INSTALLATION = "INSTALLATION";
    public synchronized static String id(Context context) {
        if (sID == null) {  
            File installation = new File(context.getFilesDir(), INSTALLATION);
            try {
                if (!installation.exists())
                    writeInstallationFile(installation);
                sID = readInstallationFile(installation);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return sID;
    }
    private static String readInstallationFile(File installation) throws IOException {
        RandomAccessFile f = new RandomAccessFile(installation, "r");
        byte[] bytes = new byte[(int) f.length()];
        f.readFully(bytes);
        f.close();
        return new String(bytes);
    }
    private static void writeInstallationFile(File installation) throws IOException {
        FileOutputStream out = new FileOutputStream(installation);
        String id = UUID.randomUUID().toString();
        out.write(id.getBytes());
        out.close();
    }
}

识别设备

或许因为应用中的需求,你的确需要一个真实的硬件设备标识符。这就变成了个麻烦的问题。

在过去,当每一台 Android 设备都只是一台手机的时候,事情会很简单。只需调用 TelephonyManager.getDeviceId() 肯定会返回手机的IMEI, MEID或者ESN(取决于手机的网络制式),这些都是设备唯一的标识符。

然而,现在这个方法有以下这些问题。

  • 非手机设备:仅支持 Wi-Fi 的设备或者音乐播放器,这些没有电话通讯硬件的设备没有这种唯一标识符。
  • 存留问题:的确存在这样的一些设备,这些设备在擦除数据或者恢复出厂设置后仍然保存同样的DeviceId。这种情况下,你的 app 应该将其认为是同一部设备。
  • 权限问题:调用该方法需要 READ_PHONE_STATE 权限。假如你不需要其他与电话有关的服务,这挺令人烦恼的。
  • 一些Bug:我们已经发现了一些设备制造商,错误地实现这个方法,导致返回的都是辣鸡,比如一串 0 或者一串星号。

Mac 地址

通过检索设备的 Wi-Fi 或者蓝牙硬件是有可能取得设备的 Mac 地址。但是我们并不建议将 Mac 地址作为唯一标识符。首先,并不是所有设备都有 Wi-Fi 硬件。其次,如果 Wi-Fi 未打开,硬件可能不会返回 Mac 地址。

序列号

从 Android 2.3 开始,可以通过 android.os.Build.SERIAL 获取设备的序列号。没有通讯功能的设备也被要求通过该方法返回一个唯一设备ID。一些手机可能也会这么做。

ANDROID_ID

更具体指的是 Settings.Secure.ANDROID_ID . 这是一串64-bit的字符串,在设备第一次启动的时候生成并保存,并且会在擦除设备后重置该值。

ANDROID_ID 看起来是一个作为唯一设备标识符的好选择。但是有几个缺点:首先,在 Android2.2 (“Froyo”) 中并不是100%可靠的。此外,至今为止至少有一家主流设备制造商制造的一款手持设备中该方法存在Bug,这导致这些设备都拥有一个一样的 ANDROID_ID.

总结

对于绝大多数应用,需要的是识别每一次特定的安装,而不是识别物理设备。幸运的是,这样的话就会很简单。

有足够多好的理由让我们避免尝试去识别一台特定的设备。但是对于那些一定要这么做的,最佳的办法还是使用 ANDROID_ID 来识别尽可能新的设备,但是需要对旧的设备的识别需要采取一些特定的处理。(原文:For those who want to try, the best approach is probably the use of ANDROID_ID on anything reasonably modern, with some fallback heuristics for legacy devices. 希望有人能告诉我fallback heuristics是啥意思)


译者注:

其实谷歌写这篇文章基于这样的想法:一般用户不怎么折腾手机,所以一般来说擦除数据或者恢复出厂设置即可以视为手机易主。这样的话一个应用需要将这台擦除数据后的设备的使用者视为新的用户。所以谷歌非常不建议开发者去跟踪设备硬件有关的唯一标识符。因为如果这样做,手机就算擦除数据或者恢复出厂设置,甚至易主后应用仍会将其认为是前一任用户,按谷歌的原话讲,这就会导致非常严重的错误。(譬如数据混乱等)。

谷歌建议应用在安装后第一次打开时,自己设置一个UUID并保存到外部存储中。谷歌这么建议的前提是假设用户不会删这个UUID文件,这样就算应用卸载后重装,第一次打开会读取这个 UUID 文件中的标识符。而且如果用户擦除数据的话,应用即可以视为手机换人。

鉴于谷歌这篇文章的发布时间(2011.3)考虑,当时安卓2.3发布才不到四个月。所以很多应用或许的确是没账号系统的。所以谷歌非常不建议这样做是很有道理的。然而在我们现在看来,基本上需要跟用户个性数据有关的 app 基本上都用了账号系统,所以其实谷歌的这些建议已经落后了。但是对于开发者而言,对设备的唯一标识符的需求并没有消失。

谷歌写这篇文章是想告诉开发者,别想着去要什么设备唯一标识符。因为谷歌自己也没想到本来为手机设备的安卓后来发展出来各种各样的设备。更没想到还有一大堆猪队友,连个返回值都能实现错吧。所以安卓上根本没有一个可靠的一步就能得到的设备标识符。

接下来我会专门写一篇博客介绍如何获取这些跟识别设备有关的值,以及其中可能存在的坑。