查询SIM卡联系人——源码流程简介

查询SIM卡联系人

查询SIM卡中的联系人使用的方法为 query( ) 方法,与操作数据库中的查询方法极其类似,使用方式与如下类似:

getContentResolver().query("content://icc/adn/subId/1", null, null, null, null);

那么我们就来探究一下该方法的流程。

流程

  • IccProvider.java —— query( )
@Override
public Cursor query(Uri url, String[] projection, String selection,
       String[] selectionArgs, String sort) {
    switch (URL_MATCHER.match(url)) {
        case ADN:
            return loadFromEf(IccConstants.EF_ADN, mSubscriptionManager.getDefaultSubId());

        case ADN_SUB:
            return loadFromEf(IccConstants.EF_ADN, getRequestSubId(url));
    //……省略其它代码……//
    }
}

从 IccProvider 中的 query 方法开始。我们首先匹配到的是 ADN 的情况,并且由于是双卡,所以下一步需要执行第二种情况的 loadFromEf 方法。该方法中可以看到 IccConstants.EF_ADN 值为6F3A,这就是SIM卡上的地址。

参考: android telephony 工作流程(一)–UICC概述及SIM卡文件系统

  • IccProvider.java —— loadFromEf( )
private MatrixCursor loadFromEf(int efType, int subId) {
    List<AdnRecord> adnRecords = null;
    try {
        IIccPhoneBook iccIpb = getIccPhbService();
        if (iccIpb != null) {
            adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType);
        }
    } 
    //……省略其它代码……//
    return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES);
}

可以看到接下来就是 IIccPhoneBook 中的 getAdnRecordsInEfForSubscriber 方法。实际上接下来执行的是 IccPhoneBookManager.java 中的 getAdnRecordInEf 方法,其中经过了 IIccPhoneBook.java –> UiccPhoneBookController.java –> IccPhoneBookManager.java 的跳转与转换,使用了 Binder 和 AIDL 实现跨进程通信以及使用了代理模式(Proxy Pattern)控制访问,如果不了解这两方面的知识,就会有点看不懂代码的意图与具体实现的内涵,关于这两点,在文章最后进行简单的描述。

  • IccPhoneBookManager.java —— getAdnRecordsInEf( )
public synchronized List<AdnRecord> getAdnRecordsInEf(int efid) {
    //……省略其它代码……//
    efid = updateEfForIccType(efid);// Check if we are trying to read ADN records
    //……省略其它代码……//
    synchronized (mLock) {
        checkThread();
        AtomicBoolean status = new AtomicBoolean(false);
        Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, status);
        if (mAdnCache != null) {
            mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
            waitForResult(status);
        } else {
            loge("Failure while trying to load from SIM due to uninitialised adncache");
        }
    }
    return mRecords;
}

在该方法中,首先先检查 efid 是否是 IccConstants.EF_ADN,判断一下下面的 efid 是使用 IccConstants.EF_ADN 还是 IccConstants.EF_PBR。然后就是执行 requestLoadAllAdnLike 方法,可以看到从该方法开始,使用了 Message 来通知获取结果。

  • AdnRecordCache.java —— requestLoadAllAdnLike( )
 public void requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
        ArrayList<Message> waiters;
        ArrayList<AdnRecord> result;
        if (efid == EF_PBR) {
            result = mUsimPhoneBookManager.loadEfFilesFromUsim();
        } else {
            result = getRecordsIfLoaded(efid);
        }

        // Have we already loaded this efid?
        if (result != null) {
            if (response != null) {
                AsyncResult.forMessage(response).result = result;
                response.sendToTarget();
            }

            return;
        }
    //……省略其它代码……//
    }

