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

Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun) 全面解析android APN 此博文写作坚持以下理念: 图胜于表,表胜于言; 最好的解释是不用解释; 1. APN的概念 1.1 APN的定义 Definition of Access Point Name In the GPRS backbone, an Access Point Name (APN) is a reference to a GGSN. To support inter-PLM... 阅读详情
手机可以上网,首先要建立数据连接,建立数据连接之前需要有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列表并创建preferredapn
private 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方法
// 从数据库中读取出所有符合要求的apn
private 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的构造方法创建出一个apnsetting
private 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是否为虚拟运营商的apn
public 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方法

// 合并相似的apn
private 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参数创建完毕。

Android MVNO APN 字段配置方法 其中Mvno Type通常有如下三种类型 :GID/SPN/IMSI1、IMSI(International Mobile Subscriber Identity):用于指定国际移动用户识别码(IMSI),这是SIM卡中的一个标识符,用于识别移动用户。2、GID(Group Identifier Level 1):用于指定组标识级别1串,用于在MVNO网络中识别用户所属的群组。3、SPN(Service Provider Name):用于指定服务提供商名称,用于在MVNO网络中标识服务提供商。 阅读详情

相关推荐

Android 虚拟运营商apn与spn配置

可以通过EF_SPN、EF_IMSI、EF_PNN、EF_GID1区分

weicaihui2008的博客 9437

mno/mvno__2020.07.16

在进行apn配置时,会有mno/mvno项,如果时mno表示是实体运营商,如果是mvno则表示是虚拟运营商。 mvno_type:iccid、imsi、gid、spn。

weixin_40316053的博客 4366

Android APN配置与网络连接:从系统解析到数据业务建立的深度剖析

本文深入解析Android系统中APN配置与网络连接的完整流程,从基础概念到数据业务建立的核心机制。详细探讨APN加载流程、SIM卡匹配、数据连接建立等关键环节,并提供常见问题排查方法与性能优化建议,帮助开发者解决Android网络连接中的实际问题。

weixin_33672109的博客 496

IMSI,TMSI,MSISDN, MSRN 和 IMEI

