【通信篇4】APN模块总结

3,177 阅读15分钟

1 APN 简介

APN(Access Point Name)是通过手机上网必须配置的一个参数,用来决定手机通过哪种接入方式来访问网络。只要我们的手机插上 SIM 卡之后就可以在手机的设置中查看当前 SIM 卡内置的默认 APN 参数,一般的安卓智能机都可以在 SIM 卡设置中找到“接入点名称(APN)”并可以查看和新增 APN。

下面我们看看 APN 到底是怎么使用的,在启动 Android 手机或者启动 Android 虚拟设备后,所有的 APN 配置信息都会保存在telephony.db的 SQLite 数据库表名为 carriers 的表中。我们可以将此数据库文件 pull 到本地,然后可以查看 carriers 表的结构和其中的 APN 配置信息数据,命令如下:

adb pull /data/user_de/0/com.android.providers.telephony
如上可以将mmssms.dbtelephony.db等数据都pull出来。

在Android系统中APN配置文件的路径:
1)vendor\qcom\proprietary\qrdplus\Extension\apps\etc\apns-config.xml
2)framework\base\core\res\xml\apn.xml

开机后,启动 phone 进程时,会加载运行在 phone 进程中的 TelephonyProvider,TelephonyProvider负责解析apns-conf.xml文件,将其中定义的APN参数写入到数据库中。

1.1 APN 配置关键字段

字段名称描述
nameAPN配置名称,如CMNET
numeric运营商编号,如46000
mcc移动国家码,如460
mnc移动网络码,如00
apnAPN接入点,比如中国移动有两个接入点:cmwap和cmnet
user用户名
server服务器地址
password密码
proxy代理服务器地址,如10.0.0.172
port端口号,如80
mmsproxy彩信代理服务器地址,如10.0.0.172
mmsport彩信代理服务器端口号,如80
mmsc彩信接入服务器地址,如mmsc.monternet.com
typeAPN接入类型,如default,net,supl,xcap,不同类型用","分隔
current
protocol连接该APN所用的协议,如IPV4IPV6
roaming_protocol漫游时连接该APN所用的协议,如IPV4IPV6
carrier_enabled用于标识APN是否可用
bearer无线接入,如LTE和eHRPD
bearer_bitmask无线接入技术位掩码,用于标明当前APN可以包含的RAT
network_type_bitmask
mvno_type移动虚拟网络运营商(Mobile virtual network operator)的类型,可用的数据有spn,IMSI,GID(Group Identifier Level 1)
mvno_match_dataMVNO_TYPE数据,这个值是和MVNO_TYPE对应的。例如:SPN:A MOBILE,BEN NL,IMSI:302720x94,2060188 GID:4E,33
sub_id用于表明这个APN属于哪个subscription,此值从siminfo表获取
profile_idProfile id,profile是modem侧存储信息的方式,这个值将APN和modem侧的profile联系起来
modem_cognitive用于表明这个APN是否会在modem侧设置
max_connsAPN支持的最大连接数量
wait_time使用该APN进行数据连接时,如果失败,retry要等待的时间
max_conns_time限制APN最大连接的时间
mtu使用该APN建立的连接,可以传输的最大单元
edited表明该APN是否被用户或运营商添加、编译或删除的状态
user_visibleAPN是否对用户可见
user_editable用户是否可以编辑APN
owned_byAPN的拥有者,0或者1
apn_set_idAPN集合id,如果用户或者框架选择了一个apn作为首选APN,那么所有与选中apn相同集合id的APN拥有更高的优先级
persistent
read_only是否只读
ppp_number
sourcetype
csdnum
ipversion

1.2 Android 支持的 APN 类型

类型描述
default默认数据连接、即浏览器、Email等手机上网数据连接
mms发送和接收彩信使用的数据连接
supl支持AGPS的数据连接
dun(dial-up-network)拨号连接
hipri扩展
ims

