【Android 数据业务解析】APN参数创建

来源:互联网 发布:淘宝二手iphone能买吗 编辑:程序博客网 时间:2024/06/04 20:11
手机可以上网,首先要建立数据连接,建立数据连接之前需要有apn才可以,所以本节先研究APN参数的创建过程。

在DcTracker.java中,创建APN的起点方法为createAllApnList方法。

DcTracker的createAllApnList方法:

/** * Based on the sim operator numeric, create a list for all possible * Data Connections and setup the preferredApn. */// 创建APN列表并创建preferredapnprivate void createAllApnList() {    mAllApnSettings = new ArrayList<ApnSetting>();    // 获取到保存SIM数据的对象    IccRecords r = mIccRecords.get();    // 通过IccRecords获取SIM卡中的MCCMNC,因为要根据MCCMNC来从数据库中读取这个运营商的apn    String operator = (r != null) ? r.getOperatorNumeric() : "";    if (operator != null) {        // 匹配条件和排列顺序        String selection = "numeric = '" + operator + "'";        String orderBy = "_id";        // query only enabled apn.        // carrier_enabled : 1 means enabled apn, 0 disabled apn.        // selection += " and carrier_enabled = 1";        if (DBG) log("createAllApnList: selection=" + selection);        // 查询数据库        Cursor cursor = mPhone.getContext().getContentResolver().query(                Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy);        if (cursor != null) {            if (cursor.getCount() > 0) {                // 创建出apn列表到集合中                mAllApnSettings = createApnList(cursor);            }            cursor.close();        }    }    // 添加emergency的apn到apn集合中    addEmergencyApnSetting();    // 合并相似的apn    dedupeApnSettings();    if (mAllApnSettings.isEmpty()) {        // APN集合为空,mPreferredApn也没用了,因为mPreferredApn肯定在apn集合里面        if (DBG) log("createAllApnList: No APN found for carrier: " + operator);        mPreferredApn = null;        // TODO: What is the right behavior?        //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);    } else {        // 获取preferredapn,该apn为用户在UI界面选择的        mPreferredApn = getPreferredApn();        if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {            // 如果此时mPreferredApn的mccmnc与SIM卡中的不一致,则mPreferredApn置为空            mPreferredApn = null;            // 删除数据库中的数据            setPreferredApn(-1);        }        if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);    }    if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);    setDataProfilesAsNeeded(); // 将apn信息发给modem,没搞懂是什么意思}

createAllApnList方法主要有3个步骤:
(1)从数据库中读取出符合要求的apn列表;
(2)添加emergency的apn到列表中并合并apn;
(3)获取到preferredApn。

(1)从数据库中读取出符合要求的apn列表

从数据库中按照mccmnc来获取所有的apn,并选择出符合要求的apn。在createApnList方法中,得到符合要求的apn集合。
createApnList方法
// 从数据库中读取出所有符合要求的apnprivate ArrayList<ApnSetting> createApnList(Cursor cursor) {    ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>(); // 实体运营商    ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>(); // 虚拟运营商    IccRecords r = mIccRecords.get();    if (cursor.moveToFirst()) {        do {            ApnSetting apn = makeApnSetting(cursor);            if (apn == null) {                continue;            }            if (apn.hasMvnoParams()) { // apn的mvnoType和mvnoMatchData都不为空,说明该apn是一个虚拟运营商的apn                if (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) { // apn的这两个参数与SIM卡中的参数匹配一致                    mvnoApns.add(apn); // 添加到虚拟运营商集合中                }            } else {                mnoApns.add(apn); // 添加到实体运营商集合中            }        } while (cursor.moveToNext()); // 移动到下一个游标处    }    // 虚拟运营商的apn集合优先级高    ArrayList<ApnSetting> result = mvnoApns.isEmpty() ? mnoApns : mvnoApns;    if (DBG) log("createApnList: X result=" + result);    return result;}

createApnList方法中,一个一个构建apn对象,并按照虚拟运营商和实体运营商来划分,当有虚拟运营商的apn时,就用虚拟运营商的apn,没有虚拟运营商的apn时,就用实体运营商的apn。

