Android设备唯一ID实现方案

随着Google对隐私的重视以及Android10的逐渐普及,获取设备的唯一标识越来越来难,在Android10以前,Android设备唯一标识包含IMEI、AndroidID、DeviceID、Mac地址等,下面收集了一些唯一ID的获取方案:

1. DEVICE_ID、IMEI

获取方法:

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String DEVICE_ID = tm.getDeviceId(); 
//String DEVICE_ID = tm.getImei(); 

这是Android系统为开发者提供的标识手机设备串号的方法,局限性:

  • Android6以后需要READ_PHONE_STATE权限才能获取到,请求这个权限会提示给用户需要读取手机、电话状态,很多用户会拒绝授权
  • 极少数手机上该实现有漏洞,会返回垃圾

2. Mac地址

可以使用手机WiFi或者蓝牙的Mac地址作为设备标识,Android 6.0以后通过 WifiManager 获取到的mac将是固定的:02:00:00:00:00:00 ,
再后来连读取 /sys/class/net/wlan0/address 也获取不到了。
现在只剩下面这种方法可以获取(没有开启wifi也可以获取到):

   /**
     * 获取手机Mac地址
     *
     * @return
     */
    public static String getMacAddress(Context mContext) {
        WifiManager wifiMgr = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInf = wifiMgr.getConnectionInfo();
        // wifiInf.getMacAddress().getMacAddress方法在安卓6.0系统上获取到的Mac 都是 02:00:00:00:00:00。
        String invalidMacAddress = "02:00:00:00:00:00";

        if (wifiInf.getMacAddress().equals(invalidMacAddress)) {
            String ret = null;
            try {
                ret = getAdressMacByInterface();
                if (ret != null) {
                    return ret;
                } else {
                    ret = getAddressMacByFile(wifiMgr);
                    return ret;
                }
            } catch (IOException e) {
                Log.e("TAG", "Erreur lecture propriete Adresse MAC");
            } catch (Exception e) {
                Log.e("TAG", "Erreur lecture propriete Adresse MAC ");
            }
        } else {
            return wifiInf.getMacAddress();
        }
        return invalidMacAddress;
    }

    /**
     * 获取6.0以上系统的mac值
     * @throws Exception
     */
    private static String getAddressMacByFile(WifiManager wifiMan) throws Exception {
        String fileAddressMac = "/sys/class/net/wlan0/address";
        String ret;
        int wifiState = wifiMan.getWifiState();

        wifiMan.setWifiEnabled(true);
        File fl = new File(fileAddressMac);
        FileInputStream fin = new FileInputStream(fl);
        ret = crunchifyGetStringFromStream(fin);
        fin.close();

        boolean enabled = WifiManager.WIFI_STATE_ENABLED == wifiState;
        wifiMan.setWifiEnabled(enabled);
        return ret;
    }

    /**
     * 获取6.0以上系统的mac值
     * @throws Exception
     */
    private static String crunchifyGetStringFromStream(InputStream crunchifyStream) throws IOException {
        if (crunchifyStream != null) {
            Writer crunchifyWriter = new StringWriter();

            char[] crunchifyBuffer = new char[2048];
            try {
                Reader crunchifyReader = new BufferedReader(new InputStreamReader(crunchifyStream, "UTF-8"));
                int counter;
                while ((counter = crunchifyReader.read(crunchifyBuffer)) != -1) {
                    crunchifyWriter.write(crunchifyBuffer, 0, counter);
                }
            } finally {
                crunchifyStream.close();
            }
            return crunchifyWriter.toString();
        } else {
            return "No Contents";
        }
    }

    private static String getAdressMacByInterface() {
        try {
            List all = Collections.list(NetworkInterface.getNetworkInterfaces());
            for (NetworkInterface nif : all) {
                if (nif.getName().equalsIgnoreCase("wlan0")) {
                    byte[] macBytes = nif.getHardwareAddress();
                    if (macBytes == null) {
                        return "";
                    }

                    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 e) {
            Log.e("TAG", "Erreur lecture propriete Adresse MAC ");
        }
        return null;
    }

局限性:

  • 没有WiFi或蓝牙硬件的条件下获取不到
  • 如果WiFi没有打开过,也不能获取到硬件地址;蓝牙只有在打开的时候才能获取到硬件地址
  • 不稳定,Google官方对这个方法限制越来越严,后面新版本可能就取不到了

3. ANDROID_ID

设备首次启动时,系统会随机生成一个64位的数字,并将这个数字以16进制的形式保存下来,flutter官方组件device_info就是通过这个方式获取的,获取方式:

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

局限性:

  • 厂商定制的bug,不同的设备可能会产生相同的ANDROID_ID:9774d56d682e549c,甚至有些设备会返回null
  • 设备被重置以后会重新生成
  • AndroidID会根据APP的签名来生成,这也就导致不同的APP获取到的AndroidID是不一致的

4. 设备序列号

获取方式:

String serial = android.os.Build.SERIAL;

局限性:

  • 厂商不规范,设备序列号+Build.MANUFACTURER应该是能唯一标识设备的,但并不是所有厂商都按照这个规范来做的,尤其是早期的设备
  • Android 8.0 以上,android.os.Build.SERIAL 总返回 “unknown”;若要获取序列号,可调用Build.getSerial() ,但是需要申请READ_PHONE_STATE 权限,这个权限之前介绍了,很多用户会拒绝授权
  • 到了Android10以上,跟IMEI一样被禁用了

结合以上几点来看,单独采用其中某一个方案都不是很完美,所以移动安全联盟MSA搞了一个OAID,这个本质上也是一个设备的唯一标识,目前已经支持的厂商包括:华为、小米、OPPO、vivo、中兴、努比亚、魅族、联想、三星等

5. OAID

目前已经开发完成,项目地址:https://gitlab.xsyxsc.com/xsfe_flutter/application/plugin/xsyx_mitt_plugin

支持终端版本:

厂商 版本
小米 MIUI10.2 及以上
vivo FuntouchOS 9 及以上
华为 全版本
OPPO Color OS 7.0 及以上
Lenovo ZUI 11.4 及以上
华硕 Android Q
魅族 已支持(具体版本号查询官网)
三星 已支持(具体版本号查询官网)
中兴 已支持(具体版本号查询官网)
努比亚 已支持(具体版本号查询官网)

插件说明:OAID支持Android10以上的设备,以前的老设备以及没有更新的设备获取不到。获取到OAID为空的情况下,会自动尝试获取IMEI号,如果用户没有授权或者获取不到IMEI的情况下,会尝试获取MAC地址,再获取不到的时候采用兜底方案AndroidID

获取顺序:
OAID -> IMEI(需要授权) -> MAC地址 -> AndroidID

你可能感兴趣的:(Android设备唯一ID实现方案)