1.3 APN 配置信息

APN 配置在apns-conf.xml中,carriers表中的数据和此文件中的数据内容一致,在加载 TelephonyProvider 的时候,会调用其 initDatabase 方法,将apns-conf.xml配置文件内容加载到carriers表中。

1.4 APN 分工

image.png

2 APN 设置

2.1 重置 APN

image.png

image.png

  1. 在 ApnSettings 界面点击重置后,通过 Uri: “content://telephony/carriers/restore"进行 delete 操作,删除完成后会重新 fillList。

  2. 进行 delete 操作时,TelephonyProvider 通过 URL_RESTOREAPN 进行删除操作,会删除carriers表,同时删除首选 APN,获取 preferred-full-apn 的 SP,如果 SP 包含version1,表示 APN 已经存储起来了,删除该 subId 对应的 version1 字段以及 APN 唯一字段与 subId 组合在一起的字段,删除完成,重新初始化加载carrier表

  3. 在 ApnSettings 界面查询到 APN 后,会通过 Uri:"content://telephony/carriers/preferapn" 去设置首选 APN

初始化完成后,DcTracker 监听到数据库变化执行 onApnChanged,然后会设置偏好 APN,此时会将 oldApnSettings 设置成首选 APN,同时 ApnSettings 查询到 APN list 检测到如果没有偏好 APN,会将第一个 APN 设置成偏好 APN。APN 如果配置了 bearer_bitmask,且 bearer_bitmask 不为 0,如果插入的卡网络类型识别的是 unknown,查询出来的 APN 则会被过滤掉不显示。

2.2 切换 APN

image.png

image.png

  1. 在 ApnSettings 界面点击 onPreferenceChange,执行 setSelectedApnKey,设置首选 APN 通过 URI:content://telephony/carriers/preferapn 和 APN_ID 为选中的 id 进行 update

  2. TelephonyProvider:获取 subId,检查 values 中是否包含 apn_id,如果包含apn_id,获取 apn_id 带过来的 id 值,将对应的 apn_id 保存为该 subId 的首选 APN

  3. DcTracker:监听到数据库变化,ApnChangeObserver 的 onChange 函数将被调用,触发 onApnChange 函数,onApnChange 的时候,底层会清除掉所有的连接。

2.3 新建 APN

image.png

  1. ApnEditor:布局初始化,获取相关控件,获取 subId 等 intent 传过来的参数,插入一条仅有 id 的信息,并根据插入信息返回 uri,根据 uri 查询相应 id 的数据。

  2. 根据查询到的数据 fillUi 进行显示,如果是新建的,mvnoType 显示有差异,mvnoMatchData显示有差异

  3. 移动定制版本,APN 协议和 APN 漫游协议默认为 IpV4V6、设置 SIM 卡相关的监听、 APN 变化的 TextWatcher

  4. 在 onResume 中会注册 phone 状态、插拔卡等的监听

  5. 点击保存:内置的 APN 则弹框提示是否保存,非内置的 APN ,直接验证并保存,如果APN 相关有用信息为空,则 toast 提示,不保存返回,根据各项字段,通过 uri 更新数据库中数据。

2.4 编辑 APN

image.png

  1. ApnEditor:布局初始化,获取相关控件,获取 subId 等 intent 传过来的参数,获取传递过来的 uri,uri 以 id 结尾,如果 uri 为空或不满足要求则返回,关闭界面

  2. 根据 uri 查询相应 id 的数据,根据查询到的数据 fillUi 进行显示,设置 SIM 卡相关的监听、APN 变化的 TextWatcher

  3. 在 onResume 中会注册 phone 状态、插拔卡等的监听,如果配置了 ReadOnly 字段,则不可保存、不可编辑 preferene 项

  4. 点击保存:name、APN、mcc、mnc 四项不能为空,如果为空则返回不让保存,内置的 APN 则弹框提示是否保存,非内置的 APN,直接验证并保存,如果 APN 相关有用信息为空,则 toast 提示,不保存返回,根据各项字段,通过 uri 更新数据库中数据

  5. 修改 mcc,点击保存,此时发生冲突时,更新旧的那条数据,然后删除掉要更新的这条数据,此时要更新这条 APN 就从数据库中删除了,此时不会触发 onApnChange,不会导致 DcTracker 重新触发连接的逻辑。重启手机后,此时通过 numeric 查询 APN 是无法查到的,因为数据库中的那条 APN 的 mnc 和 numeric 已经相对应的被修改了

