【通信篇4】APN模块总结
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.db和telephony.db等数据都pull出来。
在Android系统中APN配置文件的路径:
1)vendor\qcom\proprietary\qrdplus\Extension\apps\etc\apns-config.xml
2)framework\base\core\res\xml\apn.xm
开机后,启动phone进程时,会加载运行在phone进程中的TelephonyProvider,TelephonyProvider负责解析apns-conf.xml文件,将其中定义的APN参数写入到数据库中。
1.1 APN配置关键字段
字段名称 | 描述 |
---|---|
name | APN配置名称,如CMNET |
numeric | 运营商编号,如46000 |
mcc | 移动国家码,如460 |
mnc | 移动网络码,如00 |
apn | APN接入点,比如中国移动有两个接入点:cmwap和cmnet |
user | 用户名 |
server | 服务器地址 |
password | 密码 |
proxy | 代理服务器地址,如10.0.0.172 |
port | 端口号,如80 |
mmsproxy | 彩信代理服务器地址,如10.0.0.172 |
mmsport | 彩信代理服务器端口号,如80 |
mmsc | 彩信接入服务器地址,如http://mmsc.monternet.com |
type | APN接入类型,如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_data | MVNO_TYPE数据,这个值是和MVNO_TYPE对应的。例如:SPN:A MOBILE,BEN NL,IMSI:302720x94,2060188 GID:4E,33 |
sub_id | 用于表明这个APN属于哪个subscription,此值从siminfo表获取 |
profile_id | Profile id,profile是modem侧存储信息的方式,这个值将APN和modem侧的profile联系起来 |
modem_cognitive | 用于表明这个APN是否会在modem侧设置 |
max_conns | APN支持的最大连接数量 |
wait_time | 使用该APN进行数据连接时,如果失败,retry要等待的时间 |
max_conns_time | 限制APN最大连接的时间 |
mtu | 使用该APN建立的连接,可以传输的最大单元 |
edited | 表明该APN是否被用户或运营商添加、编译或删除的状态 |
user_visible | APN是否对用户可见 |
user_editable | 用户是否可以编辑APN |
owned_by | APN的拥有者,0或者1 |
apn_set_id | APN集合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表中。
2、APN设置
2.1 重置APN
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
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
1、ApnEditor:布局初始化,获取相关控件,获取subId等intent传过来的参数,插入一条仅有id的信息,并根据插入信息返回uri,根据uri查询相应id的数据。
2、根据查询到的数据fillUi进行显示,如果是新建的,mvnoType显示有差异,mvnoMatchData显示有差异
3、移动定制版本,APN协议和APN漫游协议默认为IpV4V6、设置SIM卡相关的监听、APN变化的TextWatcher
4、在onResume中会注册phone状态、插拔卡等的监听
5、(1)点击保存:内置的APN则弹框提示是否保存,非内置的APN,直接验证并保存,如果APN相关有用信息为空,则toast提示,不保存返回,根据各项字段,通过uri更新数据库中数据。
2.4 编辑APN
1、ApnEditor:布局初始化,获取相关控件,获取subId等intent传过来的参数,获取传递过来的uri,uri以id结尾,如果uri为空或不满足要求则返回,关闭界面。
2、根据uri查询相应id的数据,根据查询到的数据fillUi进行显示,设置SIM卡相关的监听、APN变化的TextWatcher
3、在onResume中会注册phone状态、插拔卡等的监听,如果配置了ReadOnly字段,则不可保存、不可编辑preferene项
4、(1)点击保存:name、APN、mcc、mnc 四项不能为空,如果为空则返回不让保存,内置的APN则弹框提示是否保存,非内置的APN,直接验证并保存,如果APN相关有用信息为空,则toast提示,不保存返回,根据各项字段,通过uri更新数据库中数据。
(2)修改mcc,点击保存,此时发生冲突时,更新旧的那条数据,然后删除掉要更新的这条数据,此时要更新这条APN就从数据库中删除了,此时不会触发onApnChange,不会导致DcTracker重新触发连接的逻辑。重启手机后,此时通过numeric查询APN是无法查到的,因为数据库中的那条APN的mnc和numeric已经相对应的被修改了。
3 TelephonyProvider
3.1 APN升级
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();
...............
}
4)插卡或卡发生变化后,就要创建当前卡可用的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时可能给定的值是字符串,我们需要转换为相应的值。具体关系如下:
属性值 | 合入值 |
---|---|
None | 0 |
不写(默认值) | -1 |
PAP | 1 |
CHAP | 2 |
PAP OR CHAP | 3 |
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。 在attachapn 加载的时候它有一个优先级顺序,如下:
IaApn > PreferredApn > DefaultApn>FirstApn
IaApn : 类型为ia的apn,优先级最高。
PreferredApn :选中的apn。比如在手机setting里面设置的那个apn
DefaultApn :从apnlist里面查询到的第一个类型为“default”的apn
FirstApn :apnlist中的第一个apn。
APN加载和过滤
在每次开机的时候系统回自动检查telephony.db是否存在,如果不存在则会创建数据库telephony.db,并利用apns-conf.xml中的内容生成表carriers,以后所有对apn的操作都会是直接针对表carriers,包括查询,创建,修改,删除等。
当插入一张卡后系统会根据卡的相关信息来匹配相应的apn,在apn list中主要涉及匹配的项有:mcc,mnc,mvno_type, mvno_match_data。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