构建apn对象的方法:makeApnSetting
// 利用ApnSetting的构造方法创建出一个apnsettingprivate ApnSetting makeApnSetting(Cursor cursor) {    String[] types = parseTypes(            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));    ApnSetting apn = new ApnSetting(            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),            NetworkUtils.trimV4AddrZeros(                    cursor.getString(                            cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY))),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)),            NetworkUtils.trimV4AddrZeros(                    cursor.getString(                            cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),            NetworkUtils.trimV4AddrZeros(                    cursor.getString(                            cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY))),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),            types,            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)),            cursor.getString(cursor.getColumnIndexOrThrow(                    Telephony.Carriers.ROAMING_PROTOCOL)),            cursor.getInt(cursor.getColumnIndexOrThrow(                    Telephony.Carriers.CARRIER_ENABLED)) == 1,            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER)),            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER_BITMASK)),            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),            cursor.getInt(cursor.getColumnIndexOrThrow(                    Telephony.Carriers.MODEM_COGNITIVE)) == 1,            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),            cursor.getInt(cursor.getColumnIndexOrThrow(                    Telephony.Carriers.WAIT_TIME)),            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS_TIME)),            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_TYPE)),            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_MATCH_DATA)));    return apn;}

apn有个type参数的获取方法:

/** * @param types comma delimited list of APN types * @return array of APN types */// 返回apn的type,以字符串数组的形式返回private String[] parseTypes(String types) {    String[] result;    // If unset, set to DEFAULT.    if (types == null || types.equals("")) {        result = new String[1];        result[0] = PhoneConstants.APN_TYPE_ALL; // 当types为空或为null,默认为PhoneConstants.APN_TYPE_ALL类型    } else {        // 以逗号隔开,因为在配置的时候当存在多个apn type,就是用逗号隔开的        result = types.split(",");    }    return result;}

获取一个构建的apn对象后,判断该apn是否是用于配置虚拟运营商的apn。
ApnSetting的hasMvnoParams方法:
/** * Returns true if there are MVNO params specified. */// 判断apn是否为虚拟运营商的apnpublic boolean hasMvnoParams() {    return !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData);}

如果apn的参数mvnoType和mvnoMatchData都不为空,则说明属于虚拟运营商的apn,接下来就要看看这个apn是否属于这张SIM卡的虚拟apn,即要将apn的虚拟运营商参数与SIM卡中的数据对比。根据mvnoType的类型,来对比mvnoMatchData是否相同。
ApnSetting的mvnoMatches方法
// SIM卡与apn参数的mvnoMatchData匹配规则public static boolean mvnoMatches(IccRecords r, String mvnoType, String mvnoMatchData) {    if (mvnoType.equalsIgnoreCase("spn")) { //spn        if ((r.getServiceProviderName() != null) &&                r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {            return true;        }    } else if (mvnoType.equalsIgnoreCase("imsi")) { // imsi        String imsiSIM = r.getIMSI();        if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {            return true;        }    } else if (mvnoType.equalsIgnoreCase("gid")) { // gid        String gid1 = r.getGid1();        int mvno_match_data_length = mvnoMatchData.length();        if ((gid1 != null) && (gid1.length() >= mvno_match_data_length) &&                gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {            return true;        }    }    return false;    // equals: 如果两个字符串具有相同的字符和长度,返回true,否则返回false,区分大小写    // equalsIgnoreCase: 如果两个字符串具有相同的字符和长度,返回true,否则返回false,不区分大小写,要求没有equals那么严格}

有三个比较的类型,分别是spn、imsi和gid,说白了就是比较内容是否一致。在配置apn的虚拟运营商参数时,可以不用考虑大小写的影响。