1、IMSIIMSI 全称是International Mobile Subscriber Identification Number,国际移动用户识别,国际上为唯一识别一个移动用户所分配的号码, 它存储在SIM卡、HLR/VLR中,是一个独特的(最多15位)代码用于识别个人用户对GSM网络,此码在网中所有位置包括漫游区都是有效的。我们的手机号码在系统中是被转换为IMSI进行通信的。IMSI由三个部分组成:·   移动国家代码(MCC )·   移动网络代码(MNC)·   移动电话用户身份证号码(M

marken 1万+

Android APN配置与加载流程全解析:从XML到PDN建立

APN(接入点名称)是移动网络数据连接的关键参数,它决定了设备如何接入运营商网络并建立PDN承载。很多开发者对APN的理解停留在设置页手动添加入网点,但实际系统定制中,APN从apns-conf.xml配置文件到SQLite数据库,再经Telephony框架的查询、匹配、转换,最终通过DataProfile下发给modem,任何环节出错都会导致数据业务异常。在AOSP中,TelephonyProvider负责XML解析与落库,DcTracker执行MCC/MNC过滤和类型匹配,而MVNO规则、carrier

weixin_30036289的博客 337

Android 7.1开机后上不了网?手把手教你排查APN加载与DcTracker拨号流程

本文详细解析了Android 7.1开机后无法上网的问题,重点介绍了APN加载与DcTracker拨号流程的排查方法。通过分析TelephonyProvider数据库初始化、APN配置文件解析及DcTracker模块的拨号流程,提供了一套完整的故障排查指南,帮助开发者快速定位和解决网络连接问题。

weixin_42699679的博客 161

数据业务建立流程之常规APN参数的创建

区别于前面的紧急APN,这里的APN参数主要指常规的SIM卡APN参数,手机在上网时必须传递正确的APN参数给运营商才可以接入移动网络,而常规APN参数的创建是由监听器触发的。         前面在DcTracker初始化过程中注册了大量监听器,其中有两个监听器可以触发APN的创建过程:1、SIM载入完毕;2、APN改变。这两个事件所导致的APN创建流程也都是类似的,分别是:

wf_fln的博客 982

广电APN设置全解析:cbnet与cbwap实测对比,网速提升关键在信号与SA模式

接入点名称(APN)是移动网络数据业务路由的关键参数,决定手机如何接入运营商核心网并建立数据通道。对于中国广电用户而言,正确理解APN原理比盲目照搬参数更重要:官方默认值cbnet经过全网优化,稳定性与兼容性最优;cbwap因叠加代理网关,时延和速率表现欠佳;cmnet则存在合规风险且连接极不稳定。在实际工程实践中,影响广电网速的首要因素是700MHz黄金频段覆盖质量,其次是5G SA组网模式与终端基带能力,APN参数反而只是保底配置。用户在不同手机品牌上配置广电APN时,需注意系统路径差异及“PAP认证”

weixin_29252171的博客 483

Android 12 版本 Data Call 移动数据业务流程分析

本文将通过分析Android12 版本的源码,带您一起梳理清楚移动数据业务的原理和流程。

moke926的博客 7713

Telephony Data

ConnectivityService链接服务介绍一 . 管理网络提供者(NetworkFactory) WIFI 数据 蓝牙二 . 网络有效性检测(NetworkMonitor)三 . 路由配置信息的获取(NetworkAgent)四 . 网络物理端口的设置(Netd)具体Log如下图所示ConnectivityService提供requestNetwork相关接口供,三方APP请求对应网络数据。那么如果APP不做网络数据请求,是不是数据就不会建立了呢?

tjpuzm的专栏 1475

RK3568 Android12平台移远EM05-CE 4G模块适配实战

在嵌入式Linux与Android BSP开发中,4G模块适配是连接硬件通信与上层应用的关键环节。其核心链路包括内核USB驱动识别、RIL库协议对接以及数据通路配置。设备通过USB枚举出ttyUSB串口和网卡节点,RIL库负责完成SIM卡识别、网络注册与拨号,最终由Android netd配置路由实现上网。这一套机制在商显一体机、工业平板、边缘盒子等场景中具有广泛的应用价值。本文以RK3568处理器与Android12系统为平台,详细梳理了移远EM05-CE模块的整体适配流程,涵盖QMI模式选择、内核con

weixin_33884611的博客 367

基于Android平台的APN加载流程分析

APN是GGSN的引用,被internal GPRS DNS转换为GGSN的IP地址。GGSN全称Gateway GPRS Support Node, 网关GPRS支持节点。对内:网络传输;(网络接入控制,分组数据的过滤)对外:路由器(路由选择和分组的转发,IP地址分配)为了访问网络,手机必须设置合适的APN参数。APN的英文全称是Access Point Name,全称接入点,是手机上网时必现配置的参数。APN决定了用户的手机通过哪种接入方式来访问什么样的网络。

四儿家的小祖宗的博客 3404

SAP VC实战:用CU01和CS02搞定BOM里的‘智能’对象相关性(附语法避坑指南)

在工业制造领域,产品配置的复杂性往往超出想象。想象一下,当客户需要定制一台工业设备时,可能有数百种配置选项相互影响——从基础材质到动力系统,从控制模块到安全装置。如何在SAP系统中优雅地管理这种复杂性?对象相关性(Object Dependencies)就是解决这一挑战的利器。

weixin_42524824的博客 466

Android6.0 Telephony Frameworks之数据业务建立流程

数据业务(数据网络,即SIM卡上网)的建立需满足两个条件:1.选择并激活合适的APN;2.PS域已处于Attached状态。默认状态下,手机开机后就会发起PS附着的信令给网络。而所有数据网络的请求(打开关闭数据开关、切换数据卡、修改APN等)都必须经过DcTacker.java处理,其是Telephony框架中负责数据业务的核心。 一、数据业务建立之准备知识 1. DcTracker...

Xsinwa的博客 3187

CST--时域仿真的网格设置技巧

对于物理概念清晰的工程师而言,使用时域算法几乎能够解决所有电磁仿真问题,但是,掌握时域算法的仿真技巧比掌握频域的难度大,这恐怕也是很多仿真工程师更喜欢使用频域算法的原因,因此,对于时域算法的灵活应用更能考验你是否是一名合格的仿真工程师。

一只豌豆象的博客 6856

从零构建:如何用MATLAB打造你的第一个Stewart平台运动学仿真器

本文详细介绍了如何使用MATLAB从零构建一个六自由度Stewart平台运动学仿真器,涵盖基础理论、环境配置、运动学建模、动态轨迹规划和实时可视化实现。通过MATLAB的Robotics System Toolbox和Simulink等工具,读者可以掌握六自由度Stewart平台的逆运动学求解、正运动学求解及仿真系统集成,适用于飞行模拟器和精密定位等应用场景。

peach的博客 864

四、 Android 数据业务APN参数的创建

在前两节简单介绍了连接管理的大致框架,数据链接的准备工作,包括APN的初始化与默认APN使能,DcTracker的构造,包括各种事件的注册等工作。但是数据链接的打开不止是只有用户主动去打开,Android可以提供数据业务的对象主要有,移动数据网络、WIFI、蓝牙、网线等,这些连接本身都可以独立使用,但是对于用户来说,每一时刻又最多只能使用一种方式接入网络,那么当这些功能同时打开时,比如即使用户打开了

夏菠 1776

win11本地部署openclaw实操第5集-X11图形化界面搭建,让WSL2“看见”世界

本文介绍了在WSL2中实现图形界面(GUI)的方法。由于WSL2默认没有图形界面,需要使用X11服务器作为"翻译显示器",将Linux程序的图形指令渲染到Windows窗口中。推荐使用VcXsrv作为X11服务器,详细说明了其安装和配置步骤,包括选择"One large window"显示模式、禁用访问控制等关键设置。随后在Ubuntu中安装轻量级xfce4桌面环境,并通过设置DISPLAY环境变量将图形输出指向VcXsrv。最终通过startxfce4命令成功在Vc

weixin_44626085的博客 810

android系统之apn相关

工作一年了,好久没来,默默地当颗小螺丝钉。今天借此机会把学习的写一下,记录下。最近工作在交接,正好总结回顾,这一年,主要和三个字母打交道A,P,N。 APN概述: APN 的类型分为web(internet),wap,mms三种类型,用于手机中上网时数据交换的接入点名称配置与显示。 APN配置的时候,出现的type的值主要有:default,mms,supl,dun。Defau

sir_zeng的专栏 1841

educoder中Hive综合应用案例 — 用户搜索日志分析

Hive综合应用案例 — 用户搜索日志分析

weixin_46026136的博客 4760

022、Conv-BN-SiLU 基础卷积块:构造函数参数计算,前向传播的 PyTorch 逐行实现

本文详细解析了Conv-BN-SiLU卷积块的实现细节与注意事项。作者从调试经历出发,指出bias参数冗余会导致数值不稳定,强调卷积层应设置bias=False。文章对比了SiLU与ReLU的特性差异,逐行分析了构造函数参数计算(包括自动填充、分组卷积等)和前向传播过程,特别讲解了BN层与SiLU激活的数值特性。最后分享了三个实践中的常见问题:BN的momentum设置、SiLU输出范围的影响,以及分组卷积的通道对齐要求,并提供了卷积与BN融合的推理优化技巧。全文聚焦实际工程实现中的关键细节,具有较强实操指

ai技术增长的博客 217

i.MX8M开发板Android9系统4G模块调试指南

在嵌入式系统开发中,4G通信模块的集成调试是连接硬件与移动网络的关键环节。其工作原理涉及底层驱动加载、RIL(Radio Interface Layer)服务交互以及PPP网络协议栈建立等技术栈。通过合理配置APN参数和优化射频性能,可显著提升物联网设备的网络连接稳定性,这在工业平板、智能车载等Android嵌入式设备中尤为重要。针对i.MX8M处理器平台,开发者需要掌握内核驱动验证、AT命令调试等核心技能,并配合QXDM等专业工具进行信令分析。实际项目中常见的SIM卡注册失败、RIL崩溃等问题,往往需要通

weixin_30457551的博客 287

【第三方互联】12、支付宝(Alipay)授权第三方登录

我们创建了支付宝开放平台的网页&移动应用,审核通过后,我们需要拿到appid,支付宝公钥,私钥进项开发 一、添加应用信息至项目环境中 我们保存了 appid、应用私钥、支付宝公钥、支付宝回调地址等信息,这里的公钥为支付宝开放平台为我们生成的“支付宝公钥” 二、引入 Maven 依赖 <!-- 支付宝SDK --> <dependency> <g.........

JACKAL 21万+

Android展讯平台VoLTE功能适配与CTA认证实战

本文详细解析了Android展讯平台VoLTE功能适配与CTA认证的实战经验。从VoLTE技术基础到展讯平台适配背景,再到CTA认证关键要求解析和配置实战,提供了全面的技术指导。重点介绍了volte-conf.xml文件的修改与验证步骤,以及调试技巧和常见问题排查方法,帮助开发者高效完成VoLTE功能适配并通过CTA认证。

weixin_30824599的博客 148

Transformer 时间序列预测实战

Transformer 时间序列预测实战

机器学习深度学习业余选手 743

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

上一篇: Activity中使用Dialog的方式
下一篇: UriMatcher使用原理的源码分析
Sun_Jingjing
博客等级 码龄10年 1粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值