前言
因为项目中经常会遇到要上传一系列设备信息的功能,为了方便使用,所以就拆分成以下系列文章来单独介绍如何获取各类设备信息
手机运营商获取
AndroidID、IMEI、OAID获取
地理位置信息经纬度获取
公网IP地址获取:移动网络IP、Wifi IP
Build类获取相关设备信息
屏幕相关信息:密度、物理尺寸获取
BuildConfig获取的一系列基础信息
UA、网络状态…等持续更新
1. AndroidID获取
1.1 所需权限
不需要任何权限
1.2 获取方法
private fun getAndroidID() {
val androidID = Settings.System.getString(
contentResolver, Settings.Secure.ANDROID_ID
)
Log.i(TAG, "AndroidID为:$androidID")
}
注意:手机在恢复出厂设置后,这个AndroidID会发生改变,所以用它作为设备的唯一标识不太保险
2. IMEI、MEID获取
2.1 概念了解
讲具体的获取方法前,先大概了解下这是什么东西。
IMEI和MEID其实都是用来标识设备的识别码,不同的是IMEI标识的是支持GSM网络制式的设备,MEID标识的是支持CDMA网络制式的设备。
什么是GSM和CDMA呢? CDMA和GSM简单点说其实就是使用了不同的通信技术,以下表格显示了各大运营商所使用的通信技术
2G | 3G | 4G | |
---|---|---|---|
中国移动 | GSM | TD-SCDMA | TD-LTE |
中国联通 | GSM | WCDMA | TD-LTE/FDD-LTE |
中国电信 | CDMA1X 有时直接写CDMA | CDMA2000 EVDO是中国电信的CDMA2000的3G网络的无线上网模式 | TD-LTE/FDD-LTE |
而现在我们的手机大都是双卡双待,所以这些手机IMEI和MEID号码都有。
大白话讲其实IMEI、MEID就相当于我们手机的身份证号码,唯一标识它,不同的是,有的手机双卡双待,可以同时插入两张支持GSM网络的卡(移动联通、移动移动、联通联通),或一张支持GSM网络的卡一张支持CDMA网络的卡,所以就出现一部手机有两个IMEI号,一个MEID号码
注意:IMEI和MEID是标识手机设备,跟我们手机插什么卡没有关系。
2.2 通过getDeviceId()获取
2.2.1 所需权限
需要动态申请READ_PHONE_STATE
权限
注意:Android10以上禁止获取IMEI,因为需要READ_PRIVILEGED_PHONE_STATE权限,而该权限只能是系统应用才可以获取到。
2.2.2 获取方法
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val IMEI = telManager.deviceId
Log.i(TAG, "IMEI为:${IMEI}")
} else {
Log.i(TAG, "Android10及以上版本禁止获取IMEI")
}
2.2.3 方法解释
从2.2.2可以看到,我们主要是通过TelephonyManager的getDeviceId()
方法来获取的
我们来看看该方法的注解:
/**
* Returns the unique device ID, for example, the IMEI for GSM and the MEID
* or ESN for CDMA phones. Return null if device ID is not available.
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
* href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
* the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
* <li>If the calling app is the device or profile owner and has been granted the
* {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
* owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
* Profile owner access is deprecated and will be removed in a future release.
* <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
* active subscription.
* <li>If the calling app is the default SMS role holder (see {@link
* RoleManager#isRoleHeld(String)}).
* </ul>
*
* <p>If the calling app does not meet one of these requirements then this method will behave
* as follows:
*
* <ul>
* <li>If the calling app's target SDK is API level 28 or lower and the app has the
* READ_PHONE_STATE permission then null is returned.</li>
* <li>If the calling app's target SDK is API level 28 or lower and the app does not have
* the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
* higher, then a SecurityException is thrown.</li>
* </ul>
*
* @deprecated Use {@link #getImei} which returns IMEI for GSM or {@link #getMeid} which returns
* MEID for CDMA.
*/
翻译:
返回唯一的设备 ID,例如,GSM 手机的 IMEI 和 CDMA 手机的 MEID 或 ESN。 如果设备ID不可用,则返回 null。
从Android10 API29开始,就不允许调用该方法,建议使用可重置的标志符,如果满足以下条件,可调用此方法
1、授予了READ_PRIVILEGED_PHONE_STATE该权限,但是该权限仅系统应用才可获得
2、如果应用的API为28或者更低,且具备READ_PHONE_STATE权限,则返回null
3、如果应用API为29或者更高,则会抛出SecurityException异常
建议我们通过getImei方法获取GSM手机的IMEI或者getMeid()方法获取CDMA手机的MEID
总结:
-
可以看出该方法得到的并不一定是IMEI。
-
对于只有GSM制式的手机是得到的是IMEI,对于只有CDMA制式的手机,返回的是ESN或MEID。
-
所以对于我们的双卡双待手机,有可能返回IMEI,也有可能返回MEID。这里推荐我们使用
getImei()
来获取GSM网络制式的IMEI或者getMeid()
方法来获取CDMA网络制式的MEID。
2.3 通过getImei()和getMeid()获取
2.3.1 所需权限
需要动态申请READ_PHONE_STATE权限
注意:Android10以上禁止获取IMEI,因为需要READ_PRIVILEGED_PHONE_STATE权限,而该权限只能是系统应用才可以获取到。
2.3.2 获取方法
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&Build.VERSION.SDK_INT>Build.VERSION_CODES.O){
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val imei1 = telManager.getImei(0)
val imei2 = telManager.getImei(1)
Log.i(TAG, "IMEI卡槽1:$imei1")
Log.i(TAG, "IMEI卡槽2:$imei2")
val meid1 = telManager.getMeid(0)
val meid2 = telManager.getMeid(1)
Log.i(TAG, "MEID卡槽1为: $meid1")
Log.i(TAG, "MEID卡槽2为:$meid2 ")
}
2.3.3 特殊说明
在写文章之前,看到网上关于获取IMEI的文章都是见getDeviceId()
获取不到就一股脑的反射调用getImei()
,我当时还在想人家这方法本身就能直接调用,你还反射个锤子。
反射是调用@hide标识的class或者是一些方法没有编到SDK里的,也就是我们的隐藏接口,这些才用到反射。后来通过查看5.1、7.1、8、9的源码发现,到7.1,该方法还是隐藏的,从8.0开始,getImei()方法就可以直接调用了,所以傻孩子别再不看版本就一股脑反射调用了。
5.1
10.0
2.4 测试结果-总结
通过云端的测试机测试,小米9SE和OPPOR15的数据如下:
小米9SE
IMEI手机卡1:862427041314834
IMEI手机卡2:862427041314842
MEID1为: 99001279065741
MEID2为: 99001279065741
deviceId为:862427041314834
OPPOR15
IMEI手机卡1:865741048560315
IMEI手机卡2:865741048560307
MEID1为: A0000088576EC0
MEID2为: A0000088576EC0
deviceId为:A0000088576EC0
⭐从数据再次验证了2.2和2.3中的说法
-
getDeviceId()
返回的不一定是IMEI,也有可能是MEID -
一个双卡双待的手机一般IMEI号码有两个,MEID号码有一个
-
如果想准确的获取手机的IMEI,就使用
getImei()
方法
3. OAID获取
在2中说道,在Android10以上,安卓是禁止我们获取IMEI的,上面的方法都无法获取到,那如果想要唯一标识一部手机,那我们可以使用OAID
因传统的移动终端设备标识如国际移动设备识别码(IMEI)等已被部分国家认定为用户隐私的一部分,并存在被篡改和冒用的风险,所以在Android 10及后续版本中非厂商系统应用将无法获取IMEI、MAC等设备信息。无法获取IMEI会在用户行为统计过程中对设备识别产生一定影响。
近日移动安全联盟针对该问题联合国内手机厂商推出补充设备标准体系方案,选择OAID字段作为IMEI等的替代字段。OAID字段是由中国信通院联合华为、小米、OPPO、VIVO等厂商共同推出的设备识别字段,具有一定的权威性,可满足用户行为统计的使用场景。
3.1 所需权限
不需要任何权限
3.2 获取方法
3.2.1 下载aar文件
移动安全联盟MSA (msa-alliance.cn) 官网下载如下文件,但是需要登录,注册的账号还需要公司信息等…晕死,嫌麻烦的在该项目Github上自取
该lib仅支持以下设备获取OAID
3.2.2 添加依赖
将aar文件添加到lib中,并添加依赖
implementation files('libs/oaid_sdk_1.0.25.aar')
3.2.3 设置gradle编译选项
defaultConfig{
ndk {
//设置支持的SO库架构
abiFilters 'armeabi-v7a', 'arm64-v8a','x86','x86_64','armeabi'
}
}
3.2.4 assets目录添加supplierconfig.json文件
{
"supplier":{
"vivo":{
},
"xiaomi": {
},
"huawei":{
},
"oppo":{
}
}
}
注意:一定要添加该文件,否则会报加载配置文件出错
3.2.5 代码实现
class OAIDActivity : AppCompatActivity(), IIdentifierListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_o_a_i_d)
getOAID()
}
private fun getOAID() {
//初始化
var error = MdidSdkHelper.InitSdk(this, true, this)
when (error) {
ErrorCode.INIT_ERROR_DEVICE_NOSUPPORT -> {
//不支持的设备
Log.i(TAG, "getOAID: 不支持的设备")
}
ErrorCode.INIT_ERROR_LOAD_CONFIGFILE -> {
//加载配置文件出错
Log.i(TAG, "getOAID: 加载配置文件出错")
}
ErrorCode.INIT_ERROR_MANUFACTURER_NOSUPPORT -> {
//不支持的设备厂商
Log.i(TAG, "getOAID: 不支持的设备厂商")
}
ErrorCode.INIT_ERROR_RESULT_DELAY -> {
//获取接口是异步的,结果会在回调中返回,回调执行的回调可能在工作线程
Log.i(TAG, "getOAID: 获取接口是异步的,结果会在回调中返回,回调执行的回调可能在工作线程")
}
ErrorCode.INIT_HELPER_CALL_ERROR -> {
//反射调用出错
Log.i(TAG, "getOAID: 反射调用出错")
}
}
}
//注意,该回调方法在非主线程中
override fun OnSupport(isSupport: Boolean, _supplier: IdSupplier?) {
if (_supplier == null) {
runOnUiThread {
tvInfo.text = "获取到的配置信息为空"
}
Log.i(TAG, "OnSupport: 获取到的信息为空")
return
}
//关键用这个
val oaid: String = _supplier.oaid
val vaid: String = _supplier.vaid
val aaid: String = _supplier.aaid
val builder = StringBuilder()
}
}
注意:OnSupport()回调方法在非主线程中
4. 总结
-
AndroidID能够直接获取,不需要权限
-
IMEI和MEID只与设备有关,
getDeviceId()
返回的不一定是IMEI,也有可能是MEID -
一个双卡双待的手机一般IMEI号码有两个,MEID号码有一个
-
如果想准确的获取手机的IMEI,就使用
getImei()
方法 -
Android10以上无法获取IMEI,可通过获取OAID来作为设备唯一码
在第一次查询如何获取IMEI的相关文章时,真是惊呆我了,有的文章竟然在区分插入移动联通卡和插入电信卡获取方法的区别,拜托,IMEI是跟设备相关的,跟你插什么卡有半毛线关系!!!还有的毛线不看,直接照抄前几年的代码,各种反射来获取,拜托,都可以直接获取了好不好,看不惯那些,所以总结了这篇文章。
如果本文对你有帮助,请别忘记点赞start,如果有不恰当的地方也请提出来,下篇文章见。
关注公众号,回复 IMEI 获取文章源码