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<NetworkInterface> 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容