目录
前言
本文档将基于Android 12 AOSP源码进行流程分析,围绕DcTracker的核心处理机制和关键业务流程,疏通Android手机的移动数据业务基本原理和关键流程,我把流程图放在了最后。
之前我疏忽了,Android12仍然采用的是DcTracker,但从13开始已经默认使用DataNetworkController作为数据栈了,14开始将会彻底删除老版本的数据栈DcTracker,下次会给大家带来最新的DataNetworkController的解析。
基础知识
TeleService系统应用在创建GsmCdmaPhone对象时,在该Phone对象的构造方法内会同时创建DcTracker对象。DcTracker自身继承于Handler,在构造方法中主要实现了以下三个业务:
- 初始化ApnContext
- 注册各类监听(广播接收器、Observer, Handler消息注册)
- 创建DcController对象
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
public DcTracker(Phone phone, @TransportType int transportType) {
super();
mPhone = phone;
......
mResolver = mPhone.getContext().getContentResolver();
......
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(INTENT_DATA_STALL_ALARM);
filter.addAction(INTENT_PROVISIONING_APN_ALARM);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
mDataEnabledSettings = mPhone.getDataEnabledSettings();
//注意这个把自身作为handler传过去方便之后传递消息,当数据业务开关状态改变时会通过这个通知DCTracker
mDataEnabledSettings.registerForDataEnabledChanged(this, DctConstants.EVENT_DATA_ENABLED_CHANGED, null);
mDataEnabledSettings.registerForDataEnabledOverrideChanged(this, DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED);
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
mHandlerThread = new HandlerThread("DcHandlerThread");
mHandlerThread.start();
Handler dcHandler = new Handler(mHandlerThread.getLooper());
//创建DcController对象
mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager, dcHandler.getLooper(),
tagSuffix);
registerForAllEvents();
mApnObserver = new ApnChangeObserver();
phone.getContext().getContentResolver().registerContentObserver(
Telephony.Carriers.CONTENT_URI, true, mApnObserver);
//初始化各类ApnContext
initApnContexts();
addDefaultApnSettingsAsNeeded();
mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
registerSettingsObserver();
mThrottleStatusCallback = new ThrottleStatusChangedCallback();
mDataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback);
}
APN
APN(Access Point Name)是Android手机实现移动数据上网业务必须配置的参数,用来决定手机使用哪一种方式访问网络,其配置信息保存在telephony.db中的名为carriers的表中。关键字段如下:
字段 | 说明 |
name | APN配置名称 |
numeric | 运营商编号 |
apn | APN接入点, 中国移动cmwap和cmnet |
proxy | 代理服务器地址 |
port | 端口号 |
mmsproxy | 彩信代理服务器地址 |
mmsport | 彩信代理服务器端口号 |
mmsc | 彩信接入服务地址 |
type | APN接入类型 |
APN配置信息一旦有问题就会导致无法上网。那如果需要做国内手机的Android定制化开发,或是国外的定制化开发,应该怎么增加APN配置呢?有三个方法:
- 定制化项目本地xml配置文件
- 在线更新
- APN配置管理界面手动增加
这里厂商常用的是前两个方法,我们不可能要求用户自己手动添加配置,因此厂商在定制开发过程中,会将APN配置信息提前配好,在TelephonyProvider的initDatabase方法里将APN信息插入表中.
比如我手头上的某手机厂商项目中,使用了前两个方法:
private File getApnConfFile() {
//加载本地预置配置文件
File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);
File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
File productConfFile = new File(Environment.getProductDirectory(), PARTNER_APNS_PATH);
//厂商的在线更新配置文件,这样更新重启手机后会更新表中的数据
File onlineConfFile = new File(ONLINE_UPDATED_APNS_PATH);
confFile = pickSecondIfExists(confFile, oemConfFile);
confFile = pickSecondIfExists(confFile, productConfFile);
confFile = pickSecondIfExists(confFile, updatedConfFile);
confFile = getNewerFile(confFile, onlineConfFile);
return confFile;
}
本地配置中截取部分APN配置信息如下:
<apn carrier="中国移动 (China Mobile) GPRS"
carrier_id = "1435"
mcc="460"
mnc="00"
apn="cmnet"
type="default,supl"
/>
<apn carrier="中国移动 (China Mobile) WAP"
carrier_id = "1435"
mcc="460"
mnc="00"
apn="cmwap"
proxy="10.0.0.172"
port="80"
type="default,supl"
/>
初始化ApnContext
在DcTracker构造方法里将调用initApnContexts方法初始化ApnContext,每个ApnContext对象用来保存对应APN网络类型的配置参数、连接状态等等。
private void initApnContexts() {
PersistableBundle carrierConfig;
//查看是否有运营商配置服务
CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
} else {
carrierConfig = null;
}
initApnContexts(carrierConfig);
}
private void initApnContexts(PersistableBundle carrierConfig) {
// 从本地资源中加载网络配置
final Collection<ApnConfigType> types =
new ApnConfigTypeRepository(carrierConfig).getTypes();
for (ApnConfigType apnConfigType : types) {
ApnContext apnContext = new ApnContext(mPhone, apnConfigType.getType(), mLogTag, this,
apnConfigType.getPriority());
......
mPrioritySortedApnContexts.add(apnContext);
......
}
mPrioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
}
在初始化ApnContext时,会创建ApnConfigTypeRepository对象,sDefaults对象是里面的一个静态对象,在静态代码块完成初始化,在初始化时就会添加很多默认的配置参数,包括我们关注的网络配置信息:
sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
"enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
"ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
});
从默认配置参数可以得到13个ApnConfigType对象,从而创建出13个ApnContext对象,字符串数组中每个字符串中":'之前代表一种网络配置,":"后面的数字代表着该配置的优先级,所以优先级从高到低依次是
hipri -> mcx -> xcap -> mmx -> supl -> dun -> fota -> ims -> cbs -> ia -> emergency -> default -> enterprise。
手机上网将建立default类型的数据连接,当彩信来时,因为彩信建立的是mms类型,优先级比default高,所以会断开default连接而创建mms连接,因此,在发送和接收彩信的同时不能上网 。
DataConnection
DataConnection是用来在Telephony业务模型中管理移动数据业务的类,一个DataConnection对象代表手机移动数据业务的一个数据连接。
DataConnection继承与StateMachine类,是一个典型的状态机,内部定义了6个状态类,全部继承与State类,每个状态类内部封装了对应状态下的较复杂的逻辑处理。
State子类 | 说明 |
DcDefaultState | 默认状态 |
DcInactiveState | 不活动状态 |
DcActivatingState | 正在激活状态 |
DcActiveState | 激活状态 |
DcDisconnectingState | 正在断开中状态 |
DcDisconnectionErrorCreatingConnection | 在创建连接后正处于断开中状态 |
源自状态机模式带来的优势,DataConnection切换不同连接状态时,不必操心切换状态的复杂逻辑处理,只需要通过使用内部的handler发送消息切到对应状态,之后交给对应状态类内部实现的processMessage方法处理即可。
在DataConnection的构造方法里面通过addState方法添加这6个状态对象,并且给除了DcDefaultState以外的每个状态指定父状态为DcDefaultState,这样就形成了一个树形结构,当一个State对象不能处理一个消息时就会向上交给父节点处理。
开启移动数据业务
手机可以通过以下两个交互界面开启或关闭移动数据业务:
- 通知栏快捷控制
- Settings -> Mobile network设置界面
流程分析:
打开或关闭移动数据业务的入口是TelephonyManager.setDataEnabledForReason方法,传入两个参数——切换原因和切换状态。(以前版本用的setDataEnabled方法已过时)
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void setDataEnabledForReason(@DataEnabledReason int reason, boolean enabled) {
setDataEnabledForReason(getSubId(), reason, enabled);
}
主要有四种理由:
- DATA_ENABLED_REASON_USER(用户操作控制)
- DATA_ENABLED_REASON_POLICY(由于policy数据业务被控制,通常是被限制)
- DATA_ENABLED_REASON_CARRIER(运营商特殊设置控制)
- DATA_ENABLED_REASON_THERMAL(热服务控制)
TelephonyManager的私有方法通过IBinder远程调用 ITelephony(PhoneInterfaceManager).setDataEnabledForReason 方法,接下来我们重点关注用户操作控制这一条链路:
/packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java
@Override
public void setDataEnabledForReason(int subId, @TelephonyManager.DataEnabledReason int reason, boolean enabled, String callingPackage) {
//检查权限
......
phone.getDataEnabledSettings().setDataEnabled(reason, enabled);
......
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
public synchronized void setDataEnabled(@TelephonyManager.DataEnabledReason int reason, boolean enabled) {
switch (reason) {
case TelephonyManager.DATA_ENABLED_REASON_USER:
setUserDataEnabled(enabled);
break;
//其他理由
......
}
}
}
private synchronized void setUserDataEnabled(boolean enabled) {
// By default the change should propagate to the group.
setUserDataEnabled(enabled, true);
}
public synchronized void setUserDataEnabled(boolean enabled, boolean notifyMultiSimSettingController) {
......
//先更新数据库设置
boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(), Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));
if (changed) {
......
//还记得DcTacker构造方法里面的添加监听方法吗,DcTacker就是在这里面接收到数据业务状态改变的通知
updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
......
}
}
DataEnabledSettings使用从DcTracker传过来的handler对象(也就是DcTracker本身)发送消息,DcTracker在handleMessage方法里开始处理消息:
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@Override
public void handleMessage (Message msg) {
......
switch (msg.what) {
......
case DctConstants.EVENT_DATA_ENABLED_CHANGED:
ar = (AsyncResult) msg.obj;
if (ar.result instanceof Pair) {
Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result;
boolean enabled = p.first; //开关状态
int reason = p.second; // 切换理由
onDataEnabledChanged(enabled, reason);
}
break;
......
}
}
private void onDataEnabledChanged(boolean enable, @DataEnabledChangedReason int enabledChangedReason) {
......
if (enable) { //开启移动数据业务
reevaluateDataConnections();
setupDataOnAllConnectableApns(Phone.REASON_DATA_ENABLED, RetryFailures.ALWAYS);
} else { //关闭移动数据业务
......
cleanUpAllConnectionsInternal(true, cleanupReason);
}
}
onDataEnabledChanged方法内有两个逻辑处理逻辑分支:开启移动数据业务和关闭移动数据业务。我们继续走开启移动数据业务逻辑setupDataOnAllConnectableApns。
protected void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
for (ApnContext apnContext : mPrioritySortedApnContexts) { //按照优先级开始遍历调用
setupDataOnConnectableApn(apnContext, reason, retryFailures);
}
}
protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
RetryFailures retryFailures) {
......
if (apnContext.isConnectable()) {//该ApnContext可连接,
apnContext.setReason(reason);
trySetupData(apnContext, REQUEST_TYPE_NORMAL, null);
}
}
如上代码,DcTracker会按照优先级开始遍历ApnContext,并判断当前ApnContext是否处于可连接状态,ApnContext定义见上面的基础知识。接着我们继续关注 trySetupData方法的内部逻辑:
private void trySetupData(ApnContext apnContext, @RequestNetworkType int requestType, @Nullable Message onHandoverCompleteMsg) {
......
DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
//判断是否允许传输数据
boolean isDataAllowed = isDataAllowed(apnContext, requestType, dataConnectionReasons);
if (!isDataAllowed) {
......
return;
}
if (apnContext.getState() == DctConstants.State.FAILED) {
String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
ApnContext.requestLog(apnContext, str);
apnContext.setState(DctConstants.State.IDLE);
}
//获取驻网的移动数据业务RadioTechnology
int radioTech = getDataRat();
......
apnContext.setConcurrentVoiceAndDataAllowed(mPhone.getServiceStateTracker() .isConcurrentVoiceAndDataAllowed());
//判断该apnContext是否处于空闲状态
if (apnContext.getState() == DctConstants.State.IDLE) {
ArrayList<ApnSetting> waitingApns =
buildWaitingApns(apnContext.getApnType(), radioTech);
if (waitingApns.isEmpty()) {
//未找到对应APN配置信息
return;
} else {
apnContext.setWaitingApns(waitingApns);
}
}
if (!setupData(apnContext, radioTech, requestType)
&& requestType == REQUEST_TYPE_HANDOVER) {
sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
}
}
继续分析setupData方法:
private boolean setupData(ApnContext apnContext, int radioTech,
@RequestNetworkType int requestType) {
ApnContext.requestLog(apnContext, "setupData. requestType=" + requestTypeToString(requestType));
ApnSetting apnSetting;
DataConnection dataConnection = null;
//从缓存的ApnSettings集合中获取对象,每个ApnSettings保存从我们上面说的apn配置数据表中的一行数据,包含了nmc,mmsc等等
apnSetting = apnContext.getNextApnSetting();
if (dataConnection == null) {
......
//先查询是否缓存起来且不在使用状态的DataConnection对象,没有则再创建一个
dataConnection = findFreeDataConnection();
if (dataConnection == null) {
dataConnection = createDataConnection();
}
......
}
final int generation = apnContext.incAndGetConnectionGeneration(); //计数器
apnContext.setDataConnection(dataConnection);
apnContext.setApnSetting(apnSetting); //将apnSetting与该apnContext对象绑定
apnContext.setState(DctConstants.State.CONNECTING); //设置为"连接中"状态
Message msg = obtainMessage();
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
ApnSetting preferredApn = getPreferredApn();
boolean isPreferredApn = apnSetting.equals(preferredApn);
dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType, mPhone.getSubId(), isPreferredApn); //激活移动数据业务
return true;
}
接下来我们重点关注DataConnection,DataConnection的定义见基础知识部分。
createDataConnection方法会调用DataConnection.makeDataConnection静态方法创建DataConnection对象并启动状态机。最后分析bringUp方法:
public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology, Message onCompletedMsg, int connectionGeneration,
@RequestNetworkType int requestType, int subId, boolean isApnPreferred) {
if (mApnSetting == null) {
mApnSetting = apnContext.getApnSetting();
}
sendMessage(DataConnection.EVENT_CONNECT,
new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
connectionGeneration, requestType, subId, isApnPreferred));
}
根据传入的参数创建ConnectionParams对象,然后通过sendMessage方法发送DataConnection.EVENT_CONNECT类型的消息。根据我们在基础知识部分讲的那样,根据树状结构,是DataConnection.mInactiveState进行处理,处理逻辑如下:
case EVENT_CONNECT:
ConnectionParams cp = (ConnectionParams) msg.obj;
......
//调用主类的私有方法connect
int cause = connect(cp);
......
//切换到正在激活状态
transitionTo(mActivatingState);
return HANDLED;
DataConnection对象的connect方法的主要处理逻辑如下。
private @DataFailureCause int connect(ConnectionParams cp) {
......
mCreateTime = -1;
mLastFailTime = -1;
mLastFailCause = DataFailCause.NONE;
//创建数据连接完成的消息
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
DataProfile dp = new DataProfile.Builder()
.setApnSetting(mApnSetting)
.setPreferred(cp.mIsPreferredApn)
.build();
boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
boolean isUnmeteredApnType = !ApnSettingUtils.isMeteredApnType(
cp.mApnContext.getApnTypeBitmask(), mPhone);
boolean allowRoaming = mPhone.getDataRoamingEnabled()
|| (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
|| isUnmeteredApnType));
......
allocatePduSessionId(psi -> {
this.setPduSessionId(psi);
mDataServiceManager.setupDataCall(
ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat), dp, isModemRoaming,
allowRoaming, reason, linkProperties, psi, null, td, matchAllRuleAllowed, msg
);
......
});
return DataFailCause.NONE;
}
走到了DataServiceManager.setupDataCall方法里,
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
@Nullable NetworkSliceInfo sliceInfo, @Nullable TrafficDescriptor trafficDescriptor,
boolean matchAllRuleAllowed, Message onCompleteMessage) {
CellularDataServiceCallback callback = new CellularDataServiceCallback("setupDataCall");
if (onCompleteMessage != null) {
mMessageMap.put(callback.asBinder(), onCompleteMessage);
}
......
mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
trafficDescriptor, matchAllRuleAllowed, callback);
......
}
DataServiceManager在这里通过IBinder方式远程调用phone进程的实现方法,处于DataService的内部类IDataServiceWrapper里面,通过handler发送DATA_SERVICE_REQUEST_SETUP_DATA_CALL消息:
frameworks/base/telephony/java/android/telephony/data/DataService.java
public abstract class DataService extends Service {
private class IDataServiceWrapper extends IDataService.Stub {
@Override
public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile,
boolean isRoaming, boolean allowRoaming, int reason,
LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0,
new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming,
allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
trafficDescriptor, matchAllRuleAllowed, callback))
.sendToTarget();
}
}
private class DataServiceHandler extends Handler {
@Override
public void handleMessage(Message message) {
......
case DATA_SERVICE_REQUEST_SETUP_DATA_CALL:
serviceProvider.setupDataCall(......);
break;
}
}
}
这个serviceProvider实际上有两种,分别由DataService的两个继承类CellularDataService和IwlanDataService实现提供,这两个类分别代表了蜂窝网数据业务和无线网数据业务。我们这里只看蜂窝网:
/frameworks/opt/telephony/src/java/com/android/internal/telephony/data/CellularDataService.java
private class CellularDataServiceProvider extends DataService.DataServiceProvider {
@Override
public void setupDataCall(int accessNetworkType, DataProfile dataProfile,
boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties,
int pduSessionId, NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
boolean matchAllRuleAllowed, DataServiceCallback callback) {
......
mPhone.mCi.setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming,
reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor,
matchAllRuleAllowed, message);
}
}
mCi是RILJ对象,最终由RIL完成Data Call移动数据业务的处理。这个RIL(Radio Interface Layer)是指无线通信接口层,主要运行在HAL上,HAL层我们就暂时不做分析了,这个RIL横跨了HAL层和Framework层,是telephony和modem沟通的桥梁。RIL处理成功后,DataConnection的状态mActivatingState将转换为mActiveState。
总流程图
这样framework层的移动数据业务启动流程就分析完毕了。开启业务流程图如下所示。
欢迎大家关注我,我今后会努力给大家带来更多的Android源码的解析和实用组件的。
谢谢大家 -v-