Android N数据业务总结

来源:互联网 发布:nginx sticky 编辑:程序博客网 时间:2024/05/01 10:54

一、前言
本文旨在总结Android N中数据业务在框架侧的流程,
主要包括APN加载、数据卡选择、长连接拨号、短连接拨号等。

目前框架侧的分析,侧重于AOSP相关的源码。
同时,考虑到拨号与去拨号流程涉及的类基本相似,
在理解拨号流程的基础上,可以比较容易地掌握去拨号的流程,
因此本文不单独分析去拨号涉及的流程。

最后,由于整个数据业务涉及到的细节相当多,文字有时难以进行有效地表述,
而且受限于个人的能力,对流程的梳理难免会有遗漏。
因此本文仅作为研究代码的参考和记录信息,所有流程的详细内容还是以代码为准。


图1 大图链接

图1为Android N中数据业务相关的主要类或模块组成的整体架构图。
在介绍不同场景的详细流程前,我们先结合图1大概了解一下数据业务相关的内容。

1.1 Phone进程创建相关的部分
Phone进程创建的工作,将主要由图1中的SystemServer、ZygoteInit、ActivityThread和ActivityManagerService来完成。

简单来讲,SystemServer被创建出来后,将向Zygote进程中的server socket发送消息以创建Phone进程。
Zygote接收信息并处理的函数定义于ZygoteInit中。

ZygoteInit中的runSelectLoop函数收到消息后,最终将通过fork分裂出Phone进程,
并利用反射调用Phone进程对应ActivityThread的main函数。

ActivityThread创建出Android运行环境后,向AMS发送消息进行注册。
随后,AMS回调ActivityThread对应的ApplicationThread的接口,后者发送消息触发ActivityThread创建Phone进程中的组件。

此后,当Phone进程对应的PhoneApp的onCreate函数被调用后,将创建出PhoneGlobals对象。
PhoneGlobals对象调用PhoneFactory的静态方法,创建出数据业务所需的诸多对象。

由于这部分代码不是本文的重点,故不详细分析其流程。
这部分内容,实际上之前的博客中已经做过了分析。

1.2 加载APN相关的部分
数据业务是离不开APN的,图1中与APN相关的类主要为TelephonyProvider、DcTracker及ApnContext。

TelephonyProvider在PhoneApp的onCreate函数被调用之前,就会被Phone进程加载。
它主要负责解析终端中配置的apns-conf.xml文件,生成对应APN信息,创建和更新终端中APN对应的数据库。

DcTracker是由不同卡对应的Phone对象独立创建的,会监听卡是否加载的消息。
当DcTracker收到消息,并判断卡已经加载后,将根据卡信息查询TelephonyProvider创建的数据库,生成卡对应的可用APN。
在这些可用APN中,DcTracker还负责选出并设置initial attach APN。

DcTracker在初始化时,会根据配置信息,生成终端支持的不同网络能力对应的ApnContext。
当终端收到建立网络的请求时,就会激活与该网络请求匹配的ApnContext。
只有ApnContext被激活时,终端才能使用满足该ApnContext条件的Apn进行拨号。

1.3 设置数据卡相关的部分
图1中与设置数据卡相关的类主要有UiccController、ProxyController、SubscriptionController、
IccCardProxy和SubscriptionInfoUpdater等。

Android原生流程中,设置数据卡的过程比较粗糙。
基本上就是三步:1、检测卡插入;2、加载卡对应的信息;3、设置数据卡。
其中框架检测卡、加载卡相关信息的工作,主要由UiccController、IccCardProxy及其衍生类完成;
设置数据卡的工作,将交给SubscriptionInfoUpdater、SubscriptionController等类来完成。

由于底层modem存在主协议栈和副协议栈,且一般情况下由数据卡使用主协议栈。
因此设置完数据卡后,框架还会利用ProxyController对每张卡使用的协议栈进行调整。

需要说明的是:在原生的代码流程中,框架仅支持插入单卡时,主动设置数据卡;
当插入双卡时,将由用户在界面上主动进行选择。

Qualcomm和MTK都对原生流程中,卡设置部分进行了扩展。
Qualcomm延续其一贯的扩展模式,即定义继承SubscriptionInfoUpdater和SubscriptionController的类,
通过重载部分函数,实现插入双卡时亦可设置默认数据卡的功能。
MTK也保持了其通常的风格,即定义一些新的类,例如DataSubSelector,来完成额外的双卡设置功能。

关于Qualcomm和MTK双卡设置的逻辑,由于保密条款的限制,本文不做详细描述,
其本质其实就是利用数据库中的历史信息、以及一些特殊的需求,来指导具体的选卡流程。

1.4 框架层数据拨号的部分
框架层数据拨号的流程比庞杂,包括长连接拨号、短连接拨号、数据卡切换导致的拨号、
使用的APN发生变化引起的拨号等,因此涉及到了许多的类。

同时,由于数据网络本身被纳入到ConnectivityService主导的网络管理体系,
因此还需要考虑网络共存等问题,例如WiFi的断开和连接,也会引起数据的拨号和断开。

此外,若考虑到框架与底层netd通信,创建物理网络、配置路由等操作,整个数据拨号相关的流程就更加复杂了。

因此在本篇文档中,我们先专注于描述主要的流程,其余细节有机会再做补充。

在后文中我们打算分析以下几个部分:
1、由界面开关发起的长连接拨号流程;
2、切换数据卡引起的长连接流程;
3、短连接的建立流程。

如图1所示,这部分内容涉及到的类包括:DcTracker、DataConnection、ConnectivityService、
PhoneSwitcher、NetworkManagementService等。

