热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

安卓iccid_Android获取双卡手机IMEI,IMSI,ICCID

一、首先要添加权限二、获取主卡的IMEI,IMSI,ICCID***Author:liuqiang*Time:2017-08-1415:28*Descr

一、首先要添加权限

二、获取主卡的IMEI,IMSI,ICCID

/**

* Author: liuqiang

* Time: 2017-08-14 15:28

* Description:

*

* IMEI 与你的手机是绑定关系 用于区别移动终端设备

* IMSI 与你的手机卡是绑定关系 用于区别移动用户的有效信息 IMSI是用户的标识。

* ICCID ICCID是卡的标识,由20位数字组成

* ICCID只是用来区别SIM卡,不作接入网络的鉴权认证。而IMSI在接入网络的时候,会到运营商的服务器中进行验证。

* https://github.com/android/platform_frameworks_base/blob/master/telephony/java/android/telephony/TelephonyManager.java

*/

@RequiresApi(api = Build.VERSION_CODES.O)

public void check(View view) {

TelephonyManager telephonyManager = (TelephonyManager) this

.getSystemService(TELEPHONY_SERVICE);// 取得相关系统服务

String simOperatorName = telephonyManager.getSimOperatorName();

String imei = telephonyManager.getDeviceId(); //取出 IMEI

String imeiAPI26 = telephonyManager.getImei(); //取出 IMEI 需要 api26以上

String tel = telephonyManager.getLine1Number(); //取出 MSISDN,很可能为空

String imsi = telephonyManager.getSubscriberId(); //取出 IMSI

String icc = telephonyManager.getSimSerialNumber(); //取出 ICCID

Log.d("Q_M", "运行商名字--" + simOperatorName);

Log.d("Q_M", "IMEI--" + imei);

Log.d("Q_M", "IMEI_API26--" + imeiAPI26);

Log.d("Q_M", "IMSI--" + imsi);

Log.d("Q_M", "ICCID--" + icc);

}

三、如果手机有多张卡

其实多卡情况下主要要获得的是两个地方:getSubscriberId和getSimSerialNumber,打开上面的的源码,搜索一下这两个方法,发现这两个方法都有一个带参数(subId)的重载方法,并且这两个方法都是@hide的,hide倒是无所谓,这个可以通过反射调用,主要的问题要弄清楚他的这个参数subId是个什么东西。代码片段如下:

/**

* Returns the unique subscriber ID, for example, the IMSI for a GSM phone.

* Return null if it is unavailable.

*

* Requires Permission:

* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}

*/

public String getSubscriberId() {

return getSubscriberId(getSubId());

}

/**

* Returns the unique subscriber ID, for example, the IMSI for a GSM phone

* for a subscription.

* Return null if it is unavailable.

*

* Requires Permission:

* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}

*

* @param subId whose subscriber id is returned

* @hide

*/

public String getSubscriberId(int subId) {

try {

IPhoneSubInfo info = getSubscriberInfo();

if (info == null)

return null;

return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName());

} catch (RemoteException ex) {

return null;

} catch (NullPointerException ex) {

// This could happen before phone restarts due to crashing

return null;

}

}

从上面的注释来来,这个subId 是subscription id的简写,既然提到subscription id,那不得不说的就是SubscriptionManager,这个东西这里不解释了看这篇文章,官方文档。只要知道一点,这个类就是为了5.0之后,为了配合TelephonyProvider操作和处理/data/data/com.android.providers.telephony/databases/telephony.db这个数据库中的表的,就可以了。这个数据在root了的手机上可以直接通过SQLite-Editor-2.1.1.apk这个工具apk查看。当然也可以把他导出到电脑上查看。

0 、不算业余知识的业余知识

既然所有的CURD都是针对这个数据库,那么来研究下这个数据库,

打开后发现这个数据库中有张叫siminfo的表。如下图:

1269e4ba99c9

siminfo.png

截图中并没有包含表中所有的列,但是这些是主要的,后面的那些和这篇文章无关。上图这个是在Android模拟器中导出来的。在看一张表的时候,不仅要了解他的字段的含义,而且要了解这种表中数据的插入规则。

很重要:每次插入一张新SIM卡(这张卡没有插入到过这个手机)的时候,都会在数据库中添加一条记录,插入的时候_id字段从1开始每次增1;每次移除一张SIM卡,sim_id字段设置为-1(但是这条记录不会被删除),表示这张卡曾经插入过,但是又被移除了,用专业术语来说就是这张卡现在不是Active的,处于不可用状态。

这些字段都表示了什么意思,其中最重要的是_id和sim_id:

_id:从数据库的角度来说,做过sqlite开发的都知道,他是个从1开始自增的主键。但是他在这里还代表了程序中另一个东西subId也就是subscription id

icc_id:不解释,上面说过了

sim_id:这个字段有两层含义,在大于-1,的情况下他表示的是卡槽序号,比如sim_id为0表示卡1,取值为1的时候表示的是卡2,以此类推,但是一般手机不会超过两个卡槽吧?!如果取值为-1,表示这张SIM卡曾经被插入过,但是现在被移除了。