其中imsiMatches方法的比较如下:
// MVNO_TYPE为imsi的mvnoMatchData匹配规则private static boolean imsiMatches(String imsiDB, String imsiSIM) {    // Note: imsiDB value has digit number or 'x' character for seperating USIM information    // for MVNO operator. And then digit number is matched at same order and 'x' character    // could replace by any digit number.    // ex) if imsiDB inserted '310260x10xxxxxx' for GG Operator,    //     that means first 6 digits, 8th and 9th digit    //     should be set in USIM for GG Operator.    int len = imsiDB.length();    int idxCompare = 0;    if (len <= 0) return false;    if (len > imsiSIM.length()) return false;    // imsiDB的长度要大于等于0且长度小于imsiSIM的长度    // 按照顺序对比imsiDB和imsiSIM中的每个字符,如imsiDB出现‘x’或‘X’,可以忽略不计    for (int idx=0; idx<len; idx++) {        char c = imsiDB.charAt(idx);        if ((c == 'x') || (c == 'X') || (c == imsiSIM.charAt(idx))) {            continue;        } else {            return false;        }    }    return true;}

如果说apn的虚拟运营商参数与SIM卡中的数据吻合,则将该apn加入到虚拟运营商的apn集合中。如果该apn没有虚拟运营商的参数,则加入到实体运营商的apn集合中。最后判断是该用虚拟运营商的apn集合还是用实体运营商的apn集合。

备注:当apn的虚拟运营商参数跟SIM卡中的数据不匹配时,则这个apn就是废弃的。当SIM卡为虚拟运营商,但是apn数据库中没有该mccmnc对应的虚拟运营商apn时,最终的apn就是实体运营商的apn集合了。

(2)添加emergency的apn到列表中并合并apn

DcTracker的addEmergencyApnSetting方法

/** * Add the Emergency APN settings to APN settings list */// 添加emergency的apn到apn集合中private void addEmergencyApnSetting() {    if(mEmergencyApn != null) {        if(mAllApnSettings == null) {            mAllApnSettings = new ArrayList<ApnSetting>();        } else {            boolean hasEmergencyApn = false;            for (ApnSetting apn : mAllApnSettings) { // 如果在数据库中有emergency类型的apn,那么就直接退出了                if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_EMERGENCY)) {                    hasEmergencyApn = true;                    break;                }            }            if(hasEmergencyApn == false) { // 如果数据库中没有,那么就将不为null的mEmergencyApn添加到mAllApnSettings中                mAllApnSettings.add(mEmergencyApn);            } else {                log("addEmergencyApnSetting - E-APN setting is already present");            }        }    }}

DcTracker的dedupeApnSettings方法

// 合并相似的apnprivate void dedupeApnSettings() {    ArrayList<ApnSetting> resultApns = new ArrayList<ApnSetting>();    // coalesce APNs if they are similar enough to prevent    // us from bringing up two data calls with the same interface    int i = 0;    while (i < mAllApnSettings.size() - 1) {        ApnSetting first = mAllApnSettings.get(i);        ApnSetting second = null;        int j = i + 1;        while (j < mAllApnSettings.size()) {            second = mAllApnSettings.get(j);            if (apnsSimilar(first, second)) { // 两个apn类似                ApnSetting newApn = mergeApns(first, second); // 合并这两个apn                mAllApnSettings.set(i, newApn); // 合并后的apn放到apn集合的i位置处                first = newApn;                mAllApnSettings.remove(j); // apn集合中移除掉被合并的apn            } else {                j++;            }        }        i++;    }}

