Android 常见获取设备标识方法总结

 更新时间:2024年09月25日 15:19:36   作者:ChenYhong  
随着Android系统版本更新,Google对用户隐私保护增强,限制获取设备标识,文中测试DeviceID、ANDROID_ID、Serial、MAC地址等方法在不同API级别的表现,感兴趣的朋友跟随小编一起看看吧

在最近的开发工作中,有个功能点需要获取设备标识作为用户ID,理想的标识应该是不可重置的。然而,在Android系统版本的不断迭代中,Google对用户隐私的保护力度持续加强,针对获取设备标识方法的限制也越来越严格,官方建议尽量选用用户可重置的唯一标识,例如AAID。

为了尽量满足需求,对以往常见的四种获取设备标识的方法进行了测试,本文对测试结果进行记录。

获取设备标识

DeviceID(IMEI、MEID)

稳定的标识符,即使恢复出厂设置也不会被重置。在不同版本的Android设备上,获取DeviceID的方法及效果略有不同,下面分别介绍一下。

API 23-28

AndroidManifest中配置READ_PHONE_STATE权限,代码如下:

<?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)的设备上,可以通过TelephonyManager.getDeviceId()方法获取DeviceID。 另外在API 26(Android8.0)或更高版本的设备上,还可以通过TelephonyManager.getImei()TelephonyManager.getMeid()获取设备标识。需要在运行时向用户申请授予READ_PHONE_STATE权限。示例代码如下:

class DeviceIdExampleActivity : AppCompatActivity() {
    private lateinit var binding: LayoutDeviceIdExampleActivityBinding
    private val deviceIdPermissionRequestLauncher = registerForActivityResult(/* contract = */ ActivityResultContracts.RequestPermission()) {
        // 无论是否授权,都进行获取,作对比
        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 {
                // 设备类型为GSM且有权限获取时不为空
                deviceIDStrBuilder.append("\nIMEI:").append(telephonyManager.imei)
            } catch (e: Exception) {
                e.printStackTrace()
            }
            try {
                // 设备类型为CDMA且有权限获取时不为空
                deviceIDStrBuilder.append("\nMEID:").append(telephonyManager.meid)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        binding.tvResultValue.text = deviceIDStrBuilder
    }
}

测试设备为Android Studio模拟器,API为28,测试效果如下:

API 29以上

从API 29(Android10.0)开始,即使用户授予READ_PHONE_STATE权限,上述三种方法返回值均为空字符串。

测试设备为Android Studio模拟器,API为29,测试效果如下:

在API 29以上也有仍能获取Device ID的方法,需要与运营商或者手机厂商有深度合作,具体可以参考官方文档

ANDROID_ID

设备首次启动时生成,恢复出厂设置时会被重置,无需任何权限即可获取。获取代码如下:

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)}"
        }
    }
}

测试设备为Pixel 7,API为34,测试效果如下:

需要注意的是,根据查询到的资料来看,不同厂商的设备可能会出现返回空值或者返回值相同的情况。另外,ANDROID_ID可以手动进行修改

Serial(序列号)

设备硬件序列号,通常在没有root的情况下不会重置,在不同版本的Android设备上,获取Serial的方法及效果略有不同,下面分别介绍一下。

API 23-25

在API 23-25版本的设备上无需配置权限,获取代码如下:

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()
            }
        }
    }
}

测试设备为Android Studio模拟器,API为25,测试效果如下:

API 26-28

从API 26(Android8.0)开始,android.os.Build.SERIAL总是返回UNKNOWN,改为使用android.os.Build.getSerial()方法获取,另外还需配置READ_PHONE_STATE权限并且在运行时动态申请用户授权。示例代码如下:

class DeviceIdExampleActivity : AppCompatActivity() {
    private lateinit var binding: LayoutDeviceIdExampleActivityBinding
    private val serialPermissionRequestLauncher = registerForActivityResult(/* contract = */ ActivityResultContracts.RequestPermission()) {
        // 无论是否授权,都进行获取,作对比
        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
    }
}

测试设备为Android Studio模拟器,API为28,测试效果如下:

API 29以上