3 TelephonyProvider

3.1 APN 升级

image.png APN 升级在 TelephonyProvider 中实现,先获取首选 APN 保存,然后根据条件删除数据库。重新根据 xml 插入 APN,根据名称、APN、numeric、bearer 四个属性恢复首选 APN,若其中有属性变更,则不恢复。

4 APN 流程

4.1 卡加载后,监听 APN 数据库变化

public DcTracker(Phone phone) {
    .......
    //每个Phone对象有自己DcTracker
    //每个DcTracker加载各自卡可用的APN
    mPhone = phone;
    .......
    //1、监听卡载入
    mUiccController = UiccController.getInstance();
    mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
    .......
    //2、监听卡信息变化
    mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
    mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
    .......
    //监听APN数据库变化
    mApnObserver = new ApnChangeObserver();
    phone.getContext().getContentResolver().registerContentObserver(
            Telephony.Carriers.CONTENT_URI, true, mApnObserver);
    .............
    //初始化不同APN类型对应的网络能力,后文介绍
    initApnContexts();
    .............
    // Add Emergency APN to APN setting list by default to support EPDN in sim absent cases
    initEmergencyApnSetting();
    addEmergencyApnSetting();
    ...............
}

插卡或卡发生变化后,就要创建当前卡可用的 APN,同时设置初始时使用的 APN

private void onRecordsLoadedOrSubIdChanged() {
    ..............
    //1、创建当前卡可用的APN
    createAllApnList();

    //2、设置初始使用的APN
    setInitialAttachApn();

    if (mPhone.mCi.getRadioState().isOn()) {
        if (DBG) log("onRecordsLoadedOrSubIdChanged: notifying data availability");
        notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
    }

    //卡变化也会触发拨号流程;不过若此时数据开关未开,那么拨号是不会成功的
    setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);
}

创建卡对应的 APN 的过程:

private void createAllApnList() {
    //表示mvno是否匹配
    //mvno也是APN的一种属性,代表该APN适用于虚拟运营商,目前用的比较少
    mMvnoMatched = false;

    //用于保存结果
    mAllApnSettings = new ArrayList<ApnSetting>();

    //得到当前卡的信息
    IccRecords r = mIccRecords.get();
    //得到卡对应的MCC/MNC
    String operator = (r != null) ? r.getOperatorNumeric() : "";
    if (operator != null) {
        //构造SQL语句
        String selection = "numeric = '" + operator + "'";
        String orderBy = "_id";
        ...............
        //查询MCC/MNC对应的APN
        Cursor cursor = mPhone.getContext().getContentResolver().query(
                Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy);

        if (cursor != null) {
            if (cursor.getCount() > 0) {
                //1、利用数据创建APN
                mAllApnSettings = createApnList(cursor);
            }
            cursor.close();
        }
    }

    //2、添加emergencyApnSettings
    addEmergencyApnSetting();

    //3、去除重复的APN
    dedupeApnSettings();

    if (mAllApnSettings.isEmpty()) {
        mPreferredApn = null;
    } else {
        //4、得到用户偏爱的APN (用户在UI界面主动选择的)
        mPreferredApn = getPreferredApn();
        if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator) {
            mPreferredApn = null;
            //用户偏爱的与当前卡不匹配,删除数据库中对应信息
            setPreferredApn(-1);
        }
    }

    //5、在需要的情况下,构造APN文件发送给modem
    setDataProfilesAsNeeded();
}