display_name:顾名思义,显示名。这个一般可以改,但是默认的是读取的运营商的名字,比如:中国移动,中国联通,中国电信

carrier_name :恩,运营商名字

number:SIM卡对应的手机号,这个不一定能取到

mcc:Mobile Country Code,移动国家码

mnc:Mobile Network Code,移动网络码

现在的主要问题是如何获取subscription id,对吧,how?

1 、读数据库取subId也即是表的_id 字段

///data/data/com.android.providers.telephony/databases/telephony.db

public void getSimInfo() {

Uri uri = Uri.parse("content://telephony/siminfo");

Cursor cursor = null;

ContentResolver contentResolver = getApplicationContext().getContentResolver();

cursor = contentResolver.query(uri,

new String[]{"_id", "sim_id", "icc_id", "display_name"}, "0=0",

new String[]{}, null);

if (null != cursor) {

while (cursor.moveToNext()) {

String icc_id = cursor.getString(cursor.getColumnIndex("icc_id"));

String display_name = cursor.getString(cursor.getColumnIndex("display_name"));

int sim_id = cursor.getInt(cursor.getColumnIndex("sim_id"));

int _id = cursor.getInt(cursor.getColumnIndex("_id"));

Log.d("Q_M", "icc_id-->" + icc_id);

Log.d("Q_M", "sim_id-->" + sim_id);

Log.d("Q_M", "display_name-->" + display_name);

Log.d("Q_M", "subId或者说是_id->" + _id);

Log.d("Q_M", "---------------------------------");

}

}

}

如上代码,我在小米6的测试机上进行测试,插入过3张卡,两张移动,一张联通的,运行结果如下(因为是真是的SIM卡,隐藏了icc_id):

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898600*************7

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->0

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->中国移动

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->1

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898601*************6

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->-1

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->CARD 2

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->2

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: icc_id-->898602*************9

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: sim_id-->1

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: display_name-->中国移动

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: subId或者说是_id->3

08-14 16:46:11.208 11583-11583/me.febsky.rootcheck D/Q_M: ---------------------------------

2、通过SubscriptionManager取subId

但是这个api有两个缺陷:必须5.0以上才能用;只能获取active的卡的信息。其实对于第二点缺陷,源码中是能获取所有的卡的信息的,也就是表中所有的数据,通过源码看到这个方法叫做getAllSubscriptionInfoList不过这个方法也被hide了,能用是能用,就是这种@hide的方法,不一定在哪个版本就会被删除掉。需要做很多兼容性上的操作。

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)

public void getSimInfoBySubscriptionManager() {

List list = SubscriptionManager.from(this).getActiveSubscriptionInfoList();

for (SubscriptionInfo info : list) {

Log.d("Q_M", "ICCID-->" + info.getIccId());

Log.d("Q_M", "subId-->" + info.getSubscriptionId());

Log.d("Q_M", "DisplayName-->" + info.getDisplayName());

Log.d("Q_M", "CarrierName-->" + info.getCarrierName());

Log.d("Q_M", "---------------------------------");

}

}

上面的SubscriptionInfo源码,就几乎对应了siminfo这个表中的所有的字段。

其实代码分析到这里,双卡的其他基本信息其实都已经获取到了,无论通过读取数据库的方式,还是通过SubscriptionManager的方式,唯一没有获取到的就是getSubscriberId()来得到的IMSI。

3、通过反射获取调用IMSI

反射调用带有参数的getSubscriberId(subId)是很简单的,但是问题就出在,这个方法是@hide,所以说这个方法可能在不同的Android版本中会出现不同的实现。subId在5.0传入的是long类型的参数,而5.1-7.1.1传入的是int类型的参数。再高的版本我就没看了

5.0.0上的方法签名:

/**

* Returns the unique subscriber ID, for example, the IMSI for a GSM phone

* for a subscription.

* Return null if it is unavailable.

*

* Requires Permission:

* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}

*

* @param subId whose subscriber id is returned

*/

/** {@hide} */

public String getSubscriberId(long subId) {

try {

return getSubscriberInfo().getSubscriberIdForSubscriber(subId);

} catch (RemoteException ex) {

return null;

} catch (NullPointerException ex) {

// This could happen before phone restarts due to crashing

return null;

}

}

5.1.0-5.1.1的方法签名:

public String getSubscriberId(int subId) {

try {

return getSubscriberInfo().getSubscriberIdForSubscriber(subId);

} catch (RemoteException ex) {

return null;

} catch (NullPointerException ex) {

// This could happen before phone restarts due to crashing

return null;

}

}

6.0.0-7.1.1的方法签名:

public String getSubscriberId(int subId) {

try {

IPhoneSubInfo info = getSubscriberInfo();

if (info == null)

return null;

return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName());

} catch (RemoteException ex) {

return null;

} catch (NullPointerException ex) {

// This could happen before phone restarts due to crashing

return null;

}

}

