zoukankan      html  css  js  c++  java
  • Android唯一设备ID

    设备ID,简单来说就是一串符号(或者数字),映射现实中硬件设备。如果这些符号和设备是一一对应的,可称之为“唯一设备ID(Unique Device Identifier)”

    不幸的是,对于Android平台而言,没有稳定的API可以让开发者获取到这样的设备ID。

    开发者通常会遇到这样的困境:随着项目的演进, 越来越多的地方需要用到设备ID;然而随着Android版本的升级,获取设备ID却越来越难了。

    加上Android平台碎片化的问题,获取设备ID之路,可以说是步履维艰。

    获取设备标识的API屈指可数,而且都或多或少有一些问题。

    IMEI

    IMEI本该最理想的设备ID,具备唯一性,恢复出厂设置不会变化(真正的设备相关),可通过拨打*#06# 查询手机的imei码。

    然而,获取IMEI需要 READ_PHONE_STATE 权限,估计大家也知道这个权限有多麻烦了。

    尤其是Android 6.0以后, 这类权限要动态申请,很多用户可能会选择拒绝授权。我们看到,有的APP不授权这个权限就无法使用, 这可能会降低用户对APP的好感度。

    而且,Android 10.0 将彻底禁止第三方应用获取设备的IMEI(即使申请了 READ_PHONE_STATE 权限)。所以,如果是新APP,不建议用IMEI作为设备标识;

    如果已经用IMEI作为标识,要赶紧做兼容工作了,尤其是做新设备标识和IMEI的映射。

    设备序列号

    在Android 7.1或更早系统(SDK<=25),java可通过android.os.Build.SERIAL获得,由厂商提供。

    如果厂商比较规范的话,设备序列号+Build.MANUFACTURER应该能唯一标识设备。但现实是并非所有厂商都按规范来,尤其是早期的设备。

    最致命的是,Android 8.0及 以上(SDK>=26),android.os.Build.SERIAL 总返回 “unknown”;若要获取序列号,可调用Build.getSerial() ,但是需要申请 READ_PHONE_STATE 权限。

    到了Android 10.0(SDK>=29)以上,则和IMEI一样,也被禁止获取了。

    在UE4中,使用C++代码实现如下:

    int GetPublicStaticInt(const char *className, const char *fieldName)
    {
    #if PLATFORM_ANDROID
        JNIEnv* env = FAndroidApplication::GetJavaEnv();
        if (env != NULL)
        {
            jclass clazz = env->FindClass(className);
            if (clazz != nullptr) {
                jfieldID fid = env->GetStaticFieldID(clazz, fieldName, "I");
                if (fid != nullptr) {
                    return env->GetStaticIntField(clazz, fid);
                }
            }
        }
    #endif
    
        return 0;
    }
    
    FString GetPublicStaticString(const char *className, const char *fieldName)
    {
    #if PLATFORM_ANDROID
        JNIEnv* env = FAndroidApplication::GetJavaEnv();
        if (env != NULL)
        {
            jclass clazz = env->FindClass(className);
            if (clazz != nullptr) {
                jfieldID fid = env->GetStaticFieldID(clazz, fieldName, "Ljava/lang/String;");
                if (fid != nullptr) {
                    jstring content = (jstring)env->GetStaticObjectField(clazz, fid);
                    return ANSI_TO_TCHAR(env->GetStringUTFChars(content, 0));
                }
            }
        }
    #endif
    
        return FString();
    }
    
    FString GetStaticMethodNoParametersRetString(const char *className, const char *fieldName)
    {
         JNIEnv* env = FAndroidApplication::GetJavaEnv();
         if (env != NULL)
         {
             jclass clazz = env->FindClass(className);
             if (clazz != nullptr) {
                // ()中为参数的类型列表,为空表示没有参数
                // Ljava/lang/String;为返回值类型
                 jmethodID fid = (env)->GetStaticMethodID(clazz, "fieldName", "()Ljava/lang/String;");
                if (fid != NULL)
                {
                     jstring content = (jstring)((Env)->CallStaticObjectMethod(clazz, fid));
                    serial = ANSI_TO_TCHAR(env->GetStringUTFChars(content, 0));
                 }
             }
         }
    }
    
    FString serial = TEXT("")
    int sdk = GetPublicStaticInt("android/os/Build$VERSION", "SDK_INT");
    if (sdk >= 29 ) // Android Q(>= SDK 29)
    {
        
    }
    else if (sdk >= 26) // Android 8 and later (>= SDK 26)
    {
        serial = GetStaticMethodNoParametersRetString("android/os/Build", "getSerial");
    }
    else //Android 7.1 and earlier(<= SDK 25)
    {
        serial = GetPublicStaticString("android/os/Build", "SERIAL");
    }

    总体来说,设备序列号有点鸡肋:食之无味,弃之可惜。


    MAC地址

    大多android设备都有wifi模块,因此,wifi模块的MAC地址就可以作为设备标识。基于隐私考虑,官方不建议获取

    获取MAC地址也是越来越困难了,Android 6.0以后通过 WifiManager 获取到的mac将是固定的:02:00:00:00:00:00 

    7.0之后读取 /sys/class/net/wlan0/address 也获取不到了(小米6)。

    如今只剩下面这种方法可以获取(没有开启wifi也可以获取到):

    public static String getWifiMac() {
        try {
            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
            if (enumeration == null) {
                return "";
            }
            while (enumeration.hasMoreElements()) {
                NetworkInterface netInterface = enumeration.nextElement();
                if (netInterface.getName().equals("wlan0")) {
                    return formatMac(netInterface.getHardwareAddress());
                }
            }
        } catch (Exception e) {
            Log.e("tag", e.getMessage(), e);
        }
        return "";
    }

    再往后说不准这种方法也行不通了,且用且珍惜~


    ANDROID_ID

    Android ID 是获取门槛最低的,不需要任何权限,64bit 的取值范围,唯一性算是很好的了。

    但是不足之处也很明显:

    1、刷机、root、恢复出厂设置等会使得 Android ID 改变;
    2、Android 8.0之后,Android ID的规则发生了变化

    对于升级到8.0之前安装的应用,ANDROID_ID会保持不变。如果卸载后重新安装的话,ANDROID_ID将会改变。
    对于安装在8.0系统的应用来说,ANDROID_ID根据应用签名和用户的不同而不同。ANDROID_ID的唯一决定于应用签名、用户和设备三者的组合。

    两个规则导致的结果就是:
    第一,如果用户安装APP设备是8.0以下,后来卸载了,升级到8.0之后又重装了应用,Android ID不一样;
    第二,不同签名的APP,获取到的Android ID不一样。
    其中第二点可能对于广告联盟之类的有所影响(如果彼此是用Android ID对比数据的话),所以Google文档中说“请使用Advertising ID”,
    不过大家都知道,Google的服务在国内用不了。
    对Android ID做了约束,对隐私保护起到一定作用,并且用来做APP自己的活跃统计也还是没有问题的。

    用硬件信息拼凑ID

    优点是不需要额外权限,缺点是唯一性不能百分百确保

    如下为一台小米10的硬件相关信息

    BOARD: umi
    BRAND: Xiaomi
    DEVICE: umi
    DISPLAY: QKQ1.191117.002 test-keys
    HOST: c5-miui-ota-bd074.bj
    ID: QKQ1.191117.002
    MANUFACTURER: Xiaomi
    MODEL: Mi 10
    PRODUCT: umi
    TAGS: release-keys
    TYPE: user
    USER: builder

    java代码实现如下:

    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    public class MD5Tool {
        private final static String[] hexArray = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
    
        /***
         * 获取指定的字符串的MD5
         */
        public static String CalcMD5(String originString) {
            try {
                //创建具有MD5算法的信息摘要
                MessageDigest md = MessageDigest.getInstance("MD5");
                //使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
                byte[] bytes = md.digest(originString.getBytes());
                //将得到的字节数组变成字符串返回
                String s = byteArrayToHex(bytes);
                return s.toLowerCase();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return null;
        }
        /**
         * 将字节数组转换成十六进制,并以字符串的形式返回
         * 128位是指二进制位。二进制太长,所以一般都改写成16进制,
         * 每一位16进制数可以代替4位二进制数,所以128位二进制数写成16进制就变成了128/4=32位。
         */
        private static String byteArrayToHex(byte[] b){
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < b.length; i++) {
                sb.append(byteToHex(b[i]));
            }
            return sb.toString();
        }
        /**
         * 将一个字节转换成十六进制,并以字符串的形式返回
         */
        public static String byteToHex(byte b) {
            int n = b;
            if (n < 0)
                n = n + 256;
            int d1 = n / 16;
            int d2 = n % 16;
            return hexArray[d1]+hexArray[d2];
        }
    }
    
    
    String hardwareInfo = android.os.Build.BOARD + android.os.Build.BRAND + android.os.Build.DEVICE + android.os.Build.DISPLAY 
                + android.os.Build.HOST + android.os.Build.ID + android.os.Build.MANUFACTURER + android.os.Build.MODEL 
                + android.os.Build.PRODUCT + android.os.Build.TAGS + android.os.Build.TYPE +android.os. Build.USER;
    
    string md5 = MD5Tool.CalcMD5(hardwareInfo);

    在UE4中,使用C++代码实现如下:

    FString board = GetPublicStaticString("android/os/Build", "BOARD");
    FString brand = GetPublicStaticString("android/os/Build", "BRAND");
    FString device = GetPublicStaticString("android/os/Build", "DEVICE");
    FString display = GetPublicStaticString("android/os/Build", "DISPLAY");
    FString host = GetPublicStaticString("android/os/Build", "HOST");
    FString id = GetPublicStaticString("android/os/Build", "ID");
    FString manufacturer = GetPublicStaticString("android/os/Build", "MANUFACTURER");
    FString model = GetPublicStaticString("android/os/Build", "MODEL");
    FString product = GetPublicStaticString("android/os/Build", "PRODUCT");
    FString tags = GetPublicStaticString("android/os/Build", "TAGS");
    FString type = GetPublicStaticString("android/os/Build", "TYPE");
    FString user = GetPublicStaticString("android/os/Build", "USER");
    
    FString hardwareInfo = board + brand + device + display + host + id 
                + manufacturer + model + product + tags + type + user;
    FString md5 = FMD5::HashAnsiString(*hardwareInfo)

    UE4引擎java代码所在目录:UnrealEngineEngineBuildAndroidJavasrccomepicgamesue4

    参考

    漫谈唯一设备IDlink2

    Android设备唯一标识的获取和构造

  • 相关阅读:
    误差可视化小结
    快速排序算法
    解决堆损坏的一点心得
    合并两个有序数组
    nginx安装
    Spark官方3 ---------Spark Streaming编程指南(1.5.0)
    【译】Yarn上常驻Spark-Streaming程序调优
    【Kafka】操作命令
    【Kafka】
    Spark组件
  • 原文地址:https://www.cnblogs.com/kekec/p/12544674.html
Copyright © 2011-2022 走看看