上述方法基本上对比了任意两个apn,当满足apnsSimilar方法,就认为这两个apn需要合并了。
apnsSimilar方法:
// Check if neither mention DUN and are substantially similar// 判断两个apn相似的准则private boolean apnsSimilar(ApnSetting first, ApnSetting second) {    return (first.canHandleType(PhoneConstants.APN_TYPE_DUN) == false && // first的apn不为dun类型            second.canHandleType(PhoneConstants.APN_TYPE_DUN) == false && // second的apn不为dun类型            Objects.equals(first.apn, second.apn) && // apn名称一样            !apnTypeSameAny(first, second) && // 此条件很重要,即只有当first和second的apn type没有重复的时候,才会返回true            xorEquals(first.proxy, second.proxy) && // proxy一样,或有一方者为空            xorEquals(first.port, second.port) && // port一样,或者有一方为空            first.carrierEnabled == second.carrierEnabled && // carrierEnabled一样            first.bearerBitmask == second.bearerBitmask && // bearerBitmask一样            first.profileId == second.profileId && // profileId一样            Objects.equals(first.mvnoType, second.mvnoType) && // mvnoType一样            Objects.equals(first.mvnoMatchData, second.mvnoMatchData) && // mvnoMatchData一样            xorEquals(first.mmsc, second.mmsc) && // mmsc一样,或者有一方为空            xorEquals(first.mmsProxy, second.mmsProxy) && // mmsProxy一样,或者有一方为空            xorEquals(first.mmsPort, second.mmsPort)); // mmsPort一样,或者有一方为空}

apnTypeSameAny方法:

//check whether the types of two APN same (even only one type of each APN is same)// 两个apnsetting的type类型比较private boolean apnTypeSameAny(ApnSetting first, ApnSetting second) {    if(VDBG) {        StringBuilder apnType1 = new StringBuilder(first.apn + ": ");        for(int index1 = 0; index1 < first.types.length; index1++) {            apnType1.append(first.types[index1]);            apnType1.append(",");        }        StringBuilder apnType2 = new StringBuilder(second.apn + ": ");        for(int index1 = 0; index1 < second.types.length; index1++) {            apnType2.append(second.types[index1]);            apnType2.append(",");        }        log("APN1: is " + apnType1);        log("APN2: is " + apnType2);    } // 罗列出apn的所有type    // 满足下面三个条件之一,则认为两个apn的type相似:    // ①first的apn type存在all类型    // ②second的apn type存在all类型    // ③frist和second的apn type中有相同的部分    for(int index1 = 0; index1 < first.types.length; index1++) {        for(int index2 = 0; index2 < second.types.length; index2++) {            if(first.types[index1].equals(PhoneConstants.APN_TYPE_ALL) ||                    second.types[index2].equals(PhoneConstants.APN_TYPE_ALL) ||                    first.types[index1].equals(second.types[index2])) {                if(VDBG)log("apnTypeSameAny: return true");                return true;            }        }    }    if(VDBG)log("apnTypeSameAny: return false");    return false;    // 也就是说,两个apnsetting中的apn type有重复的,则返回true,没有重复的,返回false}

在满足apnsSimilar方法后,就要进行合并apn了。
mergeApns方法:
// 合并apn的处理方法private ApnSetting mergeApns(ApnSetting dest, ApnSetting src) {    int id = dest.id; // id先暂定为小的那个,因为mAllApnSettings是按照顺序排列的    ArrayList<String> resultTypes = new ArrayList<String>();    resultTypes.addAll(Arrays.asList(dest.types));    // 对apn type的处理,此处很重要    // srcType基本都要加入到resultTypes中    // 如果srcType中存在default类型,那么resultTypes肯定不存在default类型,则id置为src的id    // 因为如果id不是default类型apn的id,那么在ApnSettings界面中选择的preferredapn,在DcTracker中    // preferredapn的id为src.id,而mergeApns的apn的id为dest.id,程序会认为id不对,获取不到preferredapn,    // 从而去拿另一个default的apn去建立数据连接    for (String srcType : src.types) {        if (resultTypes.contains(srcType) == false) resultTypes.add(srcType);        if (srcType.equals(PhoneConstants.APN_TYPE_DEFAULT)) id = src.id;    }    String mmsc = (TextUtils.isEmpty(dest.mmsc) ? src.mmsc : dest.mmsc);    String mmsProxy = (TextUtils.isEmpty(dest.mmsProxy) ? src.mmsProxy : dest.mmsProxy);    String mmsPort = (TextUtils.isEmpty(dest.mmsPort) ? src.mmsPort : dest.mmsPort);    String proxy = (TextUtils.isEmpty(dest.proxy) ? src.proxy : dest.proxy);    String port = (TextUtils.isEmpty(dest.port) ? src.port : dest.port);    String protocol = src.protocol.equals("IPV4V6") ? src.protocol : dest.protocol;    String roamingProtocol = src.roamingProtocol.equals("IPV4V6") ? src.roamingProtocol :            dest.roamingProtocol;    int bearerBitmask = (dest.bearerBitmask == 0 || src.bearerBitmask == 0) ?            0 : (dest.bearerBitmask | src.bearerBitmask);    return new ApnSetting(id, dest.numeric, dest.carrier, dest.apn,            proxy, port, mmsc, mmsProxy, mmsPort, dest.user, dest.password,            dest.authType, resultTypes.toArray(new String[0]), protocol,            roamingProtocol, dest.carrierEnabled, 0, bearerBitmask, dest.profileId,            (dest.modemCognitive || src.modemCognitive), dest.maxConns, dest.waitTime,            dest.maxConnsTime, dest.mtu, dest.mvnoType, dest.mvnoMatchData);}