反射调用getSubscriberId的代码如下,其中参数,subId的获取方式,在上面已经有了:

public String getSubscriberId(int subId) {

TelephonyManager telephonyManager = (TelephonyManager) this

.getSystemService(TELEPHONY_SERVICE);// 取得相关系统服务

Class> telephonyManagerClass = null;

String imsi = null;

try {

telephonyManagerClass = Class.forName("android.telephony.TelephonyManager");

if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) {

Method method = telephonyManagerClass.getMethod("getSubscriberId", int.class);

imsi = (String) method.invoke(telephonyManager, subId);

} else if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.LOLLIPOP) {

Method method = telephonyManagerClass.getMethod("getSubscriberId", long.class);

imsi = (String) method.invoke(telephonyManager, (long) subId);

}

} catch (Exception e) {

e.printStackTrace();

}

Log.d("Q_M", "IMSI--" + imsi);

return imsi;

}

最后: 这些操作都是在Android5.0以上版本的,并且第三方厂商没有修改这块的代码的情况下的操作。至于5.0以下的系统,怎么判断,好像要分平台单个处理

Android4.x上处理双卡

高通平台有单独的类android.telephony.MSimTelephonyManager里面有方法,这个可以用反射的方式调用,经过验证在努比亚4.4.2的系统上是可以获取的:

getSubscriberId(int simId)

getSimSerialNumber(int simId)

测试代码如下:

/**

* Author: liuqiang

* Time: 2017-08-15 10:56

* Description:

*

* 高通的“android.telephony.MSimTelephonyManager”类

*/

private void getAPI19SimInfo() {

Class> tm = null;

try {

tm = Class.forName("android.telephony.MSimTelephonyManager");

Method getSubscriberIdMethod = tm.getMethod("getSubscriberId", int.class);

Method getSimSerialNumberMethod = tm.getMethod("getSimSerialNumber", int.class);

Object service = this.getSystemService("phone_msim");

//0 代表卡1

//1 代表卡2

String s = (String) getSimSerialNumberMethod.invoke(service, 0);

Log.d("Q_M", "-->" + s);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (NoSuchMethodException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

至于MTK平台,据说有个aidl"com.mediatek.common.telephony.ITelephonyEx.aidl"类或者用Manager类com.mediatek.telephony.TelephonyManagerEx,我这没有MTK平台的测试机,没法测。

【参考文章】



推荐阅读
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • 长期从事ABAP开发工作的专业人士,在面对行业新趋势时,往往需要重新审视自己的发展方向。本文探讨了几位资深专家对ABAP未来走向的看法,以及开发者应如何调整技能以适应新的技术环境。 ... [详细]
  • 2023年,Android开发前景如何?25岁还能转行吗?
    近期,关于Android开发行业的讨论在多个平台上热度不减,许多人担忧其未来发展。本文将探讨当前Android开发市场的现状、薪资水平及职业选择建议。 ... [详细]
  • 本文介绍了如何通过命令行有效地终止所有 Node.js 进程实例,以解决因端口冲突或其他服务冲突导致的问题。 ... [详细]
  • 本文将从基础概念入手,详细探讨SpringMVC框架中DispatcherServlet如何通过HandlerMapping进行请求分发,以及其背后的源码实现细节。 ... [详细]
  • 深入解析WebP图片格式及其应用
    随着互联网技术的发展,无论是PC端还是移动端,图片数据流量占据了很大比重。尤其在高分辨率屏幕普及的背景下,如何在保证图片质量的同时减少文件大小,成为了亟待解决的问题。本文将详细介绍Google推出的WebP图片格式,探讨其在实际项目中的应用及优化策略。 ... [详细]
  • 在将 Android Studio 从 3.0 升级到 3.1 版本后,遇到项目无法正常编译的问题,具体错误信息为:org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:processDemoProductDebugResources'。 ... [详细]
  • 本文是对《敏捷软件开发:原则、模式与实践》一书的深度解析,书中不仅探讨了敏捷方法的核心理念及其应用,还详细介绍了面向对象设计的原则、设计模式的应用技巧及UML的有效使用。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 在尝试加载支持推送通知的iOS应用程序的Ad Hoc构建时,遇到了‘no valid aps-environment entitlement found for application’的错误提示。本文将探讨此错误的原因及多种可能的解决方案。 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • Java 中的十进制样式 getZeroDigit()方法,示例 ... [详细]
  • 在开发一个网页音乐播放器时遇到问题,需要从不同源读取MP3文件的ID3标签信息,包括流派、歌手和歌曲名称等。尝试使用PHP未果后转而考虑使用JavaScript进行跨域读取,但不清楚具体配置方法,寻求技术指导。 ... [详细]
  • 洛谷 P4009 汽车加油行驶问题 解析
    探讨了经典算法题目——汽车加油行驶问题,通过网络流和费用流的视角,深入解析了该问题的解决方案。本文将详细阐述如何利用最短路径算法解决这一问题,并提供详细的代码实现。 ... [详细]
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
author-avatar
超级神棍小虾米
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有