Android?常見獲取設(shè)備標(biāo)識方法總結(jié)
在最近的開發(fā)工作中,有個功能點需要獲取設(shè)備標(biāo)識作為用戶ID,理想的標(biāo)識應(yīng)該是不可重置的。然而,在Android系統(tǒng)版本的不斷迭代中,Google對用戶隱私的保護(hù)力度持續(xù)加強,針對獲取設(shè)備標(biāo)識方法的限制也越來越嚴(yán)格,官方建議盡量選用用戶可重置的唯一標(biāo)識,例如AAID。
為了盡量滿足需求,對以往常見的四種獲取設(shè)備標(biāo)識的方法進(jìn)行了測試,本文對測試結(jié)果進(jìn)行記錄。
獲取設(shè)備標(biāo)識
DeviceID(IMEI、MEID)
穩(wěn)定的標(biāo)識符,即使恢復(fù)出廠設(shè)置也不會被重置。在不同版本的Android設(shè)備上,獲取DeviceID的方法及效果略有不同,下面分別介紹一下。
API 23-28
在AndroidManifest中配置READ_PHONE_STATE權(quán)限,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application
......
>
......
</application>
</manifest>在API 23(Android6.0)到API 28(Android 9.0)的設(shè)備上,可以通過TelephonyManager.getDeviceId()方法獲取DeviceID。 另外在API 26(Android8.0)或更高版本的設(shè)備上,還可以通過TelephonyManager.getImei()或TelephonyManager.getMeid()獲取設(shè)備標(biāo)識。需要在運行時向用戶申請授予READ_PHONE_STATE權(quán)限。示例代碼如下:
class DeviceIdExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutDeviceIdExampleActivityBinding
private val deviceIdPermissionRequestLauncher = registerForActivityResult(/* contract = */ ActivityResultContracts.RequestPermission()) {
// 無論是否授權(quán),都進(jìn)行獲取,作對比
getDeviceID()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutDeviceIdExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
binding.btnGetDeviceId.setOnClickListener {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
deviceIdPermissionRequestLauncher.launch(Manifest.permission.READ_PHONE_STATE)
} else {
getDeviceID()
}
}
}
private fun getDeviceID() {
val telephonyManager = getSystemService(TelephonyManager::class.java)
val deviceIDStrBuilder = StringBuilder()
try {
deviceIDStrBuilder.append("deviceId:").append(telephonyManager.deviceId)
} catch (e: Exception) {
e.printStackTrace()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
// 設(shè)備類型為GSM且有權(quán)限獲取時不為空
deviceIDStrBuilder.append("\nIMEI:").append(telephonyManager.imei)
} catch (e: Exception) {
e.printStackTrace()
}
try {
// 設(shè)備類型為CDMA且有權(quán)限獲取時不為空
deviceIDStrBuilder.append("\nMEID:").append(telephonyManager.meid)
} catch (e: Exception) {
e.printStackTrace()
}
}
binding.tvResultValue.text = deviceIDStrBuilder
}
}測試設(shè)備為Android Studio模擬器,API為28,測試效果如下:

API 29以上
從API 29(Android10.0)開始,即使用戶授予READ_PHONE_STATE權(quán)限,上述三種方法返回值均為空字符串。
測試設(shè)備為Android Studio模擬器,API為29,測試效果如下:

在API 29以上也有仍能獲取Device ID的方法,需要與運營商或者手機廠商有深度合作,具體可以參考官方文檔
ANDROID_ID
設(shè)備首次啟動時生成,恢復(fù)出廠設(shè)置時會被重置,無需任何權(quán)限即可獲取。獲取代碼如下:
class DeviceIdExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutDeviceIdExampleActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutDeviceIdExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
binding.btnGetAndroidId.setOnClickListener {
binding.tvResultValue.text = "ANDROID_ID:${Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)}"
}
}
}測試設(shè)備為Pixel 7,API為34,測試效果如下:

