基于代码分析,我将详细讲解Android系统如何加载和管理APN配置。
整体架构
APN配置文件 → XML解析 → 数据库存储 → 应用程序查询
第一阶段:APN配置文件来源
1.1 文件存储位置(优先级顺序)
TelephonyProvider查找APN配置文件时按照以下优先级顺序:
// TelephonyProvider.java: 887-896 行
private File getApnConfFile() {
// 1. 默认系统文件 (最低优先级)
File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
// = /system/etc/apns-conf.xml
// 2. OEM定制文件 (更高优先级)
File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);
// = /oem/telephony/apns-conf.xml
// 3. 产品配置文件
File productConfFile = new File(Environment.getProductDirectory(), PARTNER_APNS_PATH);
// = /product/etc/apns-conf.xml
// 4. OTA更新文件 (最高优先级)
File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
// = /data/misc/apns/apns-conf.xml
// 按优先级选择
confFile = pickSecondIfExists(confFile, oemConfFile);
confFile = pickSecondIfExists(confFile, productConfFile);
confFile = pickSecondIfExists(confFile, updatedConfFile); // 最终优先级最高
return confFile;
}
优先级关系:
OTA文件 > 产品文件 > OEM文件 > 系统默认文件
1.2 文件结构
<?xml version="1.0" encoding="utf-8"?>
<apns version="8">
<!-- 每条APN配置 -->
<apn
mcc="310"
mnc="04"
carrier="Verizon"
apn="vzwinternet"
type="default,hipri"
protocol="IP"
roaming_protocol="IP"
proxy="0.0.0.0"
port="0"
mmsproxy="0.0.0.0"
mmsport="0"
mmsc=""
carrier_enabled="true"
user_editable="true"
user_visible="true"
authtype="3"
bearer_bitmask="0"
/>
</apns>
第二阶段:初始化阶段
2.1 数据库初始化时机
当TelephonyProvider首次启动或数据库版本变化时,触发初始化:
// TelephonyProvider.java: 918-998 行
private void initDatabase(SQLiteDatabase db) {
// 步骤1:加载系统内置APN
// 步骤2:加载OEM/Partner提供的APN
// 步骤3:清理已删除的APN
}
2.2 校验和检查
为了避免不必要的数据库重新加载,系统使用校验和机制:
// TelephonyProvider.java: 905-911 行
private boolean apnDbUpdateNeeded() {
File confFile = getApnConfFile();
long newChecksum = getChecksum(confFile); // 计算新校验和
long oldChecksum = getApnConfChecksum(); // 读取旧校验和
if (DBG) log("newChecksum: " + newChecksum);
if (DBG) log("oldChecksum: " + oldChecksum);
return newChecksum != oldChecksum; // 校验和不同则需要更新
}
存储位置:
// SharedPreferences中
SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
sp.getLong(APN_CONF_CHECKSUM, -1); // "apn_conf_checksum"
第三阶段:加载流程
3.1 两层APN数据源
// TelephonyProvider.java: 918-936 行
// 第一层:系统内置APN
Resources r = mContext.getResources();
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
try {
XmlUtils.beginDocument(parser, "apns");
int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
loadApns(db, parser, true); // 加载内置APN (isOverlay=true)
}
// 第二层:OEM/Partner提供的APN
File confFile = getApnConfFile();
FileReader confreader = new FileReader(confFile);
XmlPullParser confparser = Xml.newPullParser();
confparser.setInput(confreader);
// 版本校验:内置版本必须与外部配置版本一致
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
throw new IllegalStateException("Internal APNS file version doesn't match");
}
loadApns(db, confparser, false); // 加载外部APN (isOverlay=false)
3.2 XML解析和数据提取
// TelephonyProvider.java: 2716-2831 行
private ContentValues getRow(XmlPullParser parser, boolean isOverlay) {
ContentValues map = new ContentValues();
// 必需字段:MCC/MNC
String mcc = parser.getAttributeValue(null, "mcc");
String mnc = parser.getAttributeValue(null, "mnc");
String numeric = mcc + mnc; // 如: "31004" (Verizon)
map.put(NUMERIC, numeric);
map.put(MCC, mcc);
map.put(MNC, mnc);
// 基本信息
map.put(NAME, parser.getAttributeValue(null, "carrier"));
map.put(APN, parser.getAttributeValue(null, "apn"));
// 网络参数
addStringAttribute(parser, "proxy", map, PROXY);
addStringAttribute(parser, "port", map, PORT);
// MMS参数
addStringAttribute(parser, "mmsproxy", map, MMSPROXY);
addStringAttribute(parser, "mmsport", map, MMSPORT);
addStringAttribute(parser, "mmsc", map, MMSC);
// 认证信息
addStringAttribute(parser, "user", map, USER);
addStringAttribute(parser, "password", map, PASSWORD);
addIntAttribute(parser, "authtype", map, AUTH_TYPE);
// APN类型 (default, mms, supl, hipri, fota, ims, ia, dun等)
String apnType = parser.getAttributeValue(null, "type");
if (apnType != null) {
apnType = apnType.replaceAll("\\s+", ""); // 移除空格
map.put(TYPE, apnType);
}
// 协议配置
addStringAttribute(parser, "protocol", map, PROTOCOL); // IP/IPV6/IPV4V6
addStringAttribute(parser, "roaming_protocol", map, ROAMING_PROTOCOL);
// 网络技术支持
int networkTypeBitmask = 0;
String networkTypeList = parser.getAttributeValue(null, "network_type_bitmask");
if (networkTypeList != null) {
networkTypeBitmask = getBitmaskFromString(networkTypeList);
}
map.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
// 承载网络类型
int bearerBitmask = 0;
String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
if (bearerList != null) {
bearerBitmask = getBitmaskFromString(bearerList);
}
map.put(BEARER_BITMASK, bearerBitmask);
// 高级配置
addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED);
addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
addBoolAttribute(parser, "user_editable", map, USER_EDITABLE);
addBoolAttribute(parser, "modem_cognitive", map, MODEM_PERSIST);
addBoolAttribute(parser, "always_on", map, ALWAYS_ON);
// MVNO配置 (虚拟运营商)
String mvno_type = parser.getAttributeValue(null, "mvno_type");
if (mvno_type != null) {
String mvno_match_data = parser.getAttributeValue(null, "mvno_match_data");
if (mvno_match_data != null) {
map.put(MVNO_TYPE, mvno_type); // gid, spn, imsi等
map.put(MVNO_MATCH_DATA, mvno_match_data);
}
}
return map;
}
提取的关键字段:
| 字段名 | 含义 | 示例 |
|---|---|---|
| numeric | MCC+MNC | 31004 (Verizon) |
| apn | 接入点名称 | vzwinternet |
| type | APN类型 | default,hipri,mms |
| protocol | 协议 | IP/IPV6/IPV4V6 |
| proxy | 代理地址 | 0.0.0.0 |
| mmsc | MMS中心地址 | mms.verizon.com |
| user/password | 认证凭证 | (可选) |
| bearer_bitmask | 网络技术 | LTE, 3G等 |
第四阶段:数据库插入
4.1 事务化插入
// TelephonyProvider.java: 2865-2888 行
private void loadApns(SQLiteDatabase db, XmlPullParser parser, boolean isOverlay) {
if (parser != null) {
try {
db.beginTransaction(); // 开始事务
XmlUtils.nextElement(parser);
// 循环解析每条APN
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
ContentValues row = getRow(parser, isOverlay); // 解析一条APN
if (row == null) {
throw new XmlPullParserException("Expected 'apn' tag", parser, null);
}
insertAddingDefaults(db, row); // 插入数据库
XmlUtils.nextElement(parser);
}
db.setTransactionSuccessful(); // 提交事务
} catch (XmlPullParserException | IOException | SQLException e) {
// 异常处理
} finally {
db.endTransaction();
}
}
}
4.2 冲突处理
当APN已存在时,系统采用合并策略:
// TelephonyProvider.java: 2891-2931 行
private void insertAddingDefaults(SQLiteDatabase db, ContentValues row) {
row = setDefaultValue(row); // 设置默认值
try {
// 尝试插入 (CONFLICT_ABORT: 如果冲突则中止)
db.insertWithOnConflict(CARRIERS_TABLE, null, row, SQLiteDatabase.CONFLICT_ABORT);
} catch (SQLException e) {
// 插入失败 - 检查是否存在冲突的APN
Cursor oldRow = selectConflictingRow(db, CARRIERS_TABLE, row);
if (oldRow != null) {
ContentValues mergedValues = new ContentValues();
int edited = oldRow.getInt(oldRow.getColumnIndex(EDITED_STATUS));
// 处理编辑状态
if (edited == USER_DELETED) {
// 用户删除但在XML中存在 -> 标记为 USER_DELETED_BUT_PRESENT_IN_XML
edited = USER_DELETED_BUT_PRESENT_IN_XML;
} else if (edited == CARRIER_DELETED) {
edited = CARRIER_DELETED_BUT_PRESENT_IN_XML;
}
mergedValues.put(EDITED_STATUS, edited);
// 合并字段并更新数据库
mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, row,
mergedValues, false, mContext);
}
}
}
4.3 APN编辑状态
系统跟踪APN的编辑状态:
// TelephonyProvider.java
UNEDITED = 0 // 未编辑(系统APN)
USER_EDITED = 1 // 用户编辑
USER_DELETED = 2 // 用户删除
CARRIER_EDITED = 3 // 运营商编辑
CARRIER_DELETED = 4 // 运营商删除
USER_DELETED_BUT_PRESENT_IN_XML = 5 // 用户删除但在XML中存在
CARRIER_DELETED_BUT_PRESENT_IN_XML = 6 // 运营商删除但在XML中存在
清理规则:
// TelephonyProvider.java: 973-984 行
// 加载完成后清理已删除的APN
// 删除用户/运营商明确删除的APN
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);
// 将"删除但在XML中存在"状态改为"已删除"
ContentValues cv = new ContentValues();
cv.put(EDITED_STATUS, USER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);
第五阶段:字段合并策略
当新旧APN冲突时,系统采用智能合并策略:
5.1 APN类型合并
// TelephonyProvider.java: 2938-2982 行
// 如果类型不同,合并所有唯一的类型
// 例如:
// oldType = "default,hipri"
// newType = "default,mms"
// mergedType = "default,hipri,mms"
if (!oldType.equalsIgnoreCase(newType)) {
String[] oldTypes = oldType.toLowerCase(Locale.ROOT).split(",");
String[] newTypes = newType.toLowerCase(Locale.ROOT).split(",");
ArrayList<String> mergedTypes = new ArrayList<String>();
mergedTypes.addAll(Arrays.asList(oldTypes));
for (String s : newTypes) {
if (!mergedTypes.contains(s.trim())) {
mergedTypes.add(s); // 添加新类型
}
}
// 重新组合
StringBuilder mergedType = new StringBuilder();
for (int i = 0; i < mergedTypes.size(); i++) {
mergedType.append((i == 0 ? "" : ",") + mergedTypes.get(i));
}
newRow.put(TYPE, mergedType.toString());
}
5.2 Bearer/NetworkType位掩码合并
// TelephonyProvider.java: 2985-3009 行
// 位掩码按位或操作合并
// 例如:
// oldBearer = 0x0001 (GSM)
// newBearer = 0x0002 (CDMA)
// mergedBearer = 0x0003 (GSM | CDMA)
if (oldBearer != newBearer) {
if (oldBearer == 0 || newBearer == 0) {
newRow.put(BEARER_BITMASK, 0); // 0表示所有网络
} else {
newRow.put(BEARER_BITMASK, (oldBearer | newBearer)); // 按位或
}
}
第六阶段:OTA更新机制
6.1 系统更新安装
OTA更新可以推送新的APN配置文件:
// ApnDbInstallReceiver.java
public class ApnDbInstallReceiver extends ConfigUpdateInstallReceiver {
public ApnDbInstallReceiver() {
super("/data/misc/apns/", // 安装目录
"apns-conf.xml", // 文件名
"metadata/", // 元数据目录
"version"); // 版本文件
}
@Override
protected void postInstall(Context context, Intent intent) {
// 安装后触发数据库更新
ContentResolver resolver = context.getContentResolver();
resolver.delete(UPDATE_APN_DB, null, null); // 触发更新
}
}
第七阶段:应用查询APN
7.1 通过ContentProvider查询
应用程序通过标准ContentProvider查询APN:
// 示例代码
Cursor cursor = resolver.query(
Telephony.Carriers.CONTENT_URI,
null,
Telephony.Carriers.NUMERIC + "=?",
new String[]{"31004"}, // MCC+MNC
null
);
// 获取特定MNC的所有APN
while (cursor.moveToNext()) {
String apn = cursor.getString(cursor.getColumnIndex(Telephony.Carriers.APN));
String type = cursor.getString(cursor.getColumnIndex(Telephony.Carriers.TYPE));
String mmsc = cursor.getString(cursor.getColumnIndex(Telephony.Carriers.MMSC));
}
7.2 优先级选择
// ContentProvider支持查询首选APN
// 表中有preferred字段,值为1的APN被视为首选
数据库存储结构
APN数据存储在carriers表中:
| 字段名 | 类型 | 说明 |
|---|---|---|
| _id | INTEGER PRIMARY KEY | 行ID |
| numeric | TEXT | MCC+MNC |
| mcc | TEXT | Mobile Country Code |
| mnc | TEXT | Mobile Network Code |
| apn | TEXT | 接入点名称 |
| name | TEXT | 运营商名称 |
| proxy | TEXT | 代理地址 |
| port | TEXT | 代理端口 |
| mmsproxy | TEXT | MMS代理 |
| mmsport | TEXT | MMS端口 |
| mmsc | TEXT | MMS中心URL |
| user | TEXT | 用户名 |
| password | TEXT | 密码 |
| server | TEXT | 服务器 |
| type | TEXT | APN类型 |
| protocol | TEXT | 协议 |
| roaming_protocol | TEXT | 漫游协议 |
| authtype | INTEGER | 认证类型 (0-PAP, 1-CHAP, 3-PAP/CHAP) |
| bearer_bitmask | INTEGER | 网络技术位掩码 |
| network_type_bitmask | INTEGER | 网络类型位掩码 |
| edited | INTEGER | 编辑状态 (0-未编辑, 1-用户编辑等) |
| user_editable | BOOLEAN | 用户是否可编辑 |
| user_visible | BOOLEAN | 用户是否可见 |
| carrier_enabled | BOOLEAN | 是否启用 |
| modem_cognitive | BOOLEAN | Modem持久化 |
APN类型详解
default - 默认数据连接
mms - 彩信(MMS)
supl - A-GPS (Assisted GPS)
hipri - 高优先级数据连接
fota - Firmware Over-The-Air更新
ims - IP Multimedia Subsystem
ia - Initial Attach (初始附着)
dun - Dial-Up Networking
cbs - Carrier Branded Services
完整的APN加载时序
启动时序:
│
├─ [1] TelephonyProvider.onCreate()
│ └─→ DatabaseHelper.getReadableDatabase()
│
├─ [2] 检查校验和
│ └─→ apnDbUpdateNeeded()?
│
├─ [3] 如果需要更新,执行initDatabase()
│ │
│ ├─ 加载系统内置APN
│ │ └─→ Resources.getXml(R.xml.apns)
│ │
│ ├─ 加载外部APN配置
│ │ └─→ getApnConfFile() (优先选择最新的)
│ │ ├─ /data/misc/apns/apns-conf.xml (OTA)
│ │ ├─ /product/etc/apns-conf.xml
│ │ ├─ /oem/telephony/apns-conf.xml
│ │ └─ /system/etc/apns-conf.xml
│ │
│ ├─ 事务化插入数据库
│ │ └─→ loadApns(db, parser)
│ │ ├─ getRow() 解析每条APN
│ │ └─ insertAddingDefaults() 插入或合并
│ │
│ └─ 清理已删除的APN
│ └─→ 删除USER_DELETED/CARRIER_DELETED
│
├─ [4] 更新校验和
│ └─→ setApnConfChecksum()
│
└─ [5] 应用程序可以查询APN
└─→ ContentProvider查询
关键设计点
- 多源支持 - 支持系统、OEM、Product、OTA等多个来源
- 版本校验 - 内置版本必须与外部版本一致
- 智能合并 - 新旧APN冲突时智能合并而不是覆盖
- 编辑跟踪 - 区分用户编辑、运营商编辑和系统APN
- 校验和优化 - 避免无必要的数据库重新加载
- 事务保证 - 确保APN加载的原子性
- 向后兼容 - 数据库升级时保留用户和运营商编辑
这就是Android系统的完整APN加载机制!