5 APN 举例

1 以中国移动举例:其中carrier、apn、mcc、mnc几个字段是一个完整的 APN 一定要有的,不同的卡 mnc 会存在不同的情况如,00、02,设置错误的情况下会无法上网

<apn carrier="China Mobile"
  apn=""
  mcc="460"
  mnc="00"
  type="ia"
  protocol="IPV4V6"
  roaming_protocol="IPV4V6"
/>
<apn carrier="CMCC DM"
  apn=""
  mcc="460"
  mnc="00"
  type="fota"
/>
<apn carrier="APN_NAME_CMNET"
  apn="cmnet"
  mcc="460"
  mnc="00"
  type="default,net,supl"
  preferred="true"
  protocol="IPV4V6"
  roaming_protocol="IPV4V6"
/>
<apn carrier="APN_NAME_CMMMS"
  apn="cmwap"
  mcc="460"
  mnc="00"
  proxy="10.0.0.172"
  port="80"
  mmsproxy="10.0.0.172"
  mmsport="80"
  mmsc="http://mmsc.monternet.com"
  type="mms"
  protocol="IPV4V6"
  roaming_protocol="IPV4V6"
/>
<apn carrier="APN_NAME_CMWAP"
  apn="cmwap"
  mcc="460"
  mnc="00"
  proxy="10.0.0.172"
  port="80"
  type="supl"
  protocol="IPV4V6"
  roaming_protocol="IPV4V6"
/>
<apn carrier="China Mobile (IMS)"
  mcc="460"
  mnc="00"
  apn="ims"
  type="ims"
  protocol="IPV4V6"
  roaming_protocol="IPV4V6"
  profile_id="2"
  modem_cognitive="true"
  max_conns="1023"
  max_conns_time="300"
/>

这里需要强调一下 type 和 authtype,type 字段可以有多个属性值,依次用逗号隔开, authtype 在自己添加 APN 时可能给定的值是字符串,我们需要转换为相应的值。具体关系如下:

属性值合入值
None0
不写(默认值)-1
PAP1
CHAP2
PAP OR CHAP3

2 上网分为 wap 和 net 两种方式,使用 net 手机就会直接连入互联网,而使用 wap 则会中间多了一个代理网关,移动联通均是 10.0.0.172,端口 80。

3 彩信 APN
彩信 APN 中 mmsproxy 和 mmsport 两个字段在发彩信的 APN 中是必须的

6 Android 支持的 APN 类型

Android 中支持的 APN 类型(”default, mms, supl, dun, hipri, fota, ims…….”),其功能如下所示:

类型描述
default默认数据连接,即浏览器、Email等普通连接(internet、wap、web)
mms接收和发送彩信使用的数据连接
supl支持APGS的数据连接(gprs上网)
dun拨号连接(wifi等上网类型,tethering)
hipri扩展

此表中的数据优先级是由低到高的,即 default 数据连接的优先级最低,而 hipri 数据连接的优先级最高。比如在手机上网聊天时,将建立 default 数据连接;当手机收到一条彩信,因为彩信的数据连接是 mms,这时会断开 default 数据连接而创建 mms 数据连接,从而能快速接收到此彩信,因为 mms 比 default 的数据连接优先级高。因此,在发送和接收彩信的同时不能上网。

APN分类

1 default
默认网络连接,当激活时所有数据传输都使用该连接,不能与其他网络连接同时使用
适用场合: 绝大部分正常上网时可以使用

2 mms
彩信专用连接,此连接与default类似,用于与载体的多媒体信息服务器对话的应用程序,此连接能与default连接同时使用
适用场合:使用彩信服务时,必须有mms类型的接入点,不必选中,应用程序会自动使用此接入点