1.5 底层拨号流程概述
最后以Qualcomm的底层实现为例,在不涉及源码的前提下,大致介绍一下RIL层以下拨号涉及的流程。

对于框架层而言,RIL作为无线接口层的抽象,负责传递AP与BP之间的消息。
RIL层以下由具体的厂商提供对应的实现。
如图1所示,对于Qualcomm而言,其底层的通信主要由QCRIL daemon、Netmgr Daemon和QMUX Daemon完成。

对于数据业务而言,QCRIL daemon仅作为一个消息传递通道,
将框架侧发送的信息递交给QMUX Daemon,后者将通过SMD将拨号命令发送modem。

modem与网络端通信,获得网络端分配的IP地址、DNS地址、MTU等信息后,
同样通过SMD将这些信息递交给QMUX Daemon。

QMUX Daemon收到信息后,将这些信息递交给Netmgr Daemon。

Netmgr Daemon负责与Linux kernel通信,打开数据接口,同时利用网络信息配置该接口。
应用层发送和接收数据使用的socket,就是利用这个接口来进行实际的数据传输。
当然这个接口还需要通过驱动与modem连接,才能进行实际的无线数据收发。

一旦接口配置完毕后,Linux kernel就会通知QCRIL daemon。
此时,QCRIL daemon将返回的结果通知给RIL。

根据上面的描述可以看出,Qualcomm底层的通信从架构上来看是比较清晰的。
但由于其QMI通信机制涉及到一层层的封装,同时Netgmr daemon与Linux kernel通信涉及到netlink socket的转发,
使得具体的流程显得比较繁琐。

同样由于保密条款的限制,这部分流程在本文档中就不做深入分析了。

1.6 小结
至此,结合整体架构图,我们对数据业务的基本功能有了一个初步的了解。
当然,由于自己能力有限,可能将数据业务局限在了上述的一些基本流程中,
还有很多比较重要的领域没有涉及到,今后有机会再来逐渐完善本篇博客。

二、加载APN相关的流程
在这一部分,我们首先看看终端加载APN相关的主要流程。

2.1 TelephonyProvider加载APN
正如前文所述,终端加载APN的操作,是由TelephonyProvider发起的。

TelephonyProvider主要负责将apns-config.xml文件中的APN信息加载到数据库中。
这部分工作涉及的操作主要是:解析XML和执行SQLite命令。

本文档不分析加载APN对应的具体操作涉及的技术细节,
仅关注一下TelephonyProvider加载APN相关流程的主要函数调用逻辑。

2.1.1 首次加载APN的流程

图2 大图链接

终端每次启动时,TelephonyProvider的onCreate函数均会被调用。
1、如图2所示,在TelephonyProvider的onCreate函数中,首先会创建出DataBaseHelper对象。
该对象定义与TelephonyProvider中,是SQLiteOpenHelper的子类。

然后,TelephonyProvider会调用DataBaseHelper的getReadableDataBase函数,
该函数实际上定义于SQLiteOpenHelper中。

2、在getReadableDataBase函数中,SQLiteOpenHelper会判断APN对应的数据库是否已经创建过。

如果数据库没有被创建,那么SQLiteOpenHelper将新建一个数据库;
否则SQLiteOpenHelper只会在数据库的版本发生变化时,
调用其子类TelephonyProvider的onUpgrade函数进行一些更新操作。

因此,终端只有在首次刷机或恢复出厂设置后启动时,
TelephonyProvider才会真正地执行数据库的创建工作,并调用DataBaseHelper的onCreate函数。

3、在DataBaseHelper的onCreate函数中,将会依次调用createSimInfoTable、createCarriersTable和initDataBase函数。
其中:
createSimInfoTable负责利用SQLite语句创建出卡信息对应的数据表;
createCarriersTable函数则负责创建运营商信息对应的数据表(目前的代码流程中,主要使用的是运营商对应的数据表)。

initDataBase函数负责解析apns-conf.xml,并将XML中APN相关的信息加入到运营商对应的数据表中。

从上面的流程可以看出:终端一旦建立起数据库后,执行重启机器等操作时,
在TelephonyProvider的初始化的过程中,不会再进行加载XML中APN的工作。

2.1.2 修改apns-conf.xml后的流程
在遇到问题或新需求时,设备厂商可能会修改etc目录下的apns-conf.xml文件,然后推送更新后的版本。
当厂商修改了apns-conf.xml文件并推送版本时,新版本将对应一个新的build id。

此时,用户更新并重启终端后,TelephonyProvider将会进入另一个流程,重新解析和加载apns-conf.xml文件。

图3 大图链接

1、如图3所示, TelephonyProvider完成正常的初始化流程后,将判断系统的build id是否发生变化。
一旦发现build id发生变化时,TelephonyProvider将调用updateApnDb函数。

2、updateApnDb函数,将调用DataBaseHelper的apnDbUpdateNeeded函数判断是否需要重新加载APN信息。

apnDbUpdateNeeded函数主要是通过比较新旧apns-conf.xml文件的CRC校验值,来判断该文件是否发生了变化。

3、当检测到apns-conf.xml文件发生变化后,updateApnDb函数将主动删除数据库中旧有的APN信息。

然后,重新调用initDatabase函数解析并加载apns-conf.xml中的APN信息。
一旦重新加载完APN信息后,TelephonyProvider会通知APN数据库的观察者。

2.1.3 其它
以上是TelephonyProvider开机加载APN的主要流程。整体上来讲,这部分是比较简单的。

终端在其它的场景下,例如显示可用APN、编辑APN、新建APN、选择APN、复位APN等,
都是通过界面来显示或修改APN数据库中的数据。

