Android双卡手机:获取主卡副卡的simid(上)方案实现

近期公司下发了新任务,需要对Android手机进行双sim卡的管控,而这个功能的关键在于的准确的获取sim卡的SubscriberId值,做过sim卡相关或者电话功能的工程师会很清楚,Android SDK提供了一个方法来获取sim卡的getSubscriberId值。可是这只能获取其中主卡的id值,而副卡根本没有提供API,而且国内的手机大多是双卡手机,所以笔者花了很大功夫调研了大部分市场的畅销Android手机的双卡功能。

整个功能比较繁琐,所以一共分上下两部分讲述。上部讲述市场上主流手机获取副卡Id的方法,以及遇到的困难,注意事项;下部讲述如何整合所有的方案,实现机型自适配,提供一个稳定的API,供其他程序使用。

原生的Android是不支持双卡双待功能的,所以Android sdk没有提供API,直到在Android6.0+上增加了相关的API,但是国内的android嘛,有6.0和没6.0没啥区别,碎片化导致只能利用另外一种方法去获取了—反射。关于反射相关的知识,这里就不进行讲解了,因为获取副卡id会用到大量的反射,不懂的读者可以学习这篇博文[http://www.cnblogs.com/lzq198754/p/5780331.html。]

先说说最困难的地方吧,国内的双卡功能是由厂商联合芯片开发商定做的,所以芯片商不一样,厂商不一样,实现方案也就不一样,所以反射的类和函数名也不一样。总的来说,目前全球有高通(小米),联发科(魅族),麒麟海思(华为),猎户座(三星)四家移动通讯芯片制造商,笔者通过一周的时间,将公司的所有测试机进行了反射获取,终于总结出了如下方案:

  • 高通系:
    代表手机:小米,vivo,oppo
    反射的类:TelephonyManager
    反射方法:getSubscriberId(int id)
    反射方法:getSubscriberId(long id)

    高通系手机获取副卡的API还算规范化,直接在android的原生类中添加了方法,而android6.0+提供的官方双卡API也是这样做的。android提供获取simid原生单卡API为:

TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String id = telephonyManager.getSubscriberId();

所以使用部分厂商直接在TelephonyManager类中重载了该方法, 不过厂商的重载的参数不同,比如笔者在进开发时发现,小米,红米系列使用的重载方法为getSubscriberId(int id),而vivo,oppo,中兴则使用getSubscriberId(long id),反射的代码如下:

public int getSecondImsi(Context context) throws InvocationTargetException,IllegalAccessException, NoSuchMethodException {
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        Class telephonyclass = telephonyManager.getClass();
        Method method = null;
        boolean param = true;
        try {
            method = telephonyclass.getDeclaredMethod("getSubscriberId",int.class);

        }catch (NoSuchMethodException e){
            try {
                method = telephonyclass.getDeclaredMethod("getSubscriberId",long.class);
                param = false;
            }catch (NoSuchMethodException e2) {
            }
        }
        Object object = null;
        HashSet buildSet = new HashSet<>();
        if (param){
            for(int i = 0;i < 9;i++) {
                object = method.invoke(telephonyManager,i);
                if (object != null){
                    buildSet.add(object.toString());
                }
            }
        }

        return 0;
    }

这里需要做几点说明:
1.反射需要抛出几种异常情况如:
NoSuchMethodException: 没有这个函数
IllegalAccessException:此方法为私有方法,不能反射
InvocationTargetException:反射方法内部出现异常
ClassNotFoundException:没有这个类,在华为系手机中会用到

2.使用HashSet保存结果,这里使用set保存结果的原因有2点
第一是因为厂商问题,系统不会给你准确的返回卡槽1或者卡槽2的值,而是一旦判定卡槽里有卡就会返回,比如,一张卡插在任意一个卡槽中,通过系统API都能返回SIMid。所以,在插入双卡的情况下,通过反射可能会获取到所有的值,我们没有办法根据卡槽来获取值,保证卡与卡槽的一一对应。这样只能采用set的唯一性来保证获取到所有的值。

第二还是因为厂商原因,笔者在开发时发现,通过反射getSubscriberId(long id)方法,这里的参数id没有任何可参考性,当初以为可以输入0,1就能返回不同的simid,结果是输入5才能获取另一个simid……而且,就拿vivo的一款旗舰机而言,输入从1-9,只有输入5才获取到副卡的id,其他返回的都是主卡的id,这就很坑了,所有笔者简单粗暴的使用for循环,不管有没有值,都存到set里,性能会影响,但保证功能。总结一句话,厂商提供的getSubscriberId(long id)方法的id没有参考意义,直接简单粗暴的循环,这也导致了笔者实现的此功能的缺陷:只能获取到simid,没有办法保证卡与卡槽的一一对应

  • 华为系
    代表手机:华为荣耀系列,P系列,mate系列
    反射类:”android.telephony.MSimTelephonyManager”
    反射方法:getSubscriberId(int id)
    反射方法:getSubscriberId(long id)

华为系手机比较复杂,因为和自身的芯片有关系,比如,大部分华为高端机型使用了自身的芯片,第二张sim卡的相关API封装在android.telephony.MSimTelephonyManager这个类,而部分采用高通机型的华为手机还是使用高通方案。比如华为P7,P8,P9用了自身方案,而MATE8采用了高通方案,笔者在后期适配时被整的很惨……
要反射华为系手机的MSimTelephonyManager类的方法如下:

protected int getassistantImsi(Context context) throws IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
        Class telephonyClass = Class.forName("android.telephony.MSimTelephonyManager");
        Method getdefault = telephonyClass.getMethod("getDefault");
        Object telephonyManager = getdefault.invoke(null);
        Method method = null;
        boolean param = true;
        try {
            method = telephonyClass.getMethod("getSubscriberId", int.class);
        } catch (NoSuchMethodException e) {
            try {
                method = telephonyClass.getMethod("getSubscriberId", long.class);
                param = false;
            } catch (NoSuchMethodException e2) {
                return NO_SUCH_METHOD;
            }
        }
        Object object = null;
        HashSet buildSet = new HashSet<>();
        if (param) {
            for (int i = 0; i < 9; i++) {
                object = method.invoke(telephonyManager, i);
                if (object != null) {
                    buildSet.add(object.toString());
                }
            }
        } else {
            for (long i = 0; i < 9; i++) {
                object = method.invoke(telephonyManager, i);
                if (object != null) {
                    buildSet.add(object.toString());
                }
            }
        }

        return SUCCESS;
    }