3 supl
是 SecureUser Plane Location “安全用户面定位”的简写,此连接与 default 类似,用于帮助定位设备与载体的安全用户面定位服务器对话的应用程序,此连接能与 default 连接同时使用

4 dun
Dial UpNetworking 拨号网络的简称,此连接与 default 连接类似,用于执行一个拨号网络网桥,使载体能知道拨号网络流量的应用程序,此连接能与 default 连接同时使用
适用场合:当我们使用自己的手机给别人做热点时使用,不管是 USB 热点,wifi 热点或则 bluetooth 热点。将他与 default 区别开来的主要目的一般是方面计费,国外很多运营商手机自己上网和做热点计费不同的。目前在国内三大运营商都没有区分,所以也就没有 dun 这个 APN

5 hipri
高优先级网络,与 default 类似,但路由设置不同。使用较少。

6、ims
当 ims 发起激活请求时会使用这个 APN 连建立 ims 的专用承载.

7 FOTA
手机FOTA升级的时候使用

8 IA
IA 的 APN 专用于 LTE attach 使用,在手机检测到 SIM 卡后,便会加载这个 attach apn. 不过很多运营商并没有严格规定 attach apn,所以常常复用 default 类型的 APN。 在 attach apn加载的时候它有一个优先级顺序,如下:

IaApn  > PreferredApn > DefaultApn > FirstApn

IaApn : 类型为 ia 的 APN,优先级最高。
PreferredApn :选中的 APN。比如在手机 settin g里面设置的那个 APN
DefaultApn :从 apnlist 里面查询到的第一个类型为“default”的 APN
FirstApn :apnlist 中的第一个 APN

APN 加载和过滤

  1. 在每次开机的时候系统回自动检查telephony.db是否存在,如果不存在则会创建数据库telephony.db,并利用apns-conf.xml中的内容生成表carriers, 以后所有对 APN 的操作都会是直接针对表carriers,包括查询,创建,修改,删除等。
  2. 当插入一张卡后系统会根据卡的相关信息来匹配相应的 APN,在 APN list 中主要涉及匹配的项有:mcc,mnc,mvno_type, mvno_match_data
  3. mvno_type 值决定 mvno_match_data 的值,android 原生代码里 mvno_type 会有4个值,他们分别是 “spn”, “imsi”, “gid”, “iccid”。所以,在 APN 读取的时候,会先根据 SIM 卡的 mcc,mnc 读取出相应的 APN list,接着会判断 APN list 中的每一个 APN 的 mvno_type 的值,如果不为空,则会根据mvno_type 和 mvno_match_data 再一次对 APN list 进行过滤,一般情况下,mvno_type, mvno_match_data 为空。

7 常见的APN问题

7.1 重置APN,显示为默认的APN接入点后又变为手动更改的接入点

D ApnSettings: --restoreDefaultApn--  ——开始重置
D TelephonyProvider: restoreDefaultAPN: where: owned_by!=0

D TelephonyProvider: deletePreferredApn: for subId 1
D TelephonyProvider: deletePreferredApn: apn is stored. Deleting it now for subId 1
D TelephonyProvider: dbh.initDatabase:+ db=SQLiteDatabase: /data/user_de/0/com.android.providers.telephony/databases/telephony.db ——重新创建
D TelephonyProvider: dbh.initDatabase:- db=SQLiteDatabase: /data/user_de/0/com.android.providers.telephony/databases/telephony.db ——插入数据库完成
D TelephonyProvider: setPreferredApn: _id 2965 subId 1 ————设置偏好APN
 DCT     : [ApnContext:default] getApnSetting: apnSetting=[ApnSettingV5] CUWAP, 2966, 46001, 3gwap, 10.0.0.172, http://mmsc.myuni.com.cn, 10.0.0.172, 80, 80, -1, default | mms, IPV4V6, IPV4V6, true, 0, 0, 0, false, 0, 0, 0, 0, , , false, 0, 0
 DCT     : [ApnContext:default] getApnSetting: apnSetting=[ApnSettingV5] CUWAP, 2966, 46001, 3gwap, 10.0.0.172, http://mmsc.myuni.com.cn, 10.0.0.172, 80, 80, -1, default | mms, IPV4V6, IPV4V6, true, 0, 0, 0, false, 0, 0, 0, 0, , , false, 0, 0
 QtiDCT  : [0]buildWaitingApns: reset preferred APN to [ApnSettingV5] CUWAP, 2966, 46001, 3gwap, 10.0.0.172, http://mmsc.myuni.com.cn, 10.0.0.172, 80, 80, -1, default | mms, IPV4V6, IPV4V6, true, 0, 0, 0, false, 0, 0, 0, 0, , , false, 0, 0 ——设置偏好APN失败,重置到2966了