这些操作对应的流程,最终都会直接或间接地用到TelephonyProvider提供的接口。
由于这些流程大多比较直观,且和具体的UI界面相关联,此处就不对这些场景的流程做进一步的梳理。

2.2 DcTracker与APN相关的流程
DcTracker主要负责将APN数据库中的信息,加载到框架中供拨号流程使用。
在分析DcTracker中APN相关的流程前,我们先来看看这部分流程涉及的主要类及它们之间的关系。

图4 大图链接

1、如图4所示,ApnSetting是框架用于保存APN信息的类。
当DcTracker从数据库中读取卡对应的APN信息后,就会创建并保存每个APN对应的ApnSetting。

2、DcTracker在初始化时,为终端支持的每一种网络创建对应的ApnContext。
这部分流程体现在DcTracker调用的initApnContexts函数中。

当收到不同类型的数据网络连接请求时,ConnectivityService会将请求发送给TelephonyNetworkFactory。
TelephonyNetworkFactory如果能够处理请求,就会调用DcTracker的接口,
激活对应网络类型的ApnContext,并触发拨号流程。

3、当拨号流程启动后,DcTracker将根据激活的ApnContext的网络类型,
从所有可用的ApnSetting中,筛选出可以使用的APN信息。

然后,DcTracker将按照APN排列的先后顺序,依次使用APN进行拨号。

需要注意的是:只有前一个APN拨号失败时,DcTracker才会尝试使用下一个APN重拨。
每次拨号时,ApnContext都持有当前APN对应的ApnSetting,并记录当前拨号工作的执行状态,例如CONNECTING、FAILED等。

由于DcTracker调用initApnContexts创建ApnContexts的流程过于直观,此处不做赘述。
我们仅看看APN数据库改变或卡状态发生变化时,DcTracker加载和设置ApnSetting的流程。

2.2.1 卡状态变化引起的APN加载

图5 大图链接

1、如图5所示,DcTracker在初始化时,向SubscriptionManager注册了onSubscriptionChangedListener,以监听卡状态变化的消息。

2、当用户进行插卡等会引起卡状态变化的动作后,SubscriptionManager就会通知DcTracker。

3、DcTracker收到通知后,将判断当前卡是否有效,同时与历史信息比较,判断卡的SubId是否发生变化。

4、一旦发现SubId发生变化后,DcTracker就会调用onRecordsLoadedOrSubIdChanged函数,重新从数据库中加载APN信息。

如图5所示,onRecordsLoadedOrSubIdChanged函数中,与APN相关的函数为createAllApnList和setInitialAttachApn。其中:
createAllApnList函数主要根据卡信息,实际上是卡对应运营商的MCC/MCC,查询数据库,检索出所有匹配的数据;
然后,生成并保存对应的ApnSetting。
于是,当用户使用这张卡上网时,框架就会从这张卡对应的ApnSetting中选择可用的APN。

setInitialAttachApn主要是从得到的所有ApnSetting中,选择一个用于初始时注册数据网络的APN,并将该APN下发给modem使用。
Initial Attach APN选择的规则是:
首先选择具有IA type的APN;
其次选择用户在设置界面选择的APN;
再次是选择第一个具有default type的APN;
最后选择第一个从数据库中加载上来的APN。

5、从图5中可以看出,当DcTracker加载完APN后,还调用了setupDataOnConnectableApns函数,尝试进行数据拨号。
不过,终端是否会进行实际的连网动作,取决于数据开关是否打开及ApnContext是否被激活等条件。

2.2.2 数据库状态变化引起的APN加载
DcTracker在初始时同样定义了一个ContentObserver,用于监听APN数据库的变化。
因此,当用户通过界面执行选择、新建或复位APN等会修改APN数据库的操作时,DcTracker都将收到通知。

图6 大图链接

1、如图6所示,当DcTracker收到数据库变化的通知后,将调用onApnChanged函数进行处理。

2、在onApnChanged函数中,同样将调用前文所述的createAllApnList加载数据中中的信息组成ApnSetting,
并调用setInitialAttachApn函数设置Initial Attach APN。

3、与前文不同的是,当检测到APN发生改变时,onApnChanged函数还将根据所有ApnContext的状态,
判断终端当前是否已经建立起了数据连接,或者在试图建立数据连接,即ApnContext是否处于CONNECTED、CONNECTING等状态。

4、当DcTracker发现终端已经建立起数据连接,或者试图建立数据连接时,
就调用cleanUpConnectionsOnUpdatedApns函数,断开已有的数据连接或停止拨号动作。

然后,数据卡对应的DcTracker将再次调用前文所述的setupDataOnConnectableApns函数,重新建立数据连接。

DcTracker执行上述一系列动作的逻辑是:
当用户操作APN数据库时,终端能够拨号的APN可能发生了变化,
因此框架为了保证一致性,最好的策略是断开旧有的数据连接,
然后重新选择APN进行拨号。

当然,这部分流程可能会产生冗余的操作,
即DcTracker最后还是用原来的APN进行了拨号,使得断开并重建数据连接显得没有意义。
例如,用户仅新建了一个APN,但并不去使用它。
不过除去特定的测试外,一般的用户操作APN界面时,
改变终端拨号使用的APN的概率还是比较大的。

2.3 小结
至此,框架加载APN相关的主要流程介绍完毕。

这部分内容从整体来看,其实就做了两件事:
1、将XML文件中的信息,变成数据库中的数据;
2、将数据库中的数据,变成框架可以使用的数据结构。
在这两件事的基础上,当框架检测到文件或数据库等发生变化时,再启动相应的流程来应对这种变化。

