Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)

每篇一格言:
人生就像滚雪球,关键是要找到足够湿的雪,和足够长的坡。
——沃伦巴菲特

目录

  • 1. APN的概念
    • 1.1 APN的定义
      • Definition of Access Point Name
    • 1.2 APN的参数
          • 举例(CMCC APN)
          • APN的类型:
    • 1.3 APN的存储与加载
        • 1.3.1 APN的存储位置
        • 1.3.2 APN的加载
    • 1.4虚拟运营商的APN
  • 2. APN的实现机制
      • 2.1 APN的创建: 从XML到database
      • 2.2 APN匹配SIM卡与菜单显示
      • 2.3 PDP时APN的选择
      • 2.4 modem中的APN
  • 附录1 从xml加载到database
  • 附录2。APN的读取与菜单显示
  • 附录3 PDP中的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-PLMN roaming, the internal GPRS DNS functionality is used to translate the APN into the IP address of the GGSN.
——3gpp 23.003

从定义可看出,APN是GGSN的引用,被internal GPRS DNS转换为GGSN的IP地址。
在这里插入图片描述
那么GGSN是什么,又是做什么的?
GGSN: Gateway GPRS Support Node, 网关GPRS支持节点
GGSN主要起网关作用,所扮演的角色:
对内:网络传输; (网络接入控制,分组数据的过滤)
对外:路由器(路由选择和分组的转发,IP地址分配)

1.2 APN的参数

一个典型的APN有如下参数(名称,MCCMNC,接入点,类型)

举例(CMCC APN)

mcc=“460” //MCC
mnc=“07” //MNC
apn=“cmnet” //接入点
type=“default,supl,net” //类型
/>
其他更多参数:

Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)_第1张图片

Note :每个有数据业务的运营商都会设定自己的APN,APN可能是多条,包括3G使用、4G使用,NET和WAP,不同APN的使用范围和收费会有差别。

APN的类型:

Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)_第2张图片

1.3 APN的存储与加载

1.3.1 APN的存储位置

以XML的格式存储。
文件名
Apns-conf.xml

源码文件路径
MTK平台(通常):alps\mediatek\frameworks\base\telephony\etc
高通平台(通常):android\vendor\qcom\proprietary\telephony-apps\etc

UE文件路径
system/etc/ Apns-conf.xml

当UE开机后,读取XML并写入到database中。

database中table名
content://telephony/carriers
注:有些平台为了适配dual SIM,可能会加上sub1/sub2等。

1.3.2 APN的加载

加载到database:
TelephonyProvider读取XML并在database中插入apn的table。

加载到UI菜单:
根据SIM卡的MCCMNC,去匹配database中同样MCCMNC的APN项,并将匹配到的APN填写到菜单列表。

加载到PDP请求:
由DCtracker负责创建/更新waiting APN list,供PDP选用。

1.4虚拟运营商的APN

虚拟运营商(MVNO)没有自营网段,使用了主运营商的网段,因而和主运营商有相同的MCCMNC。为了能够与主运营商区分,虚拟运营商的APN还包含了MVNO参数。MVNO参数分为SPN/PNN/IMSI/GID1,是从SIM卡对应栏位读取的值,目的是从该值中判断该SIM卡是否属于MVNO。

在加载MVNO SIM卡的APN时,会同时去匹配MCCMNC和MVNO参数。

APN的概念部分到此告一段落,下面做个总结:
Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)_第3张图片

2. APN的实现机制

2.1 APN的创建: 从XML到database

在上一章中我们讲到,APN的原始参数存放在XML中;
而UE是通过query database的方式使用APN;
因而必然需要将APN从XML录入到database,这一步在telephonyprovider中实现。

为了更清晰的理解实现思路,避免过多的源码干扰阅读节奏,接下来的分析中涉及到的源码部分,都以伪代码的形式给出,详细的代码以附录形式给出。

分析telephonyprovider中的initDatabase方法:

private void initDatabase(SQLiteDatabase db) {
 1.打开APN xml文件etc/apns-conf.xml
 2. 获得文件句柄后,使用FileReader得到文件字符流
 3. 检查APN version一致性
 4.加载XMl中的数据到database,具体见loadApns方法
}

对第3点:“检查APN version一致性” 的说明:
随着OS升级,APN字段也在更新,因此检查APN version的目的是为了避免XML与OS不兼容。若version不一致,抛出异常:

// Sanity check. Force internal version and confidential versions to agree
                int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
                if (publicversion != confversion) {
                    throw new IllegalStateException("Internal APNS file version doesn't match "
                            + confFile.getAbsolutePath());
                }

分析loadApns方法