(3)获取到preferredApn

在获取到apn集合后,就需要看看用哪个apn来进行上网了。当用户使用一张卡时,可能手动选择过使用的apn,因此,当用户再次插拔卡或者重启手机后,getPreferredApn用于找出用户之前选择的APN。
getPreferredApn方法:
// 得到preferredapn的方法private ApnSetting getPreferredApn() {    if (mAllApnSettings == null || mAllApnSettings.isEmpty()) { // 为空判断        log("getPreferredApn: mAllApnSettings is " + ((mAllApnSettings == null)?"null":"empty"));        return null;    }    String subId = Long.toString(mPhone.getSubId());    Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);    // 从数据库中读取preferredapn    Cursor cursor = mPhone.getContext().getContentResolver().query(            uri, new String[] { "_id", "name", "apn" },            null, null, Telephony.Carriers.DEFAULT_SORT_ORDER);    if (cursor != null) {        mCanSetPreferApn = true;    } else {        mCanSetPreferApn = false;    }    // mRequestedApnType就是PhoneConstants.APN_TYPE_DEFAULT    log("getPreferredApn: mRequestedApnType=" + mRequestedApnType + " cursor=" + cursor            + " cursor.count=" + ((cursor != null) ? cursor.getCount() : 0));    if (mCanSetPreferApn && cursor.getCount() > 0) {        int pos;        cursor.moveToFirst();        // 获取preferredapn在数据库中的id号        pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID));        for(ApnSetting p : mAllApnSettings) {            log("getPreferredApn: apnSetting=" + p);            // 遍历apn集合,存在id相同,并且apn type符合preferredapn的要求时,则返回给preferredapn            if (p.id == pos && p.canHandleType(mRequestedApnType)) {                log("getPreferredApn: X found apnSetting" + p);                cursor.close();                return p;            }        }    }    if (cursor != null) {        cursor.close();    }    log("getPreferredApn: X not found");    return null;}


getPreferredApn方法就是从数据库中读取出preferred的apn,如果存在,且preferredapn的id与之前创建的apn集合中一个apn的id相同,且满足该apn的type参数中有一个default,则成功获取preferredapn。
接着,在createAllApnList方法中,对该preferredapn进行判断,如果该apn的mccmnc不是这张卡的mccmnc,则将preferredapn置为空,且清除数据库中的数据。
setPreferredApn(-1)方法:
private void setPreferredApn(int pos) {    if (!mCanSetPreferApn) {        log("setPreferredApn: X !canSEtPreferApn");        return;    }    String subId = Long.toString(mPhone.getSubId());    Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);    log("setPreferredApn: delete");    ContentResolver resolver = mPhone.getContext().getContentResolver();    // 删除数据库中的数据    resolver.delete(uri, null, null);    if (pos >= 0) {        // 如果pos为-1,则不执行插入,否则插入新的preferredapn,pos即为id        log("setPreferredApn: insert");        ContentValues values = new ContentValues();        values.put(APN_ID, pos);        resolver.insert(uri, values);    }}

到此为止,APN参数创建完毕。