AndroidAndroid资料库Android 开发技术交流

谈谈 Android 中的各种设备标识符

2016-04-07  本文已影响8394人  totato

在上一篇谷歌博客 (识别应用安装) 中,谷歌介绍了Android 中一些常用的标识符,并提出了合理识别应用每次安装的办法。指出通过获取设备可靠,唯一,稳定标识符来追踪设备可能产生的错误,并简单介绍了 Android 中一些设备标识符可能存在的问题。今天,我会介绍一下 Android 中的一些标识符以及如何获取它们,以及获取这些标识符过程中可能存在的坑。

标识符(identifier)

设备ID(DeviceId)

android.telephony.TelephonyManager tm = (android.telephony.TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String deviceId = tm.getDeviceId();

Sim 序列号(Sim Serial Number)

android.telephony.TelephonyManager tm = (android.telephony.TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String simSerialNum = tm.getSimSerialNumber();

Mac 地址(Mac Address)

android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) context.getSystemService(Context.WIFI_SERVICE);
String macAddress = wifi.getConnectionInfo().getMacAddress();
读取 mac 地址导致 app 被谷歌框架判定为有害应用

设备序列号(Serial Number, SN)

String serialNum = android.os.Build.SERIAL;

ANDROID_ID

String androidId = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

制造商 (Manufacturer)

String manufacturer = android.os.Build.MANUFACTURER;

型号(Model)

String model = android.os.Build.MODEL;

品牌(Brand)

String brand = android.os.Build.BRAND;

设备名 (Device)

String device = android.os.Build.DEVICE;

以下是我的一台 Nexus 4 所获取的全部值:
以下的值仅作为举例,并非真实

Identifier_Device_ID:    355136021808056
Identifier_Mac_Address: 10:68:3f:81:ed:ff
Identifier_Android_ID:    6ae48d23d1887323
Identifier_Serial_Num:    01b4549262d6a4a2
Identifier_Sim_SN:    898600e6111551111111
Identifier_Manufacturer: LGE
Identifier_Model:    Nexus 4
Identifier_Brand:    google
Identifier_Device:    mako

如何合理使用标识符跟踪设备

介绍完了一些常见的、可能的作为标识符的值,现在来谈谈如何合理地使用这些标识符跟踪设备。

首先,我们先要弄清自己跟踪设备的具体需求。目前看来,需求无非两种:

  1. 跟踪用户设备使用周期层次上的设备。
    意思是将每次用户的擦除设备、恢复出厂设置动作后将设备视为一台新的设备。
  2. 跟踪硬件层次上的设备。
    意思是无论设备擦除数据或者恢复出厂设置后都需要将该设备视为同一台设备。

跟踪用户使用层次上的设备

方案 1:
这个层次上的设备跟踪,我比较推荐使用谷歌官方推荐的办法来跟踪, App 首次启动时生成一个 Random UUID 并保存在本地存储,以后每次启动时检查该 UUID 文件。具体可以查看我的上一篇翻译文章),其中有具体的代码实现。

方案 2:
如果你不喜欢谷歌推荐的这种方式,或者觉得这种方式涉及到文件读写太过繁琐等。我们也可以通过以上介绍的这些标识符来跟踪设备。因为需要将设备擦除数据或恢复出厂设置后将其视为一台新的设备,所以需要使用一些与当前用户设备使用周期有关的值。

理论上,Android_ID 这一个值就已经足够我们实现这样的需求,不过正是因为 Android_ID 存在缺陷,所以我们无法直接拿来识别设备。这里我们使用多个值拼凑来规避这些缺点。
与用户设备使用周期有关的标识符我推荐使用Android_ID和Sim Serial Number。另外可以加上Device_ID,通过 UUID 或者 MD5 等来计算生成设备的标识符。

以下是一个简单的实现,参考了 Stack Overflow 上的这个问题下面的回答

UUID deviceUuid = new UUID(androidId.hashCode(), ((long)deviceId.hashCode() << 32) | simSerialNum.hashCode());
String deviceId = deviceUuid.toString();

结果类似:00000000-54b3-e7c7-0000-000046bffd97

String md5ID = md5(androidId + deviceId + simSerialNum);

结果类似:f87b20b3c359c4af608b3eb26b26a1b8