这里我们需要看的是 loadEfFilesFromUsim 方法,可以看到先对各种情况进行判断,如果已经存在纪录,刚只需要 refresh 一下,否则循环读取。

  • UsimPhoneBookManager.java —— loadEfFilesFromUsim( )
 public ArrayList<AdnRecord> loadEfFilesFromUsim() {
    //……省略其它代码……//
    int numRecs = mPbrFile.mFileIds.size();
    // read adn by CPBR, not by read record from EF , So we needn't read
    // for every pbr record.
    if (mFh instanceof CsimFileHandler) { //fzl add for Uicc card start
        // fzl comment: UICC card need CRSM, not CPBR. and need read for every pbr record.
        for (int i = 0; i < numRecs; i++) {
            Rlog.d(LOG_TAG, "readAdnFileAndWaitForUicc: numRecs = " + i);
            readAdnFileAndWaitForUICC(i);
        }
    } else {
        readAdnFileAndWait(0);
    }  //fzl add for Uicc card end
    //……省略其它代码……//
    mAasForAnrRec.clear();
    for (int i = 0; i < numRecs; i++) {
        readAASFileAndWait(i);
        readSneFileAndWait(i);
        readAnrFileAndWait(i);
        readEmailFileAndWait(i);
    }
    readGrpIdsAndWait();
    // All EF files are loaded, post the response.

    return mPhoneBookRecords;
}

这个方法的再接着往下跳转可以看到 RIL.java 文件里的 iccIO 方法,在 IO 方法中可以找到一个 TAG,根据这个 TAG 可以在 Reference-ril.c 文件中找到处理这个 Case 的分支,因为对于这种 IO 指令不了解,大概就只能看个热闹了。

以上流程参考:android – sim/usim卡导联系人

AIDL跨进程通信

loadFromEf( ) 方法中调用了 IIccPhoneBook.java 中的方法,事实上,这是用了 AIDL 方式生成的类。

通过添加 IIccPhoneBook.aidl 文件,描述了方法接口,然后编译生成了 IIccPhoneBook.java 文件,然后 UiccPhoneBookController.java 继承于 IIccPhoneBook.Stub。一般而言,继承于 Stub 的称之为 Binder。

这种方式可以实现跨进程通信,在 Android 中的 Binder 机制是一个庞大的体系模块,而 Android 考虑到了逻辑的复杂性,提供了一个简便的方式即 AIDL (Android Interface Description Language)来生成所需要的文件。

代理模式

代理模式适用于无法或不想直接访问某个对象或者访问某个对象有困难时可以通过一个代理对象来间接访问。

在这里我们可以看一下源码中是怎么实现代理模式的。
首先定义一个接口 Phone.java

public interface Phone {
    // ……省略代码
}   

然后 PhoneBase.java 实现接口

public abstract class PhoneBase extends Handler implements Phone {
    // ……省略代码 
}

然后几种不同制式的SIM卡手机继承了 PhoneBase.java。比如 CDMAPhone.java 、GSMPhone.java 等。
最后定义一个代理类来整合继承于 PhoneBase.java 的类。

public class PhoneProxy extends Handler implements Phone {
    // ……省略代码 
    private IccPhoneBookInterfaceManagerProxy mIccPhoneBookInterfaceManagerProxy;
    // ……省略代码 
    public PhoneProxy(PhoneBase phone) {
        mActivePhone = phone;
        mResetModemOnRadioTechnologyChange = SystemProperties.getBoolean(
                TelephonyProperties.PROPERTY_RESET_ON_RADIO_TECH_CHANGE, false);
        mIccPhoneBookInterfaceManagerProxy = new IccPhoneBookInterfaceManagerProxy(
                phone.getIccPhoneBookInterfaceManager());
        mPhoneSubInfoProxy = new PhoneSubInfoProxy(phone.getPhoneSubInfo());
        // ……省略代码 
    }
}

以上三个类的构造是代理模式常用的实现:一个公共接口、一个被代理类、一个代理类。

可以看到,在 PhoneProxy 代理类中还包含了其他的代理类,比如流程中所用的 IccPhoneBookInterfaceManagerProxy。事实上 UiccPhoneBookController.java 就是通过 IccPhoneBookInterfaceManagerProxy 来访问 IccPhoneBookInterfaceManager 中的 getAdnRecordsInEf( ) 方法,而在这个方法中:

public synchronized List<AdnRecord> getAdnRecordsInEf(int efid) {

    if (mPhone.getContext().checkCallingOrSelfPermission(
            android.Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
        throw new SecurityException(
                "Requires android.permission.READ_CONTACTS permission");
    }
    //……省略其它代码……//
            mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
   //……省略其它代码……//         
    return mRecords;
}

可以看到除了业务逻辑之外还调用了 PhoneBase 的方法来检查权限问题。

由此可以看出代理模式的优点和其使用方式,通过代理来控制对原始对象的访问,而在访问原始对象时执行一些自己的附加操作,实现了代理类与被代理类之间的解耦,符合面向对象原则。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值