如何唯一的标识一台Android设备?

Oct 24, 2016



唯一的标识一台Android设备

几个概念:

  • UUID : (Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得)。

  • IMEI : (International Mobile Equipment Identity) 是国际移动设备身份码的缩写,国际移动装备辨识码,是由15位数字组成的”电子串号”,它与每台手机一一对应,而且该码是全世界唯一的。

  • MEID :( Mobile Equipment IDentifier )是全球唯一的56bit CDMA制式移动终端标识号。标识号会被烧入终端里,并且不能被修改。可用来对CDMA制式移动式设备进行身份识别和跟踪。

两者的区别在于:IMEI是手机的身份证,MEID是CDMA制式(电信运营的)的专用身份证;IMEI是15位,MEID是14位。

  • DEVICE_ID

根据不同的手机设备返回IMEI,MEID或者ESN码,可以根据以下代码获得:

    device_id = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();

缺陷:
- 非手机设备:最开始搭载Android系统都手机设备,而现在也出现了非手机设备:如平板电脑、电视、音乐播放器等。这些设备没有通话的硬件功能,系统中也就没有TELEPHONY_SERVICE,自然也就无法通过上面的方法获得DEVICE_ID。
- 权限问题:获取DEVICE_ID需要READ_PHONE_STATE权限,在Android 6.0上使用运行时动态授予权限的机制,一旦用户不给予授权,将获取不到DEVICE_ID。 
- 厂商定制系统中的Bug:少数手机设备上,由于该实现有漏洞,会返回垃圾,如:zeros或者asterisks。

补充:
MEID:Mobile Equipment IDentifier(MEID)是全球唯一的56bit移动终端标识号。标识号会被烧入终端里,并且不能被修改。可用来对移动式设备进行身份识别和跟踪。MEID主要分配给CDMA制式的手机。
IMEI:IMEI(International Mobile Equipment Identity)是国际移动设备身份码的缩写,国际移动装备辨识码,是由15位数字组成的"电子串号",它与每台手机一一对应。IMEI码由GSM(全球移动通信协会)统一分配。
ESN:ESN是电子序列号Electronic Serial Number的缩写。它是一个32bits长度的参数,是手机的惟一标识。
  • MAC ADDRESS

      WifiManager wm = (WIfiManager)Context.getsystemService(Context.WIFI_SERVICE);
      return wm.getConnectionInfo().getMacAddress();
    
      可以使用手机WiFi或蓝牙的MAC地址作为设备标识,但是并不推荐这么做,原因有以下两点:
      - 硬件限制:并不是所有的设备都有WiFi和蓝牙硬件,硬件不存在自然也就得不到这一信息。
      - 获取的限制:如果WiFi没有打开过,是无法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。
    
  • Serial Number

硬件序列,在Android 2.2 以上可以通过 android.os.Build.SERIAL 获得序列号。在一些没有电话功能的设备会提供,某些手机上也可能提供(所以就是经常会返回Unknown)

  • ANDROID_ID

ANDROID_ID是设备第一次启动时产生和存储的64bit的一个数,当设备被wipe后该数重置。

    androidID = "" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

问题:
ANDROID_ID似乎是获取Device ID的一个好选择,但它也有缺陷:在主流厂商生产的设备上,有一个很经常的bug,就是每个设备都会产生相同的ANDROID_ID:9774d56d682e549c 。同时刷机,或者重置ANDROID_ID的值都会变化。

实际的标识方法

  • deviceId:

      public static String getDeviceId(Context context){
    
          String deviceid_key = "share_prefrence_device_id";
          MySharedPreference sp = new MySharedPreference(context);
          String deviceIdString = sp.getKeyStr(deviceid_key);
    
          if("".equals(deviceIdString)){
              String device_id = null;
              boolean random = false;
              try {
                  device_id = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
              }catch (Exception e){
                  e.printStackTrace();
                  random = true;
                  device_id = new Random().nextInt(16)+"";
              }
              String device_id_md5 = MD5Util.MD5Encode(device_id, "UTF-8");
              if(random){
                  device_id_md5 = "rd_"+device_id_md5;
              }
              sp.setKeyStr(deviceid_key, device_id_md5);
              return device_id_md5;
          }else{
              return deviceIdString;
          }
      }
    
  • Universal ID/Android ID

      public static String getDeviceId(Context context){
    
          String deviceid_key = "share_prefrence_device_id";
          MySharedPreference sp = new MySharedPreference(context);
          String deviceIdString = sp.getKeyStr(deviceid_key);
    
          if ("".equals(deviceIdString)) {
              String androidID;
              try {
                  androidID = "" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
                  if ("".equals(androidID) || "9774d56d682e549c".equals(androidID)) {
                      deviceIdString = UUID.randomUUID().toString();
                  }
                  else {
                      deviceIdString = UUID.nameUUIDFromBytes(androidID.getBytes("utf8")).toString();
                  }
    
              }catch (Exception e) {
                  e.printStackTrace();
                  deviceIdString = UUID.randomUUID().toString();
              }
                
              String device_id_md5 = MD5Util.MD5Encode(deviceIdString, "UTF-8");
              sp.setKeyStr(deviceid_key, device_id_md5);
              return device_id_md5;
          }else {
                
              return deviceIdString;
          }
      }
    

