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
參考
漫談唯一設備ID(link2)
Android設備唯一標識的獲取和構造
總結
以上是生活随笔為你收集整理的Android唯一设备ID的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神清气爽海波东高燃剪辑(神清气爽)
- 下一篇: 通俗易懂的电脑科普知识通俗易懂的电脑科普