三、数据卡选择
在这一部分,我们主要看一下原生的数据卡选择流程。

需要说明的是:原生代码流程只有插入单卡时,才会选择默认数据卡;
插入双卡时,需要用户在卡设置界面上主动选择数据卡。

目前,一般机器中插入双卡时,默认数据卡的选择流程,
是MTK和Qualcomm在Android原生的基础上独立添加的。

由于保密条款的限制,本文不分析MTK和Qualcomm补充的流程。

3.1 加载卡信息的流程
在选择数据卡之前,终端必须先加载卡相关的信息,其基本过程大致如图7所示:

图7 大图链接

1、开机后PhoneFactory创建出了UiccController,该对象将作为RIL的观察者,监听卡状态变化的事件。

2、当PhoneFactory创建出GsmCdmaPhone后,GsmCdmaPhone在其初始化函数中将创建出IccCardProxy对象。
IccCardProxy对象将作为UiccController的观察者,同样监听卡状态的变化。

3、当用户进行插卡操作且底层检卡成功后,modem将主动向RIL发送RIL_UNSOL_SIM_STATUS_CHANGED消息。
此时,RIL将通知它的观察者UiccController。

4、UiccController收到RIL的通知后,将调用getIccCardStatus函数主动从modem获取卡相关的信息。
一旦卡信息获取成功,UiccController将调用其onGetIccCardStatusDone函数。

5、在onGetIccCardStatusDone函数中,UiccController将判断是否创建过卡对应的UiccCard对象。

如果没有创建过,UiccController将根据卡信息创建出对应的UiccCard对象;
否则,仅进行UiccCard的更新操作。

6、当UiccCard对象被创建时,在它的构造函数中将进一步创建出对应的UiccCardApplication和IccRecords。

需要注意的是,IccRecords只是一个抽象类,
UiccCardApplication将根据modem上报的卡类型,创建出实际的子类,
即SIMRecords、RuimRecords等。

当IccRecords被创建后,同样将作为RIL的观察者,监听Icc Refresh消息。

7、UiccController完成上述工作后,将通知其观察者IccCardProxy对象。
此时,卡信息还未完全加载完毕。

IccCardProxy收到UiccController的通知后,注册成为IccRecords的观察者。

8、收到modem主动上报的RIL_UNSOL_SIM_REFRESH消息后,RIL将通知IccRecords对象。

IccRecords对象收到通知后,将主动与modem通信,进一步获取卡相关的信息。

图7中画出的是以SIMRecords为例的流程。
当SIMRecords收到RIL的通知后,将调用handleSimRefresh函数进行处理。

handleSimRefresh函数将负责获取或更新卡对应的文件信息。
例如,图7中的fetchSimRecords函数,将向modem发送Request,获取卡对应的诸多信息。
为了保证主要流程的清晰性,图7中略去了该过程中,框架与modem的大量交互过程。

9、当卡信息加载完毕后,IccRecords将调用onAllRecordsLoaded函数,通知它的观察者。

10、IccCardProxy收到通知后,将调用broadcastInternalIccStateChangedInent函数,
发送ACTION_INTERNAL_SIM_STATE_CHANGED广播。
该广播发送后,将触发数据卡的选择流程。

3.2 选择数据卡的流程
如图8所示,为原生代码中选择数据卡的流程。

图8 大图链接

1、在前文中我们提到了IccCardProxy在卡信息加载完毕后,会发送ACTION_INTERNAL_SIM_STATE_CHANGED广播。
SubscriptionInfoUpdater收到广播后,就会调用handleSimLoaded函数进行处理。

2、在handleSimLoaded函数中,主要工作将由updateSubscriptionInfoByIccId函数来承担。
updateSubscriptionInfoByIccId依次调用clearSubscriptionInfo和addSubscriptionInfo函数完成实际的工作。

3、如图8所示,clearSubscriptionInfo主要用于清除slot Id和sub Id之间的关系。
毕竟在卡槽中重新插入一张卡后,sub Id可能发生了变化,因此需要先清理过去的缓存信息。

4、addSubscriptionInfo函数调用SubscriptionController的接口进行实际工作。
如同图8中的注释,SubscriptionController首先将更新一些数据库字段,
设置一些卡界面显示所需要的信息,例如SIM卡在设置界面中的名称、颜色等。

然后,SubscriptionController重新建立起Slot Id和Sub Id之间的映射关系。

最后,SubscriptionController判断终端中只有一张卡时,开始设置数据、语音和短信使用的Sub Id等。
数据卡设置完毕后,SubscriptionController将发送相应的广播信息。

5、此外,SubscriptionController调用setDefaultDataSubId设置数据卡后,
还需要利用ProxyController的接口设置每个Phone对应的无线能力,即设置每张卡使用的协议栈。

3.3 设置数据卡后的影响
在这一部分我们总结一下选择数据卡后,对数据业务中PhoneSwitcher和TelephonyNetworkFactory的影响。

3.3.1 PhoneSwitcher的处理流程

图9 大图链接

1、如图9所示,PhoneSwitcher在初始时注册了广播接收器,监听数据卡的变化。
同时,TelephonyNetworkFactory初始时,注册成PhoneSwitcher的观察者,监听Phone数据能力的切换。

2、当PhoneSwitcher收到数据卡变化的广播后,将调用onEvaluate函数进行处理。
onEvaluate函数判断出数据卡确实发生变化后,就会激活数据卡对应的Phone的拨号能力;
同时去激活非数据卡对应phone的拨号能力。

PhoneSwitcher中定义了PhoneState类,专门用于处理激活和去激活Phone拨号能力的工作,
同时维护对应Phone的激活状态。