需要注意的是,根據(jù)查詢到的資料來看,不同廠商的設(shè)備可能會出現(xiàn)返回空值或者返回值相同的情況。另外,ANDROID_ID可以手動進(jìn)行修改
Serial(序列號)
設(shè)備硬件序列號,通常在沒有root的情況下不會重置,在不同版本的Android設(shè)備上,獲取Serial的方法及效果略有不同,下面分別介紹一下。
API 23-25
在API 23-25版本的設(shè)備上無需配置權(quán)限,獲取代碼如下:
class DeviceIdExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutDeviceIdExampleActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutDeviceIdExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
binding.btnGetDeviceSerial.setOnClickListener {
try {
binding.tvResultValue.text = "deviceSerial:${Build.SERIAL}"
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}測試設(shè)備為Android Studio模擬器,API為25,測試效果如下:

API 26-28
從API 26(Android8.0)開始,android.os.Build.SERIAL總是返回UNKNOWN,改為使用android.os.Build.getSerial()方法獲取,另外還需配置READ_PHONE_STATE權(quán)限并且在運行時動態(tài)申請用戶授權(quán)。示例代碼如下:
class DeviceIdExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutDeviceIdExampleActivityBinding
private val serialPermissionRequestLauncher = registerForActivityResult(/* contract = */ ActivityResultContracts.RequestPermission()) {
// 無論是否授權(quán),都進(jìn)行獲取,作對比
getSerial()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutDeviceIdExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
binding.btnGetDeviceSerial.setOnClickListener {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
serialPermissionRequestLauncher.launch(Manifest.permission.READ_PHONE_STATE)
} else {
getSerial()
}
}
}
private fun getSerial() {
val serialStrBuilder = StringBuilder()
try {
serialStrBuilder.append("Build.SERIAL:").append(Build.SERIAL)
} catch (e: Exception) {
e.printStackTrace()
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serialStrBuilder.append("\nBuild.getSerial():").append(Build.getSerial())
}
} catch (e: Exception) {
e.printStackTrace()
}
binding.tvResultValue.text = serialStrBuilder
}
}測試設(shè)備為Android Studio模擬器,API為28,測試效果如下:

API 29以上
從API 29(Android10.0)開始,獲取Serial和獲取DeviceID一樣,文中列舉的方法無法正常獲取,但官方也提供了相應(yīng)的解決方法。
測試設(shè)備為Android Studio模擬器,API為29,測試效果如下:

MAC地址
用于標(biāo)識網(wǎng)絡(luò)接口的硬件地址,通常在設(shè)備出廠時分配。有root權(quán)限時可以進(jìn)行修改,因此存在重復(fù)的可能性。在不同版本的Android設(shè)備上,獲取MAC地址的效果略有不同,下面分別介紹一下。
API 23-28
使用NetworkInterface.getNetworkInterfaces()方法來獲取MAC地址,無需任何權(quán)限,示例代碼如下:
class DeviceIdExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutDeviceIdExampleActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutDeviceIdExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
binding.btnGetMacAddress.setOnClickListener {
val networkInterfaces = NetworkInterface.getNetworkInterfaces()
while (networkInterfaces.hasMoreElements()) {
val network = networkInterfaces.nextElement()
if (network.name == "wlan0") {
binding.tvResultValue.text = "macAddress:${network.hardwareAddress?.joinToString(":") { macAddress -> String.format("%02X", macAddress) }}"
break
}
}
}
}
}測試設(shè)備為samsung Galaxy S8,API為28,測試效果如下:
關(guān)閉WI-FI

WI-FI

熱點

API 29
獲取代碼和上面一致,但是從API 29(Android10.0)開始,連接不同的WI-FI時會獲取到不同的MAC地址。
測試設(shè)備為samsung Galaxy S9,API為29,測試效果如下:
WI-FI

熱點

API 30以上
從API 30(Android11.0)開始,已無法獲取到MAC地址。
測試設(shè)備為Pixel 7,API為34,測試效果如下:

小結(jié)
文中測試的四種設(shè)備標(biāo)識,確定不會重復(fù)的應(yīng)該只有DeviceId和MAC地址,但是二者從API 29開始也都不可靠了。當(dāng)然,在市面上仍然有不少API 29以下的設(shè)備,在選擇唯一標(biāo)識方案時,低版本的設(shè)備仍然可以采用DeviceID或MAC地址,高版本則采用官方推薦的AAID等方案。
到此這篇關(guān)于Android 常見獲取設(shè)備標(biāo)識方法現(xiàn)狀的文章就介紹到這了,更多相關(guān)Android設(shè)備標(biāo)識內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Flutter學(xué)習(xí)之Navigator的高級用法分享
這篇文章主要為大家詳細(xì)介紹了Flutter中之Navigator的高級用法的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的可以了解一下2023-02-02
Android App開發(fā)中自定義View和ViewGroup的實例教程
這篇文章主要介紹了Android App開發(fā)中自定義View和ViewGroup的實例教程,分別介紹了進(jìn)度條和圖片上傳并排列的例子,效果很好很強大,需要的朋友可以參考下2016-05-05
Flutter pageview切換指示器的實現(xiàn)代碼
這篇文章主要介紹了Flutter pageview切換指示器的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Android原生側(cè)滑控件DrawerLayout使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android原生側(cè)滑控件DrawerLayout的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
關(guān)于Android 4.4相機預(yù)覽、錄像花屏的問題的解決方法
這篇文章主要介紹了關(guān)于Android 4.4相機預(yù)覽、錄像花屏的問題的解決方法,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2016-12-12