跟踪硬件层次上的设备

跟踪硬件层次上的设备建议使用硬件的标识符,比如设备ID(DeviceId)、Mac 地址、设备序列号(SN)或者设备的品牌,型号名等,这些值在用户擦除数据或者恢复出厂设置后也不会改变。同样的,为了提升稳定性及排除单一标识符所存在的缺陷,我们使用多个标识符拼接,然后通过 UUID 或者 MD5 算法计算得出我们需要的设备标识符。

以下是一个简单的实现,使用了设备序列号(SN)、设备ID(DeviceId)和 Mac 地址。

拼接后的字符串类似于:01b4549262d6a4a235513602180805610:68:3f:81:ed:ff

同时为了不暴露用户的设备具体信息,这里我们同样采用 MD5 对拼接后的字符串进行Hash操作:

String md5ID = md5("01b4549262d6a4a235513602180805610:68:3f:81:ed:ff");

拼凑的标识符选择,拼接的顺序,MD5或者UUID的选择并无绝对,重要的是思想。

其他

*以下是一个Demo,项目建立后添加权限后即可使用

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        String deviceId = "";
        String macAddress = "";
        String androidId = "";
        String serialNum = "";
        String simSerialNum = "";
 
        //需要READ_PHONE_STATE权限
        android.telephony.TelephonyManager tm = (android.telephony.TelephonyManager) this
                .getSystemService(Context.TELEPHONY_SERVICE);
        if(checkPermission(this, Manifest.permission.READ_PHONE_STATE)){
            deviceId = tm.getDeviceId();
            simSerialNum = tm.getSimSerialNumber();
        }
 
        //需要ACCESS_WIFI_STATE权限
        android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) this
                .getSystemService(Context.WIFI_SERVICE);
        macAddress = wifi.getConnectionInfo().getMacAddress();
 
        androidId = android.provider.Settings.Secure.getString(this.getContentResolver(),
                android.provider.Settings.Secure.ANDROID_ID);
 
        serialNum = Build.SERIAL;
 
        String deviceManufacturer = Build.MANUFACTURER;
        String deviceModel = Build.MODEL;
        String deviceBrand = Build.BRAND;
        String device = Build.DEVICE;
 
        //==============
        Log.e("Identifier_Device_ID", validate(deviceId));
        Log.e("Identifier_Mac_Address", validate(macAddress));
        Log.e("Identifier_Android_ID", validate(androidId));
        Log.e("Identifier_Serial_Num", validate(serialNum));
        Log.e("Identifier_Sim_SN", validate(simSerialNum));
 
        Log.e("Identifier_Manufacturer", validate(deviceManufacturer));
        Log.e("Identifier_Model", validate(deviceModel));
        Log.e("Identifier_Brand", validate(deviceBrand));
        Log.e("Identifier_Device", validate(device));
 
        UUID deviceUserLifetimeUUID = new UUID(validate(androidId).hashCode(), ((long)validate(deviceId).hashCode() << 32) | validate(simSerialNum).hashCode());
        String deviceUserLifetimeId = deviceUserLifetimeUUID.toString();
 
        String deviceHardwareId = md5(validate(serialNum)  + validate(deviceId) + validate(macAddress));;
 
        Log.e("deviceUserLifetimeId", deviceUserLifetimeId);
        Log.e("deviceHardwareId", deviceHardwareId);
    }
 
    // MD5加密,32位小写
    public static String md5(String str) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        md5.update(str.getBytes());
        byte[] md5Bytes = md5.digest();
        StringBuilder hexValue = new StringBuilder();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
 
    //检查权限,READ_PHONE_STATE在API>=23需要用户手动赋予权限
    public static boolean checkPermission(Context context, String permission) {
        boolean result = false;
        if (Build.VERSION.SDK_INT >= 23) {
            if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
                result = true;
            }
        } else {
            PackageManager pm = context.getPackageManager();
            if (pm.checkPermission(permission, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
                result = true;
            }
        }
        return result;
    }
 
    //判空
    private String validate(String value) {
        if(value == null) {
            return "";
        }
        return value;
    }
}

本文章为原创作品,转载请注明出处。
更多 Android 开发相关文章,可以查看本人简书或者本人博客 http://Loyea.com

上一篇下一篇

猜你喜欢

热点阅读