在完成上述工作后,PhoneSwitcher就会通知TelephonyNetworkFactory发生了Active Phone Switch。

3、TelephonyNetworkFactory收到通知后,就会调用onActivePhoneSwitch进行处理。

在onActivePhoneSwitch函数中,首先会调用PhoneSwitcher提供的isPhoneActive接口,
查询并更新TelephonyNetworkFactory的激活状态。

此时,对于处于激活状态的TelephonyNetworkFactory来说,就可以进入后续的拨号的流程;
对于非激活的TelephonyNetworkFactory而言,则会进入后续断开数据连接的流程。

PhoneSwitcher的这部分流程实际上起到了一种承前启后的作用。
终端加载完APN、设置完数据卡后,必须在这一步激活数据卡对应Phone的拨号能力,
后续的数据拨号流程才能顺利完成。

3.3.2 TelephonyNetworkFactory的处理流程
PhoneFactory初始时创建出了SubscriptionMonitor对象,该类注册了广播监听器,监听数据卡的变化。

图10 大图链接

1、如图10所示,TelephonyNetworkFactory初始化时就注册成为了SubscriptionMonitor的观察者。

当SubscriptionMonitor收到数据卡变化的广播后,
将根据数据卡的sub id等信息判断数据卡是否真的发生了改变。

一旦发现数据卡确实发生了变化,SubscriptionMonitor就会通知两个Phone对应的TelephonyNetworkFactory。

2、TelephonyNetworkFactory收到通知后,将调用onDefaultChange函数进行处理。

在onDefaultChange函数中,TelephonyNetworkFactory将判断自己是否已经被激活。
如果没有被激活,TelephonyNetworkFactory将不做任何处理。
毕竟TelephonyNetworkFactory没被激活,就无法进行拨号,也不会有去拨号的义务。

3、当TelephonyNetworkFactory已经激活时,就会进一步调用applyRequests函数。

applyRequests函数将判断当前的TelephonyNetworkFactory是否对应于数据卡。
如过对应于数据卡,那么TelephonyNetworkFactory将进行后续的拨号工作;
否则,TelephonyNetworkFactory将进行后续的去拨号工作。

从上面的描述可以看出,对于这一部分流程而言,
数据卡的变化主要将影响到已经激活的TelephonyNetworkFactory。

3.4 小结
在第三部分,我们主要介绍了原生流程中,框架加载卡信息的流程以及选择数据卡的流程。

同时,数据卡选择后必须激活Phone的拨号能力,
因此在第三部分还介绍了数据卡选择后对PhoneSwitcher和TelephonyNetworkFactory的影响。

四、ConnectivityService概述
考虑到许多场景下的数据业务拨号流程,与ConnectivityService有着非常密切的关系,
因此在分析实际的数据拨号流程前,先简单了解一下ConnectivityService的基本内容。

4.1 基本概念

图11 大图链接

如图11所示,ConnectivityService建立起的网络管理体系,
实际上是架构在NetworkRequest、NetworkFactory和NetworkAgent这三个概念之上的。

NetworkRequest作为信息输入,NetworkFactory作为工厂,NetworkAgent作为产出。

1 NetworkRequest
NetworkRequest表示申请网络的请求。

当一个客户端需要建立网络时,就会向ConnectivityService发送NetworkRequest。
NetworkRequest中将携带客户端需要的网络对应的类型、能力等信息。

例如发送短信时,彩信对应的APK就会向ConnectivityService发送NetworkRequest,
并在其中指定需要MMS类型的网络,同时指定建立网络时对应的Sub Id。

2 NetworkFactory
NetworkFactory负责处理NetworkRequest,显然这是一种基于工厂模式的设计方式。

终端支持的所有网络都有对应的NetworkFactory。
例如,数据业务有对应的TelephonyNetworkFactory、蓝牙有对应的BluetoothTetheringNetworkFactory、
WiFi有对应的WifiNetworkFactory等。

这些NetworkFactory都会定义自己支持的网络能力等信息,然后在开机时注册到ConnectivityService中。

当ConnectivityService收到NetworkRequest后,
通过匹配NetworkRequest要求的网络能力及各种NetworkFactory提供的网络能力,
就能判断出NetworkRequest应该交给哪个NetworkFactory处理。

NetworkFactory收到NetworkRequest后,就会根据NetworkRequest中的信息,建立起对应的网络。
例如,TelephonyNetworkFactory收到建立彩信的NetworkRequest后,
就会进行激活彩信的ApnContext、建立数据连接等一系列操作。

3 NetworkAgent
NetworkAgent是网络建立成功后生成的抽象对象,它携带了网络相关的信息。

当建立网络成功后,框架内就会创建出符合该网络类型的NetworkAgent,
并将NetworkAgent注册到ConnectivityService中。
例如,数据业务拨号成功后,就会创建出DcNetworkAgent。

ConnectivityService将验证NetworkAgent的有效性,
并利用NetworkManagementService与netd通信,
配置NetworkAgent对应网络的路由信息等。

4.2 管理策略
根据上文介绍的基本概念我们可以看出,当一个NetworkRequest被多个NetworkFactory匹配时,
ConnectivityService会将该NetworkRequest发送给所有的NetworkFactory处理。
于是,每个NetworkFactory均会创建对应的网络,并生成NetworkAgent注册到ConnectivityService中。

然而,从需求的角度来讲,同一个设备在同一时间内不需要连接多个网络。
例如,假设用户的目的就是上网,WiFi和移动网络均能满足用户上网的需求,
设备就没有必要同时连接WiFi和移动网络。