UUID操作类

    public class UniversalID {

        private static String filePath = File.separator + "HappyShopping" + File.separator + "comm";

        public static String getUniversalID(Context context) {
            String androidId;
            String fileRootPath = getPath(context) + filePath;
            String uuid = FileUtils.readFile(fileRootPath);
            if (uuid == null || uuid.equals("")) {
                androidId = "" + Settings.Secure.getString(context.getContentResolver(),
                        Settings.Secure.ANDROID_ID);
                try {
                    if ("".equals(androidID) || "9774d56d682e549c".equals(androidID)) {
                        uuid = UUID.randomUUID().toString();
                    }
                    else {
                        uuid = UUID.nameUUIDFromBytes(androidID.getBytes("utf8")).toString();
                    }
                } catch (Exception e) {
                    uuid = UUID.randomUUID().toString();
                }

                String uuid_md5 = MD5Util.MD5Encode(uuid, "UTF-8");
                if(!uuid.equals("")){
                    saveUUID(context,uuid_md5);
                }

                return uuid_md5;
            }else {
                return uuid;
            }
        }

        public static void saveUUID(Context context, String UUID) {
            String ExternalSdCardPath = getExternalSdCardPath() + filePath;
            FileUtils.writeFile(ExternalSdCardPath, UUID);
            String InnerPath = context.getFilesDir().getAbsolutePath() + filePath;
            FileUtils.writeFile(InnerPath,UUID);
        }

        public static String getPath(Context context) {
            //首先判断是否有外部存储卡,如没有判断是否有内部存储卡,如没有,继续读取应用程序所在存储
            String phonePicsPath = getExternalSdCardPath();
            if (phonePicsPath == null) {
                phonePicsPath = context.getFilesDir().getAbsolutePath();
            }
            return phonePicsPath;
        }

        /**
         * 遍历 "system/etc/vold.fstab” 文件,获取全部的Android的挂载点信息
         *
         * @return
         */
        private static ArrayList<String> getDevMountList() {
            String[] toSearch = FileUtils.readFile("/system/etc/vold.fstab").split(" ");
            ArrayList<String> out = new ArrayList<>();
            for (int i = 0; i < toSearch.length; i++) {
                if (toSearch[i].contains("dev_mount")) {
                    if (new File(toSearch[i + 2]).exists()) {
                        out.add(toSearch[i + 2]);
                    }
                }
            }
            return out;
        }

        /**
         * 获取扩展SD卡存储目录
         * <p/>
         * 如果有外接的SD卡,并且已挂载,则返回这个外置SD卡目录
         * 否则:返回内置SD卡目录
         *
         * @return
         */
        public static String getExternalSdCardPath() {

            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                File sdCardFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
                return sdCardFile.getAbsolutePath();
            }

            String path = null;

            File sdCardFile = null;

            ArrayList<String> devMountList = getDevMountList();

            for (String devMount : devMountList) {
                File file = new File(devMount);

                if (file.isDirectory() && file.canWrite()) {
                    path = file.getAbsolutePath();

                    String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmmss").format(new Date());
                    File testWritable = new File(path, "test_" + timeStamp);

                    if (testWritable.mkdirs()) {
                        testWritable.delete();
                    } else {
                        path = null;
                    }
                }
            }

            if (path != null) {
                sdCardFile = new File(path);
                return sdCardFile.getAbsolutePath();
            }

            return null;
        }

    }

参考文章:如何唯一的标识一台Android设备?


结语:

坚持每天进步一点点…