前言
從 Android 10 開始,應(yīng)用必須具有 READ_PRIVILEGED_PHONE_STATE 特許權(quán)限才能訪問設(shè)備的不可重置標(biāo)識符(包含 IMEI 和序列號)。
而這個權(quán)限是系統(tǒng)權(quán)限,也就是說一般應(yīng)用將無法再獲取IMEI 和序列號
受影響的方法包括:
-
Build
- getSerial()
-
TelephonyManager
- getImei()
- getDeviceId()
- getMeid()
- getSimSerialNumber()
- getSubscriberId()
如果您的應(yīng)用沒有該權(quán)限,但您仍嘗試查詢不可重置標(biāo)識符的相關(guān)信息,則平臺的響應(yīng)會因目標(biāo) SDK 版本而異:
- 如果應(yīng)用以 Android 10 或更高版本為目標(biāo)平臺,則會發(fā)生 SecurityException。
- 如果應(yīng)用以 Android 9(API 級別 28)或更低版本為目標(biāo)平臺,則相應(yīng)方法會返回 null 或占位符數(shù)據(jù)(如果應(yīng)用具有 READ_PHONE_STATE 權(quán)限)。否則,會發(fā)生 SecurityException。
google也給出了一個解決方案
許多使用場景都不需要不可重置的設(shè)備標(biāo)識符。例如,如果您的應(yīng)用將不可重置的設(shè)備標(biāo)識符用于廣告跟蹤或用戶分析目的,請為這些特定使用場景使用 Android 廣告 ID。要了解詳情,請參閱唯一標(biāo)識符的最佳做法。
這里大部分方案對國內(nèi)無效,比如廣告ID,需要google play的服務(wù),但是國內(nèi)的手機上都閹割掉了。所以我們只能參考一些可用的方案。
官方唯一標(biāo)識符建議
這部分我們一起來看官方唯一標(biāo)識的建議
使用廣告 ID
國內(nèi)就不要考慮了,需要依賴google play服務(wù)
使用實例 ID 和 GUID
只對單一應(yīng)用有效,卸載了就變了,不可取。
不要使用 MAC 地址
MAC 地址具有全局唯一性,無法由用戶重置,在恢復(fù)出廠設(shè)置后也不會變化。因此,一般不建議使用 MAC 地址進行任何形式的用戶標(biāo)識。運行 Android 10(API 級別 29)和更高版本的設(shè)備會報告不是設(shè)備所有者應(yīng)用的所有應(yīng)用的隨機化 MAC 地址。
在 Android 6.0(API 級別 23)到 Android 9(API 級別 28)中,無法通過第三方 API 使用 Wi-Fi 和藍牙等本地設(shè)備 Mac 地址。WifiInfo.getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法都返回 02:00:00:00:00:00。
此外,在 Android 6.0 到 Android 9 版本中,您還必須擁有下列權(quán)限,才能訪問通過藍牙和 Wi-Fi 掃描獲得的附近外部設(shè)備的 MAC 地址:
方法/屬性 | 所需權(quán)限 |
---|---|
WifiManager.getScanResults() | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION |
BluetoothDevice.ACTION_FOUND | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION |
BluetoothLeScanner.startScan(ScanCallback) | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION |
所以,mac是僅次于DeviceId的靠譜的標(biāo)識,不過android 6.0之后獲取不到了。不過有其他方法完善,見后面。
標(biāo)識符特性
一堆廢話
常見用例和適用的標(biāo)識符
也是一堆廢話,要么就是國內(nèi)無法使用,不過提到了SSAID。
SSAID,即ANDROID_ID(Settings.Secure.ANDROID_ID),在8.0系統(tǒng)迎來改變,具體如下:
對于在 OTA 之前安裝到某個版本 Android 8.0(API 級別 26)的應(yīng)用,除非在 OTA 后卸載并重新安裝,否則 ANDROID_ID 的值將保持不變。要在 OTA 后在卸載期間保留值,開發(fā)者可以使用密鑰/值備份關(guān)聯(lián)舊值和新值。
對于安裝在運行 Android 8.0 的設(shè)備上的應(yīng)用,ANDROID_ID 的值現(xiàn)在將根據(jù)應(yīng)用簽署密鑰和用戶確定作用域。應(yīng)用簽署密鑰、用戶和設(shè)備的每個組合都具有唯一的 ANDROID_ID 值。因此,在相同設(shè)備上運行但具有不同簽署密鑰的應(yīng)用將不會再看到相同的 Android ID(即使對于同一用戶來說,也是如此)。
只要簽署密鑰相同(并且應(yīng)用未在 OTA 之前安裝到某個版本的 O),ANDROID_ID 的值在軟件包卸載或重新安裝時就不會發(fā)生變化。
即使系統(tǒng)更新導(dǎo)致軟件包簽署密鑰發(fā)生變化,ANDROID_ID 的值也不會變化。
可以看到8.0之后ANDROID_ID是與應(yīng)用簽名關(guān)聯(lián)的,同簽名的應(yīng)用共用相同的ANDROID_ID,而且卸載重裝不會變化。
而8.0之前,ANDROID_ID是與設(shè)備關(guān)聯(lián)的,當(dāng)設(shè)備首次啟動時,系統(tǒng)會隨機生成一個64位的數(shù)字,并以16進制字符串的形式保存到手機系統(tǒng)中,當(dāng)手機恢復(fù)出廠設(shè)置后,Android ID會被重置,這是Android ID與Device ID的主要區(qū)別。當(dāng)然還有其他bug,比如有些廠家獲取為null之類的。
所以,ANDROID_ID是可以考慮的選擇之一,后面細說。
解決方案
想要一個行為獲取穩(wěn)定的DeviceId是不可能的,我們需要多個行為結(jié)合處理。
DeviceId
首先就是傳統(tǒng)的DeviceId,在Android 10一下還是很穩(wěn)定的。
ANDROID_ID
在Android 8.0之后,就可以考慮用ANDROID_ID來代替DeviceId了。
Settings.System.getString(BaseApp.getAppContext().getContentResolver(), Settings.Secure.ANDROID_ID);
這樣可以做一個版本判斷,低于10.0(或8.0)獲取DeviceId,否則獲取ANDROID_ID
Mac地址
如果上面兩步獲取的還是null,那么可以使用mac地址,但是mac由于6.0之后無法通過WifiInfo.getMacAddress()獲取了,所以我們需要處理一下,代碼如下:
public static String getMac(Context context) {
String mac = "";
if (context == null) {
return mac;
}
if (Build.VERSION.SDK_INT < 23) {
mac = getMacBySystemInterface(context);
} else {
mac = getMacByJavaAPI();
if (TextUtils.isEmpty(mac)){
mac = getMacBySystemInterface(context);
}
}
return mac;
}
@TargetApi(9)
private static String getMacByJavaAPI() {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface netInterface = interfaces.nextElement();
if ("wlan0".equals(netInterface.getName()) || "eth0".equals(netInterface.getName())) {
byte[] addr = netInterface.getHardwareAddress();
if (addr == null || addr.length == 0) {
return null;
}
StringBuilder buf = new StringBuilder();
for (byte b : addr) {
buf.append(String.format("%02X:", b));
}
if (buf.length() > 0) {
buf.deleteCharAt(buf.length() - 1);
}
return buf.toString().toLowerCase(Locale.getDefault());
}
}
} catch (Throwable e) {
}
return null;
}
private static String getMacBySystemInterface(Context context) {
if (context == null) {
return "";
}
try {
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if (checkPermission(context, Manifest.permission.ACCESS_WIFI_STATE)) {
WifiInfo info = wifi.getConnectionInfo();
return info.getMacAddress();
} else {
return "";
}
} catch (Throwable e) {
return "";
}
}
可以看到6.0即23以下直接獲取,否則先通過NetworkInterface獲取,獲取不到再通過原方法獲取。
目前來看這一步還是能穩(wěn)定獲取的。
UUID
兜底行為。因為需要我們手動生成,且每次生成的都不一樣。
UUID.randomUUID().toString()
所以必須生成一次保存起來。這樣就有一個問題,如果保存到應(yīng)用內(nèi)部存儲,卸載后重裝一定要重新生成,這樣就無法判斷是同一設(shè)備了。
所以最好將其保存到外部存儲,保證卸載重裝后還能讀取到上次的值。
這樣一般情況下是最穩(wěn)定的,除非手動刪除該文件。
所以最好的方案,就是將上面四個方案融合在一起,一個個兜底。目前來看,各手機廠商的指導(dǎo)方案也就這幾個方案。
補充
除了上面的方案,還有移動安全聯(lián)盟(信通院牽頭)提供的sdk,可以獲取幾種設(shè)備標(biāo)識符,大部分國內(nèi)廠商都支持。
不過需要申請使用,還沒測試過。
總結(jié)
通過上面分析可以看到,官方確實給出了不少替代方案,但是大部分都由于國內(nèi)的限制而無法使用。所以國內(nèi)基本上都是通過依次獲取DeviceId、ANDROID_ID、MAC、UUID的方式來得到一個唯一id,流程大致如下:
你可能感興趣:
Android 13發(fā)布,一起來看看有哪些新功能文章來源:http://www.zghlxwxcb.cn/news/detail-597940.html
詳細解讀Android中的事件分發(fā)機制文章來源地址http://www.zghlxwxcb.cn/news/detail-597940.html
到了這里,關(guān)于Android 如何獲取有效的DeviceId的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!