Android设备唯一标识符

Android开发中有时候因业务需要APP端产生一个唯一的标识符使服务器能识别Android设备或与终端设备相互认证,目前一般使用三种标识符分别为DeviceId、AndroidId、MAC地址(WiFi、BT)。

一、DeviceId

DeviceId是Android系统为开发者提供的用于标识手机设备的串号,也是各种方法中普适性较高的,可以说几乎所有的设备都可以返回这个串号,并且唯一性良好。

DeviceId获取方法:

    TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
    String DeviceId = tm.getDeviceId();

根据不同的手机设备返回IMEI(移动或联通),MEID(电信)或者ESN码(电信),但在使用的过程中有以下问题:

  • 非手机设备:最开始搭载Android系统都手机设备,而现在也出现了非手机设备:如平板电脑、电子书、电视、音乐播放器等。这些设备没有通话的硬件功能,系统中也就没有TELEPHONY_SERVICE,自然也就无法通过上面的方法获得DeviceId。
  • 权限问题:获取DeviceId需要READ_PHONE_STATE权限,如果只是为了获取DeviceId而没有用到其他的通话功能,申请这个权限一来大才小用,二来部分用户会怀疑软件的安全性。
  • 厂商定制系统中的Bug:少数手机设备上,由于该实现有漏洞,会返回垃圾,如:zeros或者asterisks

二、AndroidId(ANDROID_ID)

AndroidId是不需要权限的但是可能变的,在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是AndroidId,因是在用户第一次激活这个设备时产生的,所以当用户重置手机时,AndroidId会产生变化;理论上这个AndroidId是可以接受的,毕竟重置手机这个事发生也不会太频繁。可以通过下面的方法获取:

import android.provider.Settings;   

String AndroidId = Settings.System.getString(getContentResolver(),Settings.System.ANDROID_ID); 

AndroidId可以作为设备标识,但需要注意:

  • 厂商定制系统的Bug:不同的设备可能会产生相同的ANDROID_ID:9774d56d682e549c。
  • 厂商定制系统的Bug:有些设备返回的值为null。
  • 设备差异:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。

三、MAC地址(WiFi、BT)

我们也可以通过手机的Wifi或者蓝牙设备获取MAC ADDRESS作为DEVICE ID,但是并不推荐这么做:

  • 硬件限制:并不是所有的设备都有Wifi和蓝牙硬件,硬件不存在自然也就得不到这一信息。
  • 获取的限制:如果Wifi没有打开过,是无法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。

在6.0 以下后去mac地址方式

    WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    WifiInfo winfo = wifi.getConnectionInfo();
    String mac =  winfo.getMacAddress();

在6.0 及其以上8.0以下,上面的方法在7.0 的设备获取的永远是 02:00:00:00:00:00

    private static String getNewMac() {
        try {
            List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());

            for (NetworkInterface nif : all) {
                if (!nif.getName().equalsIgnoreCase("wlan0")) continue;
                byte[] macBytes = nif.getHardwareAddress();
                if (macBytes == null) {
                    return null;
                }

                StringBuilder res1 = new StringBuilder();
                for (byte b : macBytes) {
                    res1.append(String.format("%02X:", b));
                }

                if (res1.length() > 0) {
                    res1.deleteCharAt(res1.length() - 1);
                }

                return res1.toString();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

四、设备序列号

这个东西理论上来说是来自硬件,出厂是就设置好了,但是有些设备厂商会随便写一个值 Build.SERIAL 在 api>=26 的时候,可以这么获取:

    Build.getSerial();

总结:上文可以看出,Android系统中并没有标准的获取所有厂商设备唯一ID的方法,各个方法都有自己的使用范围和局限性,这也是目前流行的Android系统版本过多,设备也是来自不同厂商,且没有统一标准等原因造成的。

根据不同的应用领域选择不同的参数作为唯一标志符。