Android系统APN(Access Point Name)加载流程详解

6 阅读8分钟

基于代码分析,我将详细讲解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;
}

提取的关键字段:

字段名含义示例
numericMCC+MNC31004 (Verizon)
apn接入点名称vzwinternet
typeAPN类型default,hipri,mms
protocol协议IP/IPV6/IPV4V6
proxy代理地址0.0.0.0
mmscMMS中心地址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表中:

字段名类型说明
_idINTEGER PRIMARY KEY行ID
numericTEXTMCC+MNC
mccTEXTMobile Country Code
mncTEXTMobile Network Code
apnTEXT接入点名称
nameTEXT运营商名称
proxyTEXT代理地址
portTEXT代理端口
mmsproxyTEXTMMS代理
mmsportTEXTMMS端口
mmscTEXTMMS中心URL
userTEXT用户名
passwordTEXT密码
serverTEXT服务器
typeTEXTAPN类型
protocolTEXT协议
roaming_protocolTEXT漫游协议
authtypeINTEGER认证类型 (0-PAP, 1-CHAP, 3-PAP/CHAP)
bearer_bitmaskINTEGER网络技术位掩码
network_type_bitmaskINTEGER网络类型位掩码
editedINTEGER编辑状态 (0-未编辑, 1-用户编辑等)
user_editableBOOLEAN用户是否可编辑
user_visibleBOOLEAN用户是否可见
carrier_enabledBOOLEAN是否启用
modem_cognitiveBOOLEANModem持久化

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查询

关键设计点

  1. 多源支持 - 支持系统、OEM、Product、OTA等多个来源
  2. 版本校验 - 内置版本必须与外部版本一致
  3. 智能合并 - 新旧APN冲突时智能合并而不是覆盖
  4. 编辑跟踪 - 区分用户编辑、运营商编辑和系统APN
  5. 校验和优化 - 避免无必要的数据库重新加载
  6. 事务保证 - 确保APN加载的原子性
  7. 向后兼容 - 数据库升级时保留用户和运营商编辑

这就是Android系统的完整APN加载机制!