代码与上文中高通系手机反射方案差不多,不同的是增加了一个反射类的处理

// 通过包名获取此类
Class telephonyClass = Class.forName("android.telephony.MSimTelephonyManager");
//通过Class基类的getDefault方法获取此类的实例
Method getdefault = telephonyClass.getMethod("getDefault");
Object telephonyManager = getdefault.invoke(null);
Method method = null;

这样我们就能获取到类的实例,然后反射去执行就行了,其他操作如上文。

  • 三星系
    代表手机:note2,3,s4
    反射类:无
    反射方法:无

早期的三星系使用的是自家的方案,如note2,3,s4等,三星也和华为一样,另外封装了一个类来管理副卡,而近期的高端机型貌似使用了android原生双卡管理,就是高通方案。
代码如下:

 TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService("phone2");
 telephonyManager.getSubscriberId()

个人觉得另外封装一个类是比较好的处理……

  • 联发科系:
    代表手机:魅族
    反射类:TelephonyManager
    反射方法:getSubscriberIdGemini()

反射方案和高通类似,就是反射的方法不同,这里就不贴代码了……

  • 其他手机:
    展讯,MTK

其他手机反射方案也不写了,因为笔者找遍公司所有测试机,没有找到这些山寨机
不过要适配这些机型的,可以这个博文
http://blog.csdn.net/zc0727/article/details/15541289

Android双卡手机:获取主卡副卡的simid(上)方案实现_第1张图片

Android双卡手机:获取主卡副卡的simid(上)方案实现_第2张图片

结束语:
这是笔者第一次写博文,所以写的不好请多包含,有什么问题或者错误请指正,互相进步嘛。
欢迎转载,请指明出处。
关于双卡功能的管理,本文只是对市场上android手机双卡的一次普查,以及方案的提供。目前是市场上存在的机型大多使用这些方法,而且网上也有很多相关博客。所以如何适配机型,根据机型来选择正确的方案,这才是实现的难点。采用人为枚举是一个很傻叉的行为(因为笔者早期的方案就是枚举所有机型),适配的工作,应该交给软件自身来做。所以,下一篇博文会讲述如何整和这些方案,让程序自行进行适配,选择合适的方案获取simid。相关代码会在下一博文中提供开源地址。

你可能感兴趣的:(android)