private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
1。每次读取parser中的一个element,也就是一条APN数据
2.通过getrow方法将APN转换为Contentvalues;
3.最后通过insertAddingDefaults将键值写入database:
}

写入的table名是CARRIERS_TABLE,该table由createCarriersTable方法创建。
该table url是:content://telephony/carriers/

table 创建时机:
telephonyprovider的内部类DatabaseHelper在oncreate时创建APN table。
DatabaseHelper类负责APN database的增删改查。

2.2 APN匹配SIM卡与菜单显示

设备插入SIM后,设置菜单中会显示该SIM卡对应的APN菜单。
这部分在ApnSettings.java (android\packages\apps\settings\src\com\android\settings) 中的fillList方法实现的。该方法主要根据SIM卡的mccmnc去query db并将APN填入菜单中。感兴趣可自行查看,不再具体讨论。

对于MVNO SIM卡,除了mccmnc还需根据MVNO type和MVNO value过滤。详细见mvnoMatches方法。

备注1.
SIM卡的mccmnc获取的是PROPERTY_ICC_OPERATOR_NUMERIC的值,而PROPERTY_ICC_OPERATOR_NUMERIC是在SIM loaded后根据IMSI得到。(取前5位or前6位)

备注2.
mvno type举例:SPN类型,通过getServiceProviderName获取,其值来自SIM卡中的EF_SPN:分为SIM (EF_SPN=0x6F46)或 RUIM (EF_RUIM_SPN=0x6F41)。

有关SIM卡加载的更多内容,请移步这里:
Android:USIM软件架构与加载流程

2.3 PDP时APN的选择

UE插入SIM卡后,telephony根据SIM卡的mccmnc,先创建一个包含该SIM卡所有APN的列表(AllApnList)。

这一步在DcTracker中实现。Call flow见下图:
Android:一篇就够!全面&详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)_第4张图片
下面列出了createAllApnList的实现步骤:

protected void createAllApnList() {
1。根据SIM卡mccmnc,创建APN list
2。设置preferred APN
3。Set DataProfile in modem
}

有了AllApnList,在setup data时,根据数据业务类型(上网/彩信/…),从all apn list中筛选出“waiting apn list”,用于建立PDP.

private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
//根据requestedApnType和radioTech筛选waiting apn
}

2.4 modem中的APN

modem中同样存储了APN,详细内容可参考我的另一篇文章:
Modem2G/3G/4G/5G:APN:使用AT+CGDCONT命令设置modem默认APN(CID1)

你可能想知道更多详细内容,下面源码以附录形式给出。

附录1 从xml加载到database

TelephonyProvider.java (amss\linux\android\packages\providers\telephonyprovider\src\com\android\providers\telephony)

        private void initDatabase(SQLiteDatabase db) {
            if (VDBG) log("dbh.initDatabase:+ db=" + db);
            // Read internal APNS data
            Resources r = mContext.getResources();
            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
            int publicversion = -1;
            try {
                XmlUtils.beginDocument(parser, "apns");
                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
                loadApns(db, parser);
            } catch (Exception e) {
                loge("Got exception while loading APN database." + e);
            } finally {
                parser.close();
            }

            // Read external APNS data (partner-provided)
            XmlPullParser confparser = null;
            File confFile = getApnConfFile();

            FileReader confreader = null;
            if (DBG) log("confFile = " + confFile);
            try {
                confreader = new FileReader(confFile);
                confparser = Xml.newPullParser();
                confparser.setInput(confreader);
                XmlUtils.beginDocument(confparser, "apns");

                // Sanity check. Force internal version and confidential versions to agree
                int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
                if (publicversion != confversion) {
                    log("initDatabase: throwing exception due to version mismatch");
                    throw new IllegalStateException("Internal APNS file version doesn't match "
                            + confFile.getAbsolutePath());
                }

                loadApns(db, confparser);
            } catch (FileNotFoundException e) {
                // It's ok if the file isn't found. It means there isn't a confidential file
                // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
            } catch (Exception e) {
                loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
                        e);
            } finally {
                // Get rid of user/carrier deleted entries that are not present in apn xml file.
                // Those entries have edited value USER_DELETED/CARRIER_DELETED.
                if (VDBG) {
                    log("initDatabase: deleting USER_DELETED and replacing "
                            + "DELETED_BUT_PRESENT_IN_XML with DELETED");
                }

                // Delete USER_DELETED
                db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);

                // Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
                ContentValues cv = new ContentValues();
                cv.put(EDITED, USER_DELETED);
                db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);

                // Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
                cv = new ContentValues();
                cv.put(EDITED, CARRIER_DELETED);
                db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);

                if (confreader != null) {
                    try {
                        confreader.close();
                    } catch (IOException e) {
                        // do nothing
                    }
                }

                // Update the stored checksum
                setApnConfChecksum(getChecksum(confFile));
            }
            if (VDBG) log("dbh.initDatabase:- db=" + db);

        }
