前言
因为项目中经常会遇到要上传一系列设备信息的功能,为了方便使用,所以就拆分成以下系列文章来单独介绍如何获取各类设备信息
- 手机运营商获取
- AndroidID、IMEI、OAID获取
- 地理位置信息经纬度获取
- 公网IP地址获取:移动网络IP、Wifi IP
- Build类获取相关设备信息
- 屏幕相关信息:密度、物理尺寸获取
- BuildConfig获取的一系列基础信息
- UA、网络状态…等持续更新
为了不产生歧义搞混淆,先提前说明下移动和中国移动是两码事,移动客户并不是指中国移动的客户。
1. 概念了解
MCC: 移动国家号码,由3位数字组成,唯一的识别移动客户所属的国家,我国为460;
MNC: 为网络id,由两位数字组成,用于识别移动客户所归属的移动网络, 中国移动系统使用00、02、04、07,中国联通GSM系统使用01、06、09,中国电信CDMA系统使用03、05、电信4G使用11,中国铁通系统使用20。
MSIN: 移动用户识别号码 ,是国际移动用户识别码(IMSI)组成部分
IMSI: 国际移动用户识别码,共有15位,储存在SIM卡中,由MCC、MNC,MSIN组成
所以想要知道运营商名称,我们主要是通过判断MNC来区别是哪种运营商
2. CDMA、GSM区别
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 |
3. 获取运营商方式
3.1 通过getSimOperator()、getSimOperatorName()
3.1.1 所需权限
不需要权限
3.1.2 方法解释
查看源码看以下方法的解释:
getSimOperator()
/**
* Returns the MCC+MNC (mobile country code + mobile network code) of the
* provider of the SIM. 5 or 6 decimal digits.
* <p>
* Availability: SIM state must be {@link #SIM_STATE_READY}
* @see #getSimState
*/
返回SIM卡提供商的MCC+MNC(移动国家代码+移动网络代码)。5或6位十进制数字。
使用的前提是getSimState()方法得到的值为SIM_STATE_READY
getSimOperatorName()
/**
* Returns the Service Provider Name (SPN).
* <p>
* Availability: SIM state must be {@link #SIM_STATE_READY}
*
* @see #getSimState
*/
返回服务提供商名称 (SPN)。使用的前提是SIM卡的状态必须是 SIM_STATE_READY
3.1.3 实际使用
private fun getSimOperatorName() {
var opeType = "OTHER"
val tm =
getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val simOperator = tm.simOperator
if (tm.simState != TelephonyManager.SIM_STATE_READY) {
when (tm.simState) {
TelephonyManager.SIM_STATE_ABSENT -> {//1
Log.i(TAG, "没有Sim卡")
}
TelephonyManager.SIM_STATE_PIN_REQUIRED -> {//2
Log.i(TAG, "Sim卡状态锁定,需要PIN解锁")
}
TelephonyManager.SIM_STATE_PUK_REQUIRED -> {//3
Log.i(TAG, "Sim卡状态锁定,需要PUK解锁")
}
TelephonyManager.SIM_STATE_NETWORK_LOCKED -> {//4
Log.i(TAG, "需要网络PIN码解锁")
}
//...
}
return
}
Log.i(TAG, "getSimOperator()获取的MCC+MNC为:$simOperator")
Log.i(TAG, "getSimOperatorName()方法获取的运营商名称为:${tm.simOperatorName} ")
opeType = if ("46001" == simOperator || "46006" == simOperator || "46009" == simOperator) {
"中国联通"
} else if ("46000" == simOperator || "46002" == simOperator || "46004" == simOperator || "46007" == simOperator) {
"中国移动"
} else if ("46003" == simOperator || "46005" == simOperator || "46011" == simOperator) {
"中国电信"
} else if ("46020" == simOperator) {
"中国铁通"
} else {
"OHTER"
}
Log.i(TAG, "通过getSimOperator()人为判断的运营商名称是: $opeType")
}
3.1.4 实验数据
实验设备是MI9手机,双卡双待,卡槽一是中国移动卡,卡槽二是中国联通卡,分别变换拨号卡和上网卡,通过测试,得到以下数据
拨号卡移动,上网卡移动
getSimOperator()获取的MCC+MNC为:46002
getSimOperatorName()方法获取的运营商名称为:中国移动
通过getSimOperator()人为判断的运营商名称是: 中国移动
拨号卡移动,上网卡联通
getSimOperator()获取的MCC+MNC为:46001
getSimOperatorName()方法获取的运营商名称为:中国移动
通过getSimOperator()人为判断的运营商名称是: 中国联通
拨号卡联通,上网卡移动
getSimOperator()获取的MCC+MNC为:46002
getSimOperatorName()方法获取的运营商名称为:中国联通
通过getSimOperator()人为判断的运营商名称是: 中国移动
拨号卡联通,上网卡联通
getSimOperator()获取的MCC+MNC为:46001
getSimOperatorName()方法获取的运营商名称为:中国联通
通过getSimOperator()人为判断的运营商名称是: 中国联通
拨号时选择Sim卡,上网卡移动
getSimOperator()获取的MCC+MNC为:46002
getSimOperatorName()方法获取的运营商名称为:中国移动
通过getSimOperator()人为判断的运营商名称是: 中国移动
拨号时选择Sim卡,上网卡联通
getSimOperator()获取的MCC+MNC为:46001
getSimOperatorName()方法获取的运营商名称为:中国移动
通过getSimOperator()人为判断的运营商名称是: 中国联通
3.1.5 结论⭐
-
getSimOperator()
得到的是上网卡的运营商 -
getSimOperatorName()
得到的是拨号卡的运营商 - 拨号时选择Sim卡,
getSimOperatorName()
得到的是卡槽1的运营商
3.2 通过getNetworkOperator()、getNetworkOperatorName()
3.2.1 所需权限
不需要权限
3.2.2 方法解释
查看源码看以下方法的解释:
getNetworkOperator()
/**
* Returns the numeric name (MCC+MNC) of current registered operator.
* <p>
* Availability: Only when user is registered to a network. Result may be
* unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
* on a CDMA network).
*/
返回当前注册运营商的数字名称(MCC+MNC)
使用的前提是用户需注册到网络上。在CDMA网络上可能不可靠,需要用getPhoneType()方法确定是否在CDMA网络上
getNetworkOperatorName()
/**
* Returns the alphabetic name of current registered operator.
* <p>
* Availability: Only when user is registered to a network. Result may be
* unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
* on a CDMA network).
*/
返回当前注册运营商的字母名称
仅当用户注册到网络时才可以使用,如果当前网络在CDMA上,得到的结果可能不可靠
3.2.3 实际使用
private fun getNetWorkOperatorName() {
var opeType = "OTHER"
val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
//用于判断拨号那张卡的运营商
val networkOperator = tm.networkOperator
Log.i(TAG, "getNetWorkOperator()获取的MCC+MNC为:$networkOperator")
if (tm.phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
Log.i(TAG, "getNetWorkOperatorNameTest: 在CDMA网络上,不可靠,所以不用这种方法")
return
}
when(tm.phoneType){
0->{
Log.i(TAG, "getPhoneType()网络类型为:NO_PHONE")
}
1->{
Log.i(TAG, "getPhoneType()网络类型为:GSM_PHONE")
}
2->{
Log.i(TAG, "getPhoneType()网络类型为:CDMA_PHONE")
}
3->{
Log.i(TAG, "getPhoneType()网络类型为:SIP_PHONE")
}
4->{
Log.i(TAG, "getPhoneType()网络类型为:THIRD_PARTY_PHONE")
}
5->{
Log.i(TAG, "getPhoneType()网络类型为:IMS_PHONE")
}
6->{
Log.i(TAG, "getPhoneType()网络类型为:CDMA_LTE_PHONE")
}
}
Log.i(TAG, "getNetworkOperatorName()方法获取的网络类型名称是: ${tm.networkOperatorName}")
opeType =
if ("46001" == networkOperator || "46006" == networkOperator || "46009" == networkOperator) {
"中国联通"
} else if ("46000" == networkOperator || "46002" == networkOperator || "46004" == networkOperator || "46007" == networkOperator) {
"中国移动"
} else if ("46003" == networkOperator || "46005" == networkOperator || "46011" == networkOperator) {
"中国电信"
} else {
"OHTER"
}
Log.i(TAG, "通过getNetWorkOperator()人为判断的运营商名称是: $opeType")
}
3.2.4 实验数据
拨号卡移动,上网卡移动
getNetWorkOperator()获取的MCC+MNC为:46000
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国移动
通过networkOperator人为判断的运营商名称是: 中国移动
拨号卡移动,上网卡联通
getNetWorkOperator()获取的MCC+MNC为:46000
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国移动
通过networkOperator人为判断的运营商名称是: 中国移动
拨号卡联通,上网卡移动
getNetWorkOperator()获取的MCC+MNC为:46001
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国联通
通过networkOperator人为判断的运营商名称是: OHTER
拨号卡联通,上网卡联通
getNetWorkOperator()获取的MCC+MNC为:46001
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国联通
通过networkOperator人为判断的运营商名称是: OHTER
拨号时选择Sim卡,上网卡移动
getNetWorkOperator()获取的MCC+MNC为:46000
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国移动
通过networkOperator人为判断的运营商名称是: 中国移动
拨号时选择Sim卡,上网卡联通
getNetWorkOperator()获取的MCC+MNC为:46000
getPhoneType()网络类型为:GSM_PHONE
getNetworkOperatorName()方法获取的网络类型名称是: 中国移动
通过networkOperator人为判断的运营商名称是: 中国移动
3.2.5 结论⭐
-
getNetWorkOperator()
得到的是拨号卡的运营商 -
getNetworkOperatorName()
得到的是拨号卡的运营商 - 拨号时选择Sim卡,
getNetWorkOperator()
和getNetworkOperatorName()
得到的都是卡槽1的运营商
3.3 通过getMcc()、getMnc()
3.3.1 所需权限
READ_PHONE_STATE权限
注意:小米手机申请后得到的可能是空白通行证,会导致获取不到信息
3.3.2 方法解析
getMcc()
/**
* @return the MCC.
* @deprecated Use {@link #getMccString()} instead.
*/
getMnc()
/**
* @return the MNC.
* @deprecated Use {@link #getMncString()} instead.
*/
3.3.3 实际使用
private fun getMNCAndMCC() {
//5.1及以上版本才可使用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val permissionList = mutableListOf<String>()
permissionList.add(android.Manifest.permission.READ_PHONE_STATE)
//权限申请及判断
if (requestPermission(1, permissionList)) {
Log.i(TAG, "getMNCAndMCC: 已获取READ_PHONE_STATE权限")
val subscriptionManager: SubscriptionManager =
getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
//22需要版本5.1及以上才行
val list = mutableListOf<SubscriptionInfo>()
//上面的requestPermission()方法已经进行了判断
if (subscriptionManager.activeSubscriptionInfoList != null) {
list.addAll(subscriptionManager.activeSubscriptionInfoList)
if (list.size > 0) {
for (s in list) {
//10.0以上用下面的方法获取,10.0以下用上面的方法获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.i(TAG, "卡槽${s.simSlotIndex}:mcc = " + s.mccString + " mnc = " + s.mncString)
} else {
Log.i(TAG, "卡槽${s.simSlotIndex}:mcc = " + s.mcc + " mnc = " + s.mnc)
}
}
} else {
Log.i(TAG, "getMNCAndMCC: 获取到的信息为空")
}
} else {
Log.i(TAG, "getMNCAndMCC: 获取到的信息为空")
}
}
}
}
3.3.4 实验数据
无论如何变换拨号卡和上网卡,得到的均为以下数据
卡槽0:mcc = 460 mnc = 02
卡槽1:mcc = 460 mnc = 01
3.3.5 结论⭐
- 通过
getMnc()
可以判断是何种运营商,但是前提需要申请READ_PHONE_STATE权限,且可能遇到有些手机即使有权限获取到的仍为null的情况 -
getMcc()
和getMnc()
方法得到的标识不跟随你拨号卡和上网卡的选择切换而改变,获取的是对应卡槽的内容。
3.4 getSubscriberId()
/**
* Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
* Return null if it is unavailable.
*
* <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}).
* <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>
*/
返回唯一的用户 ID,例如,GSM 电话的 IMSI。
如果不可用则返回 null。
如果安卓版本号是29及以上,即Android 10.0及以上,则需要READ_PRIVILEGED_PHONE_STATE权限才可调用
如果是28及以下,则需要申请READ_PHONE_STATE权限,且得到的是null,如果没有申请该权限,则会抛出异常
有人说可以通过getSubscribeId()
方法获取到IMSI后人为的判断,但是因为该方法需要READ_PRIVILEGED_PHONE_STATE
权限,这个权限只有系统应用才可以获取,所以我们不使用此方法。
3.5 方法比较
由3.1.5、3.2.5、3.3.5可以得知,
getSimOperator()
得到上网卡运营商getSimOperatorName()
、getNetworkOperator()
、getNetworkOperatorName()
得到拨号卡运营商
如果想得到运营商名称,有人会说那我们直接用getSimOperatorName()
或者getNetworkOperatorName()
就能直接获取到,为什么还要用getNetworkOperator()
通过MNC号来人为的判断呢,因为在有些手机上,获取到的名称是CMCC、CUCC、CTCC,而不是中国移动、中国联通、中国电信,所以需要我们人为的判断下。
综上,以后我们要获取运营商名称,直接使用getSimOperator()
和getNetworkOperator()
方法即可。
至于getMNC()
方法,因为要动态申请READ_PHONE_STATE
权限,而且有可能有权限却得到的是空白通行证,所以要谨慎使用。
4. 总结
综上,以后想要获取拨号卡运营商名称,直接通过getNetworkOperator()
方法,获取上网卡运营商,直接通过getSimOperator()
方法。
且要注意, getSimOperator()
得到的是上网卡运营商名称,而getNetworkOperatorName()
得到的却是拨号卡的运营商名称,千万不要被名字给诱导错了!!!
如果本文对你有帮助,请别忘记点赞start,如果有不恰当的地方也请提出来,下篇文章见。