因此,当终端具备建立多个网络的能力时,ConnectivityService需要决定保留哪个网络。

为了解决这个问题,ConnectivityService引入了分数的概念。
当多个网络同时满足一个NetworkRequest时,分数高者就可以保留,其余的网络均被断开。

例如,由于WiFi网络的分数高于数据网络,因此当WiFi和数据的开关同时开启时,
ConnectivityService仅会保留WiFi网络。

在这种网络管理策略下,ConnectivityService进一步避免了一些无意义的网络创建工作。

举例来说,当ConnectivityService收到一个NetworkRequest后,
若发现终端已经建立起能够处理该NetworkRequest的网络,
那么将该NetworkRequest发送给其它NetworkFactory处理时,
会一并发送当前网络对应的分数。

其它NetworkFactory收到这个NetworkRequest后,
会判断自己能否处理这个NetworkRequest及自己的分数是否大于当前网络。
当以上两个条件均满足时,NetworkFactory才能着手进行创建新的网络的工作。

从上面的例子可以看出,ConnectivityService引入了分数的概念后,
每个NetworkFactory在处理NetworkRequest时,
都会考虑终端当前网络的信息,从而抑制了低分数网络的创建。

4.3 数据业务相关工厂的注册
了解ConnectivityService的基本原理后,我们看看数据业务与ConnectivityService的联系。

4.3.1 TelephonyNetworkFactory的注册
两个Phone对应的TelephonyNetworkFactory在初始化时均会注册到ConnectivityService中。

图12 大图链接

1、如图12所示,TelephonyNetworkFactory首先会调用makeNetworkFilter定义自己支持的网络能力;
然后调用setScoreFilter函数,定义自己的分数。

2、做完上述准备工作后,TelephonyNetworkFactory调用其父类的register函数,
将自己注册到ConnectivityService中。

3、ConnectivityService发现NetworkFactory注册后,会将当前已有的NetworkRequest发送NetworkFactory处理。

这里需要说明的是:ConnectivityService初始化时,就定义了一个默认地NetworkRequest,
这个NetworkRequest要求建立的网络必须有访问Internet等能力,
可以同时被数据网络、WiFi网络等处理。

如前文所述,该NetworkRequest发送给NetworkFactory时,会携带对应的分数。
如果终端当前没有能够处理这个NetworkRequest的网络,那么携带的分数为0;
如果终端已经建立起能够处理这个NetworkRequest的网络,那么携带的分数为网络对应的分数。

例如,如果开机时NetworkRequest已经被用于建立起了WiFi网络,
那么该NetworkRequest发往TelephonyNetworkFactory时,将携带WifiFactory定义的分数。

4、NetworkFactory收到NetworkRequest后,将调用evalRequest函数进行实际的工作。

evalRequest函数主要用于判断收到的NetworkRequest能否被当前NetworkFactory处理。

对于建立网络的流程而言,evalRequest将判断该NetworkRequest是否为重复的请求,
NetworkFactory能否支持NetworkRequest要求的能力,
NetworkFactory的分数是否大于NetworkRequest携带的分数等。

当所有的条件均满足时,evalRequest将调用子类实现的needNetworkFor函数。

5、TelephonyNetworkFactory的needNetworkFor函数,将实际的工作递交给onNeedNetworkFor。

在onNeedNetworkFor函数中,TelephonyNetworkFactory将首先保存收到的NetworkRequest,
然后判断自己是否被激活。

只有处于激活状态的TelephonyNetworkFactory才会进行后续的拨号流程。

需要注意的是:初始时两个Phone对应的TelephonyNetworkFactory均处于未激活状态,
之后的能够被激活,则取决于数据卡的设置情况。

4.3.2 PhoneSwitcher的注册
除了TelephonyNetworkFactory外,Android N新增的PhoneSwitcher也会在其初始化时,
注册一个内部定义的NetworkFactory子类到ConnectivityService。

该NetworkFactory子类为PhoneSwitcherNetworkRequestListener,
后文为了方便描述,将其简称为PSListener。

图13 大图链接

1、如图13所示,PhoneSwitcher初始时定义了PSListener支持的网络能力,
然后创建出PSListener,并设置其对应的分数。

完成上述工作后,PhoneSwitcher就将PSListener注册到ConnectivityService中。

2、PSListener收到ConnectivityService发送的NetworkRequest,判断需要建立网络后,
同样将调用needNetworkFor函数来处理。

PSListener中的needNetworkFor函数将发送消息,触发PhoneSwitcher中onRequestNetwork进行实际的工作。

3、onRequestNetwork将保存收到的NetworkRequest,并调用onEvaluate函数来选择需要的激活的TelephonyNetworkFactory。

4、ConnectivityService中初始创建的NetworkRequest并没有指定Sub Id,
因此onEvalue函数将激活默认数据卡对应的TelephonyNetworkFactory。

如果在PhoneSwitcher收到NetworkRequest前,终端已经完成了默认数据卡的设置,
那么PhoneSwitcher将向数据卡对应的RIL下发RIL_REQUEST_ALLOW_DATA(true)的信息,即激活其数据拨号能力。
然后,通知对应的TelephonyNetworkFactory进行拨号相关的操作。
如果此时数据业务开关已经打开,那么就能成功建立起数据网络。
这其实也是数据开关保持开启时,数据业务开机拨号的流程。

如果PhoneSwitcher收到NetworkRequest时,终端还没有完成默认数据卡的设置,
那么PhoneSwitcher将在数据卡设置后,再进行激活数据拨号能力相关的工作。

