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

每篇一格言

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

此博文写作坚持以下理念:

图胜于表,表胜于言;
最好的解释是不用解释;

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)

<apn carrier=“连接互联网” //名称
mcc=“460” //MCC
mnc=“07” //MNC
apn=“cmnet” //接入点
type=“default,supl,net” //类型
/>
其他更多参数:

在这里插入图片描述

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

在这里插入图片描述

1.3 APN的存储与加载

1.3.1 APN的存储位置

以XML的格式存储:Apns-conf.xml
sourcecode文件路径:
MTK平台(通常):alps\mediatek\frameworks\base\telephony\etc
高通平台(通常):android\vendor\qcom\proprietary\telephony-apps\etc

编译后打包到systemimage中,
UE文件路径:system/etc/ Apns-conf.xml

当UE开机后,根据XML写入database中。
table:content://telephony/carriers

1.3.2 APN的加载

到database:TelephonyProvider读取XML并在database中插入table
到UI menu:根据MCCMNC从database读取对应参数项填写到UI菜单序列中
PDP建立时:DCtracker负责创建/更新waiting APN list

关系图:

1.4虚拟运营商的APN

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

APN的概念部分到此告一段落,下面做个总结:

在这里插入图片描述

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/
创建时机是telephonyprovider的内部类DatabaseHelper的oncreate时。
DatabaseHelper类负责db的增删改查工作。

2.2 APN的读取与菜单显示

接下来是菜单中显示插入的SIM卡对应的APN。
这部分在ApnSettings.java (amss\linux\android\packages\apps\settings\src\com\android\settings) 中的fillList方法实现。
该方法主要根据SIM卡的mccmnc去query db并将APN填入菜单中。不再具体讨论。
对于MVNO SIM卡,除了mccmnc还需根据MVNO type和MVNO value过滤。详细见mvnoMatches方法。

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

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

2.3 PDP中的APN

UE插入SIM卡后,telephony根据SIM卡的mccmnc,先建一个包含该SIM卡所有APN的list。
这一步在DcTracker中实现。Call flow见下图
在这里插入图片描述
下面看下createAllApnList的实现

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

有了all apn,在setup data时,根据apn的类型,从all apn list中筛选出“waiting apn list”,用于建立PDP

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

2.4 modem中的APN

后续更新

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

附录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<ApnPreference> mnoApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mvnoApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mnoMmsApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mvnoMmsApnList = new ArrayList<ApnPreference>();

            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<ApnPreference> mnoList,
                              ArrayList<ApnPreference> 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<ApnSetting> buildWaitingApns(String requestedApnType, int radioTech) {
        if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
        ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();

        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信号显示与刷新,总篇

本文为原创文章。看完点赞,每天进步一点点~
发布了19 篇原创文章 · 获赞 8 · 访问量 2016

猜你喜欢

转载自blog.csdn.net/GentelmanTsao/article/details/103234535