从API 29(Android10.0)开始,获取Serial和获取DeviceID一样,文中列举的方法无法正常获取,但官方也提供了相应的解决方法。

测试设备为Android Studio模拟器,API为29,测试效果如下:

MAC地址

用于标识网络接口的硬件地址,通常在设备出厂时分配。有root权限时可以进行修改,因此存在重复的可能性。在不同版本的Android设备上,获取MAC地址的效果略有不同,下面分别介绍一下。

API 23-28

使用NetworkInterface.getNetworkInterfaces()方法来获取MAC地址,无需任何权限,示例代码如下:

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
                }
            }
        }
    }
}

测试设备为samsung Galaxy S8,API为28,测试效果如下:

关闭WI-FI

WI-FI

热点

API 29

获取代码和上面一致,但是从API 29(Android10.0)开始,连接不同的WI-FI时会获取到不同的MAC地址。

测试设备为samsung Galaxy S9,API为29,测试效果如下:

WI-FI

热点

API 30以上

从API 30(Android11.0)开始,已无法获取到MAC地址。

测试设备为Pixel 7,API为34,测试效果如下:

小结

文中测试的四种设备标识,确定不会重复的应该只有DeviceId和MAC地址,但是二者从API 29开始也都不可靠了。当然,在市面上仍然有不少API 29以下的设备,在选择唯一标识方案时,低版本的设备仍然可以采用DeviceID或MAC地址,高版本则采用官方推荐的AAID等方案。

到此这篇关于Android 常见获取设备标识方法现状的文章就介绍到这了,更多相关Android设备标识内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android仿QQ空间顶部条背景变化效果

    Android仿QQ空间顶部条背景变化效果

    这篇文章主要介绍了Android仿QQ空间顶部条背景变化效果 ,qq空间的这个页面其实很简单,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-04-04
  • Android自定义view实现圆形、圆角和椭圆图片(BitmapShader图形渲染)

    Android自定义view实现圆形、圆角和椭圆图片(BitmapShader图形渲染)

    这篇文章运用实例代码介绍如何在Android中自定义view,使用BitmapShader图形渲染方法来实现圆形、圆角和椭圆的绘制,有需要的可以参考借鉴。
    2016-08-08
  • Flutter 页面跳转和传值的实现

    Flutter 页面跳转和传值的实现

    跳转传值是再普通不过的小功能了,在开发中会经常用到,比如列表进入详情,本文主要介绍了Flutter 页面跳转和传值的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-04-04
  • Android中实现圆角图片的几种方法

    Android中实现圆角图片的几种方法

    本篇文章主要介绍了Android中实现圆角图片的几种方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • 详解android异步更新UI的几种方法

    详解android异步更新UI的几种方法

    本篇文章主要介绍了详解android异步更新UI的几种方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Android识别NFC芯片制造商的方法

    Android识别NFC芯片制造商的方法

    这篇文章介绍了Android识别NFC芯片制造商的方法,文中通过示例代码介绍的非常详细。对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-11-11
  • android TextView属性的详细介绍 分享

    android TextView属性的详细介绍 分享

    android TextView属性的详细介绍 分享,需要的朋友可以参考一下
    2013-05-05
  • Android AlertDialog(对话框)实例详解

    Android AlertDialog(对话框)实例详解

    Android在开发中经常会遇到有弹框的需求,经常使用的有Dialog弹框,Window弹框,他们之间最本质的区别是dialog是非阻塞式对话框,popupwindow是阻塞式对话框,这篇文章主要给大家介绍了关于Android AlertDialog(对话框)的相关资料,需要的朋友可以参考下
    2021-11-11
  • Android编程使用Intent传递对象的方法分析

    Android编程使用Intent传递对象的方法分析

    这篇文章主要介绍了Android编程使用Intent传递对象的方法,结合实例形式详细分析了Android使用Intent实现传递对象的相关技巧与注意事项,需要的朋友可以参考下
    2016-01-01
  • Android中对RecyclerView Adapter封装解析

    Android中对RecyclerView Adapter封装解析

    本篇文章主要介绍了Android中对RecyclerView Adapter封装解析。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06

最新评论