4.4 小结
在这一章中,我们介绍了ConnectivityService的一些基本信息,它统一管理着框架建立的所有网络。
依托于NetworkRequest及对应分数机制,ConnectivityService确定了多个网络并存时的处理规则。

此外,本章着重介绍了数据业务与ConnectivityService有直接联系的TelephonyNetworkFactory和PhoneSwitcher类。
这部分内容与后续的拨号流程,有着非常密切的关系。

五、数据拨号流程
前文花了很大的精力介绍APN、数据卡及ConnectivityService相关的内容,这些内容其实均是为本章作铺垫。
毕竟前面涉及的流程,都是数据业务拨号流程顺利进行的前提条件。

在这一部分,我们开始介绍数据业务拨号相关的流程,我们主要分析以下三种场景:
1、数据卡设置完毕后,数据开关从关闭到打开时,框架的拨号流程;
2、数据开关保持开启,切换数据卡时的拨号流程;
3、发送彩信时,建立短连接时的拨号流程。
这三种场景下的拨号流程,主要的差别在于触发拨号的条件不一样,
最终的拨号过程实际上是一致的,即殊途同归。

5.1 通过数据开关拨号的流程
5.1.1 类图介绍

图14 大图链接

我们首先结合图14看看数据业务拨号流程涉及的主要类。
虽然本节我们介绍的是数据开关触发的拨号流程,但图14中大部分类是其它场景也会使用的。

1、原生代码的拨号界面由CellDataPreference类来实现。
当点击该界面的数据开关时,将调用TelephonyManager的接口开始拨号。

2、TelephonyManager是Phone进程提供的对外接口之一,它将通过Binder通信调用到PhoneInterfaceManager的接口。
此时,拨号命令完成了从Settings进程到Phone进程的传递。

3、PhoneInterfaceManager利用PhoneFactory得到拨号命令中Sub Id对应的Phone对象,
然后将拨号命令递交给该Phone对象处理。

4、Phone对象实际的类型为GsmCdmaPhone。
该对象将数据拨号命令交给对应的DcTracker处理。

5、DcTracker是拨号流程中,进行大量逻辑判断工作的类之一,
将负责选择用于拨号的APN,判断拨号是否能够继续进行等。

需要说明的是:
用于拨号的APN必须是设置数据卡后,
与激活的ApnContext相匹配的APN。

判断拨号能否继续进行的逻辑涉及到很多的细节,
但比较主要的是判断数据能力是否被激活、是否有激活态的ApnContext等。

6、当判断拨号可以继续进行后,DcTracker就负责创建出本次拨号对应的DataConnection对象。
当然,如果框架类有可复用的DataConnection,那么会优先使用可复用的DataConnection。

7、DataConnection是一个状态机,图14中画出了拨号流程中正常情况下涉及的几个状态。
除此之外,DataConnection中还包含了去拨号及错误处理相关的状态。

DataConnection被创建后,在初始时处于DcInactiveState。
在该状态,主要负责通过RIL向modem发送拨号指令;
发送完毕后,DataConnection就从DcInactiveState转入到了DcActivatingState。

8、待RIL返回拨号命令的结果后,DcActivatingState将根据返回信息的内容,判断拨号是否成功。

如果拨号失败,DcActivatingState将转移到错误处理对应的状态中;
如果拨号成功,DcActivatingState就会进入到DcActiveState中。

9、一旦DataConnection进入到了DcActiveState,就意味着终端已经成功建立起了网络连接。
此时,DataConnection将通知DcTracker,后者通过DefaultPhoneNotifier通知数据拨号成功的消息。

DefaultPhoneNotifier将信息递交给TelephonyRegistry,
由其通知注册了PhoneStateListener观察数据状态变化的所有观察者。

10、此外,在DcActiveState中,DataConnection将根据拨号获取的网络信息创建出DcNetworkAgent。

DcNetworkAgent是NetworkAgent的子类,在其父类构造函数的最后,
将调用ConnectivityManager的接口,将自己注册到ConnectivityService中。

11、ConnectivityService检测到NetworkAgent的注册请求后,就会进行一些验证、
利用NetworkManagementService进行相关的信息配置、发送广播通知网络状态变化等工作。

12、如图14所示,NetworkManagementService通过NativeDaemonConnector,
以Socket通信的方式与netd进程交互。

netd中的CommandListener将负责解析收到的数据,并调用对应命令进行处理,
这是一种典型的基于命令模式的设计。

netd在数据业务的拨号流程中,主要的作用是创建出物理接口、
网络对应的框架层抽象,同时完成路由等信息的配置。

以上是数据拨号流程中比较主要的内容,其中的每一步实际上都涉及到许多的细节,
此外为了降低了整个分析过程的复杂度,我也略去了一些中间步骤。
若需要分析更具体的内容,还是需要深入阅读源码。

5.1.2 流程图介绍

图15 大图链接

这一节涉及的内容,其流程图大致如图15所示。
图15中,我略去了从界面拨号到调用DcTracker接口这一部分。
这一部分内容逻辑较少,基本上是单纯的接口调用,因此没有必要单独画出来。

从图15可以看出,DcTracker在数据拨号流程中,承担的任务比较重要,
包括:选择可用的ApnContext,并刷选出该ApnContext对应的可用ApnSetting;
判断拨号能否继续进行;创建DataConnection等。

当拨号进入到DataConnection后,大致的流程就如同我上文介绍类图所描述的。

此外,如图15所示,DataConnection注册NetworkAgent到ConnectivityService后的这部分流程,我也省略掉了。
此处仅简单介绍一下后续内容:
1、与Netd通信,配置根据NetworkAgent的信息,配置接口、路由等;

2、利用NetworkAgent对应的NetworkMonitor检测NetworkAgent的可用性。
NetworkMonitor本身也是一个状态机,它主要用于验证申明具有Internet能力的NetworkAgent是否真的可以上网。
其原理就是向指定地址发送Http请求,通过请求得到的返回结果,判断网络是否需要验证、是否能够访问成功。

3、由于有新的NetworkAgent注册,因此ConnectivityService需要匹配当前框架中的NetworkRequest和NetworkAgent,
判断该NetworkAgent是否需要保留、是否需要断开其它NetworkAgent等。

5.2 切换数据卡的拨号流程
在上文中我们已经提到过了,切换数据卡将触发TelephonyNetworkFactory和PhoneSwitcher进行激活和去激活的动作。
在这一部分,我们就看看PhoneSwitcher激活TelephonyNetworkFactory后的拨号流程。

图16 大图链接

TelephonyNetworkFactory初始时就会保存ConnectivityService发送的default NetworkRequest。
当数据卡切换,TelephonyNetworkFactory被激活后,就会调用applyRequests函数处理保存的NetworkRequest。

1、如图16所示,当TelephonyNetworkFactory对应的是数据卡时,
applyRequests中将会调用DcTracker的requestNetwork函数进行处理。

2、在DcTracker的requestNetwork函数中,将根据NetworkRequest指定的网络能力,选择对应的ApnContext。

例如,长连接拨号时将选择Default类型的ApnContext,短连接拨号时将选择MMS类型的ApnContext。

3、选择出NetworkRequest对应的ApnContext后,DcTracker将调用ApnContext的incRefCount函数,增加对ApnContext的引用计数。

当ApnContext第一次被使用时,就会调用DcTracker的setEnabled函数,进行激活ApnContext并拨号的流程。
这么设计的目的是,当多个NetworkRequest对应于同一个ApnContext时,只有第一次需要进行激活相关的操作。

4、DcTracker的setEnabled函数被调用后,将发送消息触发DcTracker调用onEnableApn函数。
onEnableApn函数的主要工作将由applyNewState函数完成。

applyNewState将调用ApnContext的setEnabled函数,将ApnContext置为激活状态。
后续的拨号流程判断ApnContext是否可用时,依据的就是此时设置的标志位。

5、完成ApnContext的激活后,DcTracker将调用trySetupData函数,开始进行数据拨号。
后续的内容与5.1一致,此处不再赘述。

5.3 短连接拨号
最后我们来看看短连接的建立过程。

图17 大图链接

1、如图17所示,彩信APK每次发送彩信时,将由MmsNetworkManager发起短连接的建立过程。

在MmsNetworkManager的构造函数中,创建了一个NetworkRequest,指定了需要MMS类型的网络,
同时指定本次发送彩信的sub id。

MmsNetworkManager将调用ConnectivityManager的requestNetwork函数,将NetworkRequest发送给ConnectivityService。

2、ConnectivityService收到请求后,将调用handleRegisterNetworkRequest函数进行处理。
该函数会将NetworkRequest发送给所有注册的NetworkFactory。

正如之前介绍ConnectivityService时提到过的,如果此时框架中已经有NetworkAgent能够匹配NetworkRequest,
那么NetworkRequest发往NetworkFactory时,将携带对应的分数。

3、每个NetworkFactory收到NetworkRequest后,都将判断自己能否处理NetworkRequest。

由于TelephonyNetworkFactory和NetworkRequest都指定了Sub id,
因此只有sub id匹配的TelephonyNetworkFactory才会实际处理该NetworkRequest。

PhoneSwitcher在初始化时,指定其能匹配任何sub id对应的NetworkRequest,
因此PhoneSwitcher能够收到MMS对应的NetworkRequest。

4、如果处理MMS NetworkRequest的TelephonyNetworkFactory对应的是数据卡,
那么该TelephonyNetworkFactory将直接进行后续的拨号工作;
否则,将保存该NetworkRequest,并等待PhoneSwitcher激活该TelephonyNetworkFactory。

5、PhoneSwitcher收到MMS NetworkRequest后,将判断该NetworkRequest指定的sub id是否为data sub id。
如果是的话,PhoneSwitcher不会进行实际的工作;
否则PhoneSwitcher会将数据拨号能力切换到MMS对应的phone上,
同时激活对应的TelephonyNetworkFactory。

6、当TelephonyNetworkFactory被激活后,将调用applyRequests函数进行后续的拨号过程,这部分内容就和5.2一致了。

7、当MMS发送完毕后,彩信APK会释放NetworkRequest。
若之前进行过数据能力切换的工作,那么PhoneSwitcher将重新将数据能力切换回数据卡对应的phone。

5.4 小结
在这一章我们介绍了数据业务在一些主要场景的拨号流程。

当前几章介绍的拨号准备工作完成后,本章的流程就显得比较清晰了。
当然,由于表达方式的限制,本章的流程实际上略去了很多的细节,
因此还是需要参考源码,才能更好地进行理解。

六、总结
至此,Android N中数据业务相关的主要内容介绍完毕。

正如我在前文提到过的,由于自己的能力、经验等的限制,
可能目前仅将数据业务限制在上述的流程中。

同时,考虑到在理解拨号场景的流程的前提下,
一些去拨号场景的流程比较容易理解,于是并没有叙述去拨号相关的内容。

此外,由于文字对实际流程的转化率有限,难以传达出代码的力量,
导致整个的分析一定存在遗漏的地方。

因此,整个文档虽然看起来内容很多,但还是存在着诸多漏洞,
所以在本文的最后,还是呼吁大家RTFSC。

2 0