一、前言
本文旨在总结Android N中数据业务在框架侧的流程,
主要包括APN加载、数据卡选择、长连接拨号、短连接拨号等。
目前框架侧的分析,侧重于AOSP相关的源码。
同时,考虑到拨号与去拨号流程涉及的类基本相似,
在理解拨号流程的基础上,可以比较容易地掌握去拨号的流程,
因此本文不单独分析去拨号涉及的流程。
最后,由于整个数据业务涉及到的细节相当多,文字有时难以进行有效地表述,
而且受限于个人的能力,对流程的梳理难免会有遗漏。
因此本文仅作为研究代码的参考和记录信息,所有流程的详细内容还是以代码为准。
图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文件。