从log可知,上层设置2965 APN时未设置成功,底层重新设置了2966
//上层设置偏好APN 2965
Line 58117: 06-21 14:43:07.940  2883  3252 D TelephonyProvider: setPreferredApn: _id 2965 subId 1 
//framework设置偏好APN 2966
Line 4405: 06-21 14:43:07.939  2883  2883 D QtiDCT  : [0]buildWaitingApns: reset preferred APN to [ApnSettingV5] CUWAP, 2966, 46001, 3gwap, 10.0.0.172, http://mmsc.myuni.com.cn, 10.0.0.172, 80, 80, -1, default | mms, IPV4V6, IPV4V6, true, 0, 0, 0, false, 0, 0, 0, 0, , , false, 0, 0 ——设置偏好APN失败,重置到2966了
//framerok调用删除
Line 4406: 06-21 14:43:07.939  2883  2883 D QtiDCT  : [0]setPreferredApn: delete 
//OS写入完成,数据库执行完打印的log
Line 58119: 06-21 14:43:07.943  5337  5337 D _ApnSettings: set key to  2965
//framework开始写入偏好APN
Line 4407: 06-21 14:43:07.967  2883  2883 D QtiDCT  : [0]setPreferredApn: insert
//响应framework的操作,写入偏好APN 2966
Line 58161: 06-21 14:43:07.967  2883  2883 D TelephonyProvider: delete:match=12
Line 58162: 06-21 14:43:07.967  2883  2883 D TelephonyProvider: subIdString = 1 subId = 1
Line 58163: 06-21 14:43:07.967  2883  2883 D TelephonyProvider: deletePreferredApn: for subId 1
Line 58164: 06-21 14:43:07.967  2883  2883 D TelephonyProvider: deletePreferredApn: apn is stored. Deleting it now for subId 1
Line 58170: 06-21 14:43:07.972  2883  2883 D TelephonyProvider: subIdString = 1 subId = 1 ——此处log只有对应URL_PREFERAPN_NO_UPDATE_USING_SUBID 或者URL_PREFERAPN_USING_SUBID URI才会打印出来
Line 58171: 06-21 14:43:07.973  2883  2883 D TelephonyProvider: setPreferredApn: _id 2966 subId 1
//从上面流程可以看出,最后执行的是响应 framework的写入2966的操作

【分析】
如上分析,在 buildWaitingAPN() 中增加的 reset preferred APN 的逻辑和上层冲突,导致 preferred APN 值不对,这段已经不需要。上层在 restore APN 时会设置 default preferred APN
【方案】
删除reset prefered APN逻辑

7.2 升级后,SIM2的APN需要重新选择
【分析】
(1)从 log 来看,卡 2 没有选择默认的 APN,故会弹框通知设置 APN
(2)对比升级前后的 APN 可知,升级后由于名称改变了,相当于 APN 变更了,故需要重新设置 APN

7.3 手动配置APN
手动配置APN需要配置的元素,例如:
(1)name:ims
(2)APN:ims
(3)type:ims
(4)APN protocol & APN roaming protocol:IPV4V6