private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
            if (parser != null) {
                try {
                    db.beginTransaction();
                    XmlUtils.nextElement(parser);
                    while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                        ContentValues row = getRow(parser);
                        if (row == null) {
                            throw new XmlPullParserException("Expected 'apn' tag", parser, null);
                        }
                        insertAddingDefaults(db, row);
                        XmlUtils.nextElement(parser);
                    }
                    db.setTransactionSuccessful();
                } catch (XmlPullParserException e) {
                    loge("Got XmlPullParserException while loading apns." + e);
                } catch (IOException e) {
                    loge("Got IOException while loading apns." + e);
                } catch (SQLException e) {
                    loge("Got SQLException while loading apns." + e);
                } finally {
                    db.endTransaction();
                }
            }
        }

附录2。APN的读取与菜单显示

ApnSettings.java (amss\linux\android\packages\apps\settings\src\com\android\settings)

private void fillList() {
        final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId()
                : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(subId);
        Log.d(TAG, "mccmnc = " + mccmnc);
        StringBuilder where = new StringBuilder("numeric=\"" + mccmnc +
                "\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0");

        if (mHideImsApn) {
            where.append(" AND NOT (type='ims')");
        }

        appendFilter(where);

        Log.d(TAG, "where = " + where.toString());

        Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
                "_id", "name", "apn", "type", "mvno_type", "mvno_match_data", "bearer", "bearer_bitmask"}, where.toString(),
                null, Telephony.Carriers.DEFAULT_SORT_ORDER);

        if (cursor != null) {
            IccRecords r = null;
            if (mUiccController != null && mSubscriptionInfo != null) {
                r = mUiccController.getIccRecords(
                        SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
            }
            PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
            apnList.removeAll();

            ArrayList mnoApnList = new ArrayList();
            ArrayList mvnoApnList = new ArrayList();
            ArrayList mnoMmsApnList = new ArrayList();
            ArrayList mvnoMmsApnList = new ArrayList();

            mSelectedKey = getSelectedApnKey();
            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                String name = cursor.getString(NAME_INDEX);
                String apn = cursor.getString(APN_INDEX);
                String key = cursor.getString(ID_INDEX);
                String type = cursor.getString(TYPES_INDEX);
                String mvnoType = cursor.getString(MVNO_TYPE_INDEX);
                String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);

                //Special requirement of some operators, need change APN name follow language.
                String localizedName = Utils.getLocalizedName(getActivity(), cursor, NAME_INDEX);

                if (!TextUtils.isEmpty(localizedName)) {
                    name = localizedName;
                }
                int bearer = cursor.getInt(BEARER_INDEX);
                int bearerBitMask = cursor.getInt(BEARER_BITMASK_INDEX);
                int fullBearer = ServiceState.getBitmaskForTech(bearer) | bearerBitMask;
                int radioTech = networkTypeToRilRidioTechnology(TelephonyManager.getDefault()
                        .getDataNetworkType(subId));
                if (!ServiceState.bitmaskHasTech(fullBearer, radioTech)
                        && (bearer != 0 || bearerBitMask != 0)) {
                    // In OOS, show APN with bearer as default
                    if ((radioTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) || (bearer == 0
                            && radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)) {
                        cursor.moveToNext();
                        continue;
                    }
                }
                ApnPreference pref = new ApnPreference(getPrefContext());

                pref.setKey(key);
                pref.setTitle(name);
                pref.setSummary(apn);
                pref.setPersistent(false);
                pref.setOnPreferenceChangeListener(this);
                pref.setSubId(subId);

                boolean selectable = ((type == null) || !type.equals("mms"));
                pref.setSelectable(selectable);
                if (selectable) {
                    if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
                        pref.setChecked();
                    }
                    addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
                } else {
                    addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
                }
                cursor.moveToNext();
            }
            cursor.close();

            if (!mvnoApnList.isEmpty()) {
                mnoApnList = mvnoApnList;
                mnoMmsApnList = mvnoMmsApnList;

                // Also save the mvno info
            }

            for (Preference preference : mnoApnList) {
                apnList.addPreference(preference);
            }
            for (Preference preference : mnoMmsApnList) {
                apnList.addPreference(preference);
            }
        }
    }

MVNO的处理:

private void addApnToList(ApnPreference pref, ArrayList mnoList,
                              ArrayList mvnoList, IccRecords r, String mvnoType,
                              String mvnoMatchData) {
        if (r != null && !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)) {
            if (ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData)) {
                mvnoList.add(pref);
                // Since adding to mvno list, save mvno info
                mMvnoType = mvnoType;
                mMvnoMatchData = mvnoMatchData;
            }
        } else {
            mnoList.add(pref);
        }
    }

附录3 PDP中的APN

DcTracker.java
createAllApn:

protected void createAllApnList() {
        mMvnoMatched = false;
        mAllApnSettings = new ArrayList<>();
        IccRecords r = mIccRecords.get();
        String operator = mPhone.getOperatorNumeric();
        if (operator != null) {
            String selection = Telephony.Carriers.NUMERIC + " = '" + operator + "'";
            // query only enabled apn.
            // carrier_enabled : 1 means enabled apn, 0 disabled apn.
            // selection += " and carrier_enabled = 1";
            if (DBG) log("createAllApnList: selection=" + selection);

            // ORDER BY Telephony.Carriers._ID ("_id")
            Cursor cursor = mPhone.getContext().getContentResolver().query(
                    Telephony.Carriers.CONTENT_URI, null, selection, null, Telephony.Carriers._ID);

            if (cursor != null) {
                if (cursor.getCount() > 0) {
                    mAllApnSettings = createApnList(cursor);
                }
                cursor.close();
            }
        }

        addEmergencyApnSetting();

        dedupeApnSettings();

        if (mAllApnSettings.isEmpty()) {
            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 {
            mPreferredApn = getPreferredApn();
            if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
                mPreferredApn = null;
                setPreferredApn(-1);
            }
            if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
        }
        if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);

        setDataProfilesAsNeeded();
    }

筛选候选apn用于PDP

private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
        if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
        ArrayList apnList = new ArrayList();

        if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
            ApnSetting dun = fetchDunApn();
            if (dun != null) {
                apnList.add(dun);
                if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
                return apnList;
            }
        }

        String operator = mPhone.getOperatorNumeric();

        // This is a workaround for a bug (7305641) where we don't failover to other
        // suitable APNs if our preferred APN fails.  On prepaid ATT sims we need to
        // failover to a provisioning APN, but once we've used their default data
        // connection we are locked to it for life.  This change allows ATT devices
        // to say they don't want to use preferred at all.
        boolean usePreferred = true;
        try {
            usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
                    internal.R.bool.config_dontPreferApn);
        } catch (Resources.NotFoundException e) {
            if (DBG) log("buildWaitingApns: usePreferred NotFoundException set to true");
            usePreferred = true;
        }
        if (usePreferred) {
            mPreferredApn = getPreferredApn();
        }
        if (DBG) {
            log("buildWaitingApns: usePreferred=" + usePreferred
                    + " canSetPreferApn=" + mCanSetPreferApn
                    + " mPreferredApn=" + mPreferredApn
                    + " operator=" + operator + " radioTech=" + radioTech
                    + " IccRecords r=" + mIccRecords);
        }

        if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
                mPreferredApn.canHandleType(requestedApnType)) {
            if (DBG) {
                log("buildWaitingApns: Preferred APN:" + operator + ":"
                        + mPreferredApn.numeric + ":" + mPreferredApn);
            }
            if (mPreferredApn.numeric.equals(operator)) {
                if (ServiceState.bitmaskHasTech(mPreferredApn.bearerBitmask, radioTech)) {
                    apnList.add(mPreferredApn);
                    if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
                    return apnList;
                } else {
                    if (DBG) log("buildWaitingApns: no preferred APN");
                    setPreferredApn(-1);
                    mPreferredApn = null;
                }
            } else {
                if (DBG) log("buildWaitingApns: no preferred APN");
                setPreferredApn(-1);
                mPreferredApn = null;
            }
        }
        if (mAllApnSettings != null) {
            if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
            for (ApnSetting apn : mAllApnSettings) {
                if (apn.canHandleType(requestedApnType)) {
                    if (ServiceState.bitmaskHasTech(apn.bearerBitmask, radioTech)) {
                        if (DBG) log("buildWaitingApns: adding apn=" + apn);
                        apnList.add(apn);
                    } else {
                        if (DBG) {
                            log("buildWaitingApns: bearerBitmask:" + apn.bearerBitmask + " does " +
                                    "not include radioTech:" + radioTech);
                        }
                    }
                } else if (DBG) {
                    log("buildWaitingApns: couldn't handle requested ApnType="
                            + requestedApnType);
                }
            }
        } else {
            loge("mAllApnSettings is null!");
        }
        if (DBG) log("buildWaitingApns: " + apnList.size() + " APNs in the list: " + apnList);
        return apnList;
    }

相关章节:
全面&系统的解析android RSSI信号显示与刷新,总篇

本文为原创文章。看完点赞,每天进步一点点~

你可能感兴趣的:(Android)