APN的基础知识
在安卓开发中,尤其是网络开发,可能会经常接触到APN,下面讲解一下相关的基础知识。
一、APN简介
APN的完整说明在3GPP规范TS23.003 Clause 9中进行了详细定义。
MCCMNC的定义在3GPP规范TS23.003 Clause 2的IMSI定义中。
APN在GPRS骨干网中用来标识要使用的外部PDN(Packet data network,分组数据网,即常说的Internet),在GPRS网络中代表外部数据网络的总称。
APN由以下两部分组成:
第一部分:APN网络标识:
是用户通过GGSN/PGW(Gateway GPRS Support Node,GPRS网关支持节点/PDN Gateway ,分组数据网网关)可连接到外部网络的标识,该标识由网络运营者分配给ISP(Internet Service Provider,因特网业务提供者)或公司,与其固定Internet域名一致,是APN的必选组成部分。例如 , 定义移动用户通过该接入某公司的企业网,则APN的网络标识可以规划为“www.ABC123.com”。
第二部分:APN运营者标识:
用于标识GGSN/PGW所归属的网络,是APN的可选组成部分。其形式为“MNCxxxx.MCCyyyy.gprs”(3G网络中),或者“MNCxxxx.MCCyyyy.3gppnetwork.org(4G网络中)。APN实际上就是对一个外部PDN的标识,这些PDN包括企业内部网、Internet、WAP网站、行业内部网等专用网络。
二.Apn参数的组成
例:移动apn,把所有的属性都放在一起如下
apn carrier=”中国移动彩信 (China Mobile)” mcc=”460” mnc=”00” apn=”cmwap” proxy=”10.0.0.172” port=”80” mmsc=”http://mmsc.monternet.com” mmsproxy=”10.0.0.172” mmsport=”80” user=”mms” password=”mms” type=”mms” authtype=”1” protocol=”IPV4V6” />
其对应的属性定义如下:
- Carrier:apn的名字,可为空,只用来显示apn列表中此apn的显示名字。
- Mcc:由三位数组成。 用于识别移动用户的所在国家;
- Mnc:由两位或三位组成。 用于识别移动用户的归属PLMN。 MNC的长度(两位或三位数)取决于* MCC的值。
- Apn:APN网络标识(接入点名称),是APN参数中的必选组成部分。此标识由运营商分配。
- Proxy:代理服务器的地址
- Port:代理服务器的端口号
- Mmsc:MMS中继服务器/多媒体消息业务中心,是彩信的交换服务器。
- Mmsproxy:彩信代理服务器的地址
- Mmsport:彩信代理服务器的端口号
- Protocol:支持的协议,不配置默认为IPV4。
- User:用户
- Password:密码
- Authtype:apn的认证协议,PAP为口令认证协议,是二次握手机制。CHAP是质询握手认证协议,是三次握手机制。
image.png - Type: apn的接入点类型
image.png
注意:此表中的数据连接优先级是由低到高,即default数据连接的优先级最低,而hipri数据连接的优先级最高。比如:手机上网聊天,建立的是default数据连接。如果此时接到一条彩信,由于彩信的数据连接是mms,优先级比default高,所以会先断开default数据连接,建立mms数据连接,让手机先收到彩信。所以收发彩信的同时不能上网。(单条pdp连接的情况)
MCC和MNC:mnc的位数由mcc决定。比如,墨西哥334020,此国家的mnc为020,mccmnc的值都固定在了SIM卡保存的IMSI中,配置apn参数时mnc不可简洁为20,否则apn列表中将读取不到此国家的334020运营商的参数。
三.Apn的存储以及初始化
apn在安卓系统中的存储:
apn文件:system/etc/apn-conf.xml
apn数据存储的数据库:/data/data/com.android.providers.telephony/databases/ telephony.db Carriers
表
在安卓源码中的位置:
apn的初始化:
在启动手机时,需要初始化telephony.db
数据库,这时候会读取手机目录system/etc/apn-conf.xml
并把其中的内容加入到Carriers
表中。以后查询有关apn的配置参数都是从Carriers表中取出。
创建并初始化Carriers表:
packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
内部类:DatabaseHelper.java
- 创建数据库
由于添加了UNIQUE 约束条件,如果两个差不多的apn参数满足约束条件内的属性都相等,那么认为是同一组apn参数,将不会重复插入到数据库。
private void createCarriersTable(SQLiteDatabase db, String tableName) { // Set up the database schema if (DBG) log("dbh.createCarriersTable start"); String columns = "(_id INTEGER PRIMARY KEY," + NAME + " TEXT DEFAULT ''," + NUMERIC + " TEXT DEFAULT ''," + MCC + " TEXT DEFAULT ''," + MNC + " TEXT DEFAULT ''," + APN + " TEXT DEFAULT ''," + USER + " TEXT DEFAULT ''," + SERVER + " TEXT DEFAULT ''," + PASSWORD + " TEXT DEFAULT ''," + PROXY + " TEXT DEFAULT ''," + PORT + " TEXT DEFAULT ''," + MMSPROXY + " TEXT DEFAULT ''," + MMSPORT + " TEXT DEFAULT ''," + MMSC + " TEXT DEFAULT ''," + AUTH_TYPE + " INTEGER DEFAULT -1," + TYPE + " TEXT DEFAULT ''," + CURRENT + " INTEGER," + SOURCE_TYPE + " INTEGER DEFAULT 0," + CSD_NUM + " TEXT DEFAULT ''," + PROTOCOL + " TEXT DEFAULT IP," + ROAMING_PROTOCOL + " TEXT DEFAULT IP,"; /// M: add for OMACP service if (OMACP_SUPPORT) { columns += OMACP_ID + " TEXT DEFAULT ''," + NAP_ID + " TEXT DEFAULT ''," + PROXY_ID + " TEXT DEFAULT '',"; } columns += CARRIER_ENABLED + " BOOLEAN DEFAULT 1," + BEARER + " INTEGER DEFAULT 0," + BEARER_BITMASK + " INTEGER DEFAULT 0," + SPN + " TEXT DEFAULT ''," + IMSI + " TEXT DEFAULT ''," + PNN + " TEXT DEFAULT ''," + PPP + " TEXT DEFAULT ''," + MVNO_TYPE + " TEXT DEFAULT ''," + MVNO_MATCH_DATA + " TEXT DEFAULT '',"; columns += SUBSCRIPTION_ID + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," + PROFILE_ID + " INTEGER DEFAULT 0," + MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," + MAX_CONNS + " INTEGER DEFAULT 0," + WAIT_TIME + " INTEGER DEFAULT 0," + MAX_CONNS_TIME + " INTEGER DEFAULT 0," + MTU + " INTEGER DEFAULT 0," + EDITED + " INTEGER DEFAULT " + UNEDITED + "," + USER_VISIBLE + " BOOLEAN DEFAULT 1, " + USER_EDITABLE + " BOOLEAN DEFAULT 1," + // Uniqueness collisions are used to trigger merge code so if a field is listed // here it means we will accept both (user edited + new apn_conf definition) // Columns not included in UNIQUE constraint: name, current, edited, // user, server, password, authtype, type, protocol, roaming_protocol, sub_id, // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask, // user_visible "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));"; db.execSQL("CREATE TABLE " + tableName + columns); db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_DM_TABLE); db.execSQL("CREATE TABLE " + CARRIERS_DM_TABLE + columns); if (DBG) log("dbh.createCarriersTable:-"); }
- 读取apns-conf.xml文件,并初始化Carriers表。
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()); } db.beginTransaction(); try { loadApns(db, confparser); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } 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); }
- 设置APN:
packages/apps/Settings/src/com/android/settings/ApnSettings.java
查询数据库Carriers表中的数据:
private void fillList() { final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator (mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() : SubscriptionManager.INVALID_SUBSCRIPTION_ID); /// M: for plug-in @{ // use mcc&mnc in IMPI to query apn. Log.d(TAG, "before plugin, mccmnc = " + mccmnc); mccmnc = mApnExt.getOperatorNumericFromImpi(mccmnc, SubscriptionManager.getPhoneId(mSubscriptionInfo.getSubscriptionId())); /// @} 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"); */ String where = "numeric=\"" + mccmnc + "\""; /// M: for [C2K APN Customization] @{ if (mSubscriptionInfo != null) { int subId = mSubscriptionInfo.getSubscriptionId(); if (CdmaUtils.isSupportCdma(subId)) { where = CdmaApnSetting.customizeQuerySelectionforCdma(where, mccmnc, subId); } } /// @} where += " AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0"; /// M: for VoLTE, do not show ims apn for non-VoLTE project @{ /* if (mHideImsApn) { where.append(" AND NOT (type='ims')"); } */ //if (!FeatureOption.MTK_VOLTE_SUPPORT || mHideImsApn) { where += " AND NOT (type='ims' OR type='ia,ims')"; //} /// @} /// M: for plug-in where = mApnExt.getFillListQuery(where, mccmnc); Log.d(TAG, "fillList where: " + where); String order = mApnExt.getApnSortOrder(Telephony.Carriers.DEFAULT_SORT_ORDER); order = "_id asc"; Log.d(TAG, "fillList sort: " + order); Cursor cursor = getContentResolver().query( Telephony.Carriers.CONTENT_URI, new String[]{"_id", "name", "apn", "type", "mvno_type", "mvno_match_data", "sourcetype"}, where.toString(), null, order); if (cursor != null) { Log.d(TAG, "fillList, cursor count: " + cursor.getCount()); IccRecords r = null; if (mUiccController != null && mSubscriptionInfo != null) { r = mUiccController.getIccRecords(SubscriptionManager.getPhoneId( mSubscriptionInfo.getSubscriptionId()), UiccController.APP_FAM_3GPP); } PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list"); apnList.removeAll(); /// M: for plug-in, use Preference instead ApnPreference for the // convenience of plug-in side ArrayList<Preference> mnoApnList = new ArrayList<Preference>(); ArrayList<Preference> mvnoApnList = new ArrayList<Preference>(); ArrayList<Preference> mnoMmsApnList = new ArrayList<Preference>(); ArrayList<Preference> mvnoMmsApnList = new ArrayList<Preference>(); 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); /// M: check source type, some types are not editable int sourcetype = cursor.getInt(SOURCE_TYPE_INDEX); /// M: skip specific APN type if (shouldSkipApn(type)) { cursor.moveToNext(); continue; } /// M: for plug-in name = mApnExt.updateApnName(name, sourcetype); ApnPreference pref = new ApnPreference(getPrefContext()); pref.setKey(key); pref.setTitle(name); pref.setSummary(apn); pref.setPersistent(false); pref.setOnPreferenceChangeListener(this); pref.setSubId(mSubscriptionInfo.getSubscriptionId()); /// M: for [Read Only APN] pref.setApnEditable(mApnExt.isAllowEditPresetApn(type, apn, mccmnc, sourcetype)); pref.setSubId(mSubscriptionInfo == null ? null : mSubscriptionInfo .getSubscriptionId()); /// M: for ALPS02500557, do not select emergency APN boolean selectable = ((type == null) || (!type.equals("mms") && !type.equals("ia") && !type.equals("ims") && !type.equals("emergency"))) /// M: for plug-in && mApnExt.isSelectable(type); pref.setSelectable(selectable); Log.d(TAG, "mSelectedKey = " + mSelectedKey + " key = " + key + " name = " + name + " selectable=" + selectable); if (selectable) { /// M: select prefer APN later, as the apn list are not solid now @{ /* if ((mSelectedKey != null) && mSelectedKey.equals(key)) { pref.setChecked(); } */ /// @} addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData); /// M: For CT feature,when apns-conf.xml add type extra value "supl", // selectable maybe ture when 46011 mms, so need this method. mApnExt.customizeUnselectableApn(type, mvnoType, mvnoMatchData, mnoApnList, mvnoApnList, mSubscriptionInfo == null ? null : mSubscriptionInfo .getSubscriptionId()); } else { addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData); /// M: for plug-in mApnExt.customizeUnselectableApn(type, mvnoType, mvnoMatchData, mnoMmsApnList, mvnoMmsApn mSubscriptionInfo == null ? null : mSubscriptionInfo .getSubscriptionId()); } 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); } /// M: always set a prefer APN setPreferApnChecked(mnoApnList); /// M: update screen enable state according to airplane mode, SIM radio status, etc. updateScreenEnableState(getActivity()); } }