Android11 移动数据开关流程——DcTracker解析
该章只分析DcTracker部分逻辑
前言
Android移动数据上网是通过打开apn,三大运营商都有不同apn配置,如果需要上专网也要自行配置相关的apn配置。
该文主要是解析打开apn时DcTracker部分的流程,也是apn上网在framework中比较核心的一个地方。
一、DataEnabledSettings
frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
先看一下在此之前是怎么调到DcTracker里面的接口的
这个基本就是可以暴露给用户使用的打开数据流量开关的接口
最终往下进行走的是 updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
/**
* @param notifyMultiSimSettingController if setUserDataEnabled is already from propagating
* from MultiSimSettingController, don't notify MultiSimSettingController again.
* For example, if sub1 and sub2 are in the same group and user enables data for sub
* 1, sub 2 will also be enabled but with propagateToGroup = false.
*/
public synchronized void setUserDataEnabled(boolean enabled,
boolean notifyMultiSimSettingController) {
// Can't disable data for stand alone opportunistic subscription.
//判断是否是受限的subid,也就是订阅的卡槽
if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext()) && !enabled) return;
boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(), //设置上setting值
Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));
if (changed) {
localLog("UserDataEnabled", enabled);
mPhone.notifyUserMobileDataStateChanged(enabled);//通知给上层开关状态改变
updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED); //1
if (notifyMultiSimSettingController) {
MultiSimSettingController.getInstance().notifyUserDataEnabled(
mPhone.getSubId(), enabled);
}
}
}
updateDataEnabledAndNotify
这个方法很简单就是执行了两个方法
private synchronized void updateDataEnabledAndNotify(int reason) {
boolean prevDataEnabled = mIsDataEnabled;
updateDataEnabled();//1
if (prevDataEnabled != mIsDataEnabled) {
notifyDataEnabledChanged(!prevDataEnabled, reason);//2
}
}
updateDataEnabled
先看第一个
private synchronized void updateDataEnabled() {
if (isProvisioning()) {//一般开机引导完事之后都是false
mIsDataEnabled = isProvisioningDataEnabled();
} else { //更新上层状态
mIsDataEnabled = mInternalDataEnabled && (isUserDataEnabled() || mDataEnabledOverride
.shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_ALL))
&& mPolicyDataEnabled && mCarrierDataEnabled;
}
}
notifyDataEnabledChanged
private void notifyDataEnabledChanged(boolean enabled, int reason) {
mOverallDataEnabledChangedRegistrants.notifyResult(new Pair<>(enabled, reason));
}
mOverallDataEnabledChangedRegistrants这个变量是DcTracker构造方法内执行的注册近来的回调
因为DcTracker就是一个Handler
所以开始分析其内部接收到回调之后的操作
二、DcTracker
1.引入库
frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
这个就是其注册的代码
连同接收的地方一起写下
mDataEnabledSettings.registerForDataEnabledChanged(this,
DctConstants.EVENT_DATA_ENABLED_CHANGED, null);
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);//1
}
break;
onDataEnabledChanged
private void onDataEnabledChanged(boolean enable,
@DataEnabledChangedReason int enabledChangedReason) {
if (DBG) {
log("onDataEnabledChanged: enable=" + enable + ", enabledChangedReason="
+ enabledChangedReason);
}
if (enable) { //打开和关闭数据流量还不一样的流程
reevaluateDataConnections();//1
setupDataOnAllConnectableApns(Phone.REASON_DATA_ENABLED, RetryFailures.ALWAYS);//2
} else {
String cleanupReason;
switch (enabledChangedReason) {//根据不同原因,在执行清除连接方法的时候作为入参传进去
case DataEnabledSettings.REASON_INTERNAL_DATA_ENABLED:
cleanupReason = Phone.REASON_DATA_DISABLED_INTERNAL;
break;
case DataEnabledSettings.REASON_DATA_ENABLED_BY_CARRIER:
cleanupReason = Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN;
break;
case DataEnabledSettings.REASON_USER_DATA_ENABLED:
case DataEnabledSettings.REASON_POLICY_DATA_ENABLED:
case DataEnabledSettings.REASON_PROVISIONED_CHANGED:
case DataEnabledSettings.REASON_PROVISIONING_DATA_ENABLED_CHANGED:
default:
cleanupReason = Phone.REASON_DATA_SPECIFIC_DISABLED;
break;
}
cleanUpAllConnectionsInternal(true, cleanupReason);//3
}
}
第一个reevaluateDataConnections
reevaluateDataConnections
简单来说就是重新评估当前所有网络连接的可行性,更新状态,该打开的打开,该关闭的关闭,为打开的新的连接做准备
/**
* Reevaluate existing data connections when conditions change.
*
* For example, handle reverting restricted networks back to unrestricted. If we're changing
* user data to enabled and this makes data truly enabled (not disabled by other factors) we
* need to reevaluate and possibly add NET_CAPABILITY_NOT_RESTRICTED capability to the data
* connection. This allows non-privilege apps to use the network.
*
* Or when we brought up a unmetered data connection while data is off, we only limit this
* data connection for unmetered use only. When data is turned back on, we need to tear that
* down so a full capable data connection can be re-established.
*/
private void reevaluateDataConnections() {
for (DataConnection dataConnection : mDataConnections.values()) {
dataConnection.reevaluateRestrictedState();
}
}
第二个(重要)
setupDataOnAllConnectableApns
遍历所有的apn类型,然后一次调用setupDataOnConnectableApn方法
protected void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
if (VDBG) log("setupDataOnAllConnectableApns: " + reason);
if (DBG) {
StringBuilder sb = new StringBuilder(120);
for (ApnContext apnContext : mPrioritySortedApnContexts) {
sb.append(apnContext.getApnType());
sb.append(":[state=");
sb.append(apnContext.getState());
sb.append(",enabled=");
sb.append(apnContext.isEnabled());
sb.append("] ");
}
log("setupDataOnAllConnectableApns: " + reason + " " + sb);
}
for (ApnContext apnContext : mPrioritySortedApnContexts) {//遍历
setupDataOnConnectableApn(apnContext, reason, retryFailures);//1
}
}
其实际log大概是
D DCT-C-0 : setupDataOnAllConnectableApns: nwTypeChanged mcx:[state=IDLE,enabled=false] xcap:[state=IDLE,enabled=false] hipri:[state=IDLE,enabled=false] fota:[state=IDLE,enabled=false] ims:[state=IDLE,enabled=false] cbs:[state=IDLE,enabled=false] ia:[state=IDLE,enabled=false] emergency:[state=IDLE,enabled=false] unisoc1:[state=IDLE,enabled=false] mms:[state=IDLE,enabled=false] unisoc2:[state=IDLE,enabled=false] supl:[state=IDLE,enabled=false] unisoc3:[state=IDLE,enabled=false] dun:[state=IDLE,enabled=false] unisoc4:[state=IDLE,enabled=false] unisoc5:[state=IDLE,enabled=false] default:[state=CONNECTED,enabled=true]
可以看到我这只有默认是打开的 也就是普通消费者用三大运营商上网
第三个cleanUpAllConnectionsInternal
通过将APN上下文从数据连接中分离来清理所有数据连接
/**
* Clean up all data connections by detaching the APN contexts from the data connections, which
* eventually tearing down all data connections after all APN contexts are detached from the
* data connections.
*
* @param detach {@code true} if detaching APN context from the underlying data connection (when
* no other APN context is attached to the data connection, the data connection will be torn
* down.) {@code false} to only reset the data connection's state machine.
*
* @param reason reason for the clean up.
* @return boolean - true if we did cleanup any connections, false if they
* were already all disconnected.
*/
private boolean cleanUpAllConnectionsInternal(boolean detach, String reason) {
if (DBG) log("cleanUpAllConnectionsInternal: detach=" + detach + " reason=" + reason);
boolean didDisconnect = false;
boolean disableMeteredOnly = false;
// reasons that only metered apn will be torn down
if (!TextUtils.isEmpty(reason)) {
disableMeteredOnly = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED) ||
reason.equals(Phone.REASON_ROAMING_ON) ||
reason.equals(Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
}
for (ApnContext apnContext : mApnContexts.values()) {
// Exclude the IMS APN from single data connection case.
if (reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION)
&& apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)) {
continue;
}
if (shouldCleanUpConnection(apnContext, disableMeteredOnly,
reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION))) {
// TODO - only do cleanup if not disconnected
if (apnContext.isDisconnected() == false) didDisconnect = true; //如果已经;断联,直接清除
apnContext.setReason(reason);
cleanUpConnectionInternal(detach, RELEASE_TYPE_DETACH, apnContext);//执行清除方法
} else if (DBG) {
log("cleanUpAllConnectionsInternal: APN type " + apnContext.getApnType()
+ " shouldn't be cleaned up.");
}
}
stopNetStatPoll();// 停止网络统计轮询
stopDataStallAlarm();//停止数据暂停报警
// TODO: Do we need mRequestedApnType?
mRequestedApnType = ApnSetting.TYPE_DEFAULT;
log("cleanUpAllConnectionsInternal: mDisconnectPendingCount = "
+ mDisconnectPendingCount);
if (detach && mDisconnectPendingCount == 0) {
notifyAllDataDisconnected();//通知断连监听,phone进程有监听,SST也有监听
}
return didDisconnect;
}
接下来继续讲述开启流量
走到setupDataOnConnectableApn方法
protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
RetryFailures retryFailures) {
if (VDBG) log("setupDataOnAllConnectableApns: apnContext " + apnContext);
if (apnContext.getState() == DctConstants.State.FAILED
|| apnContext.getState() == DctConstants.State.RETRYING) {
if (retryFailures == RetryFailures.ALWAYS) {//如果总是打开失败,则放弃掉
apnContext.releaseDataConnection(reason);
} else if (!apnContext.isConcurrentVoiceAndDataAllowed() //该apn连接不支持语音和数据并发,也释放掉
&& mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
// RetryFailures.ONLY_ON_CHANGE - check if voice concurrency has changed
apnContext.releaseDataConnection(reason);
}
}
if (apnContext.isConnectable()) {//如果apn可供连接
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
trySetupData(apnContext, REQUEST_TYPE_NORMAL);//1
}
}
trySetupData
private boolean trySetupData(ApnContext apnContext, @RequestNetworkType int requestType) {
if (mPhone.getSimulatedRadioControl() != null) {//模拟器的话直接返回连接成功
// Assume data is connected on the simulator
// FIXME this can be improved
apnContext.setState(DctConstants.State.CONNECTED);
mPhone.notifyDataConnection(apnContext.getApnType());
log("trySetupData: X We're on the simulator; assuming connected retValue=true");
return true;
}
DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
boolean isDataAllowed = isDataAllowed(apnContext, requestType, dataConnectionReasons);//检查是否允许为给定的APN类型建立数据连接。
String logStr = "trySetupData for APN type " + apnContext.getApnType() + ", reason: "
+ apnContext.getReason() + ", requestType=" + requestTypeToString(requestType)
+ ". " + dataConnectionReasons.toString();
if (DBG) log(logStr);
apnContext.requestLog(logStr);
if (isDataAllowed) {
if (apnContext.getState() == DctConstants.State.FAILED) {//如果是失败,置成idle
String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
if (DBG) log(str);
apnContext.requestLog(str);
apnContext.setState(DctConstants.State.IDLE);
}
int radioTech = getDataRat();//获取网络类型
if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN && mPhone.getServiceState()
.getState() == ServiceState.STATE_IN_SERVICE) {
radioTech = getVoiceRat();
}
log("service state=" + mPhone.getServiceState());
apnContext.setConcurrentVoiceAndDataAllowed(mPhone.getServiceStateTracker()
.isConcurrentVoiceAndDataAllowed());
if (apnContext.getState() == DctConstants.State.IDLE) {//如果是空闲状态
ArrayList<ApnSetting> waitingApns =
buildWaitingApns(apnContext.getApnType(), radioTech);//建立用于创建PDP的APN列表
if (waitingApns.isEmpty()) {//如果没有建立起来直接返回失败
ApnSetting apn = apnContext != null ? apnContext.getApnSetting() : null;
mPhone.notifyDataConnectionFailed(apnContext.getApnType(),
apn != null ? apn.getApnName() : null,
DataFailCause.MISSING_UNKNOWN_APN);
String str = "trySetupData: X No APN found retValue=false";
if (DBG) log(str);
apnContext.requestLog(str);
return false;
} else {
apnContext.setWaitingApns(waitingApns);
if (DBG) {
log ("trySetupData: Create from mAllApnSettings : "
+ apnListToString(mAllApnSettings));
}
}
}
boolean retValue = setupData(apnContext, radioTech, requestType);//1
if (DBG) log("trySetupData: X retValue=" + retValue);
return retValue;
} else {
StringBuilder str = new StringBuilder();
str.append("trySetupData failed. apnContext = [type=" + apnContext.getApnType()
+ ", mState=" + apnContext.getState() + ", apnEnabled="
+ apnContext.isEnabled() + ", mDependencyMet="
+ apnContext.isDependencyMet() + "] ");
if (!mDataEnabledSettings.isDataEnabled()) {
str.append("isDataEnabled() = false. " + mDataEnabledSettings);
}
// If this is a data retry, we should set the APN state to FAILED so it won't stay
// in RETRYING forever.
if (apnContext.getState() == DctConstants.State.RETRYING) {//保证其不会永远处于重试状态
apnContext.setState(DctConstants.State.FAILED);
str.append(" Stop retrying.");
}
if (DBG) log(str.toString());
apnContext.requestLog(str.toString());
return false;
}
}
这其中有一个重要函数带领走向下一步
setupData
/**
* Setup a data connection based on given APN type.
*
* @param apnContext APN context
* @param radioTech RAT of the data connection
* @param requestType Data request type
* @return True if successful, otherwise false.
*/
private boolean setupData(ApnContext apnContext, int radioTech,
@RequestNetworkType int requestType) {
if (DBG) {
log("setupData: apnContext=" + apnContext + ", requestType="
+ requestTypeToString(requestType));
}
apnContext.requestLog("setupData. requestType=" + requestTypeToString(requestType));
ApnSetting apnSetting;
DataConnection dataConnection = null;
apnSetting = apnContext.getNextApnSetting();
if (apnSetting == null) {
if (DBG) log("setupData: return for no apn found!");
return false;
}
// profile id is only meaningful when the profile is persistent on the modem.
//只有当配置文件在调制解调器上持久存在时,配置文件id才有意义。
int profileId = DATA_PROFILE_INVALID;
if (apnSetting.isPersistent()) {
profileId = apnSetting.getProfileId();
if (profileId == DATA_PROFILE_DEFAULT) {
profileId = getApnProfileID(apnContext.getApnType());
}
}
// On CDMA, if we're explicitly asking for DUN, we need have
// a dun-profiled connection so we can't share an existing one
// On GSM/LTE we can share existing apn connections provided they support
// this type.
if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DUN)//cdma相关,现在基本都是5g了
|| ServiceState.isGsm(getDataRat())) {
dataConnection = checkForCompatibleDataConnection(apnContext);
if (dataConnection != null) {
// Get the apn setting used by the data connection
ApnSetting dataConnectionApnSetting = dataConnection.getApnSetting();
if (dataConnectionApnSetting != null) {
// Setting is good, so use it.
apnSetting = dataConnectionApnSetting;
}
}
}
if (dataConnection == null) {
if (isOnlySingleDcAllowed(radioTech)) {
if (isHigherPriorityApnContextActive(apnContext)) {//检查优先级,如果不够,直接返回
if (DBG) {
log("setupData: Higher priority ApnContext active. Ignoring call");
}
return false;
}
// Should not start cleanUp if the setupData is for IMS APN
// or retry of same APN(State==RETRYING).
if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)
&& (apnContext.getState() != DctConstants.State.RETRYING)) {
// Only lower priority calls left. Disconnect them all in this single PDP case
// so that we can bring up the requested higher priority call (once we receive
// response for deactivate request for the calls we are about to disconnect
if (cleanUpAllConnectionsInternal(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
// If any call actually requested to be disconnected, means we can't
// bring up this connection yet as we need to wait for those data calls
// to be disconnected.
if (DBG) log("setupData: Some calls are disconnecting first."
+ " Wait and retry");
return false;
}
}
// No other calls are active, so proceed
if (DBG) log("setupData: Single pdp. Continue setting up data call.");
}
dataConnection = findFreeDataConnection();//1,给dataConnection变量赋值,主要是控制数据连接的各种状态
if (dataConnection == null) {//如果没有现成的就新建一个
dataConnection = createDataConnection();
}
if (dataConnection == null) {//都不行就直接返回
if (DBG) log("setupData: No free DataConnection and couldn't create one, WEIRD");
return false;
}
}
final int generation = apnContext.incAndGetConnectionGeneration();//数量增加并获取对象
if (DBG) {
log("setupData: dc=" + dataConnection + " apnSetting=" + apnSetting + " gen#="
+ generation);
}
configQos();
configFallbackCause();
apnContext.setDataConnection(dataConnection);//把建好的变量设置给apnContext对象
apnContext.setApnSetting(apnSetting);
apnContext.setState(DctConstants.State.CONNECTING);//状态变化
mPhone.notifyDataConnection(apnContext.getApnType());//通知上层数据连接
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);
if (DBG) {
if (isPreferredApn) {
log("setupData: initing! isPreferredApn=" + isPreferredApn
+ ", apnSetting={" + apnSetting.toString() + "}");
} else {
String preferredApnStr = preferredApn == null ? "null" : preferredApn.toString();
log("setupData: initing! isPreferredApn=" + isPreferredApn
+ ", apnSetting={" + apnSetting + "}"
+ ", preferredApn={" + preferredApnStr + "}");
}
}
return true;
}
讲一下刚才说的findFreeDataConnection函数
每个数据连接的背后都会有一个DataConnection对象与之对应
这个函数主要是看一下当前系统有没有已经创建好的相同类型的,就可以拿来直接用,不用再创建了
private DataConnection findFreeDataConnection() {
for (DataConnection dataConnection : mDataConnections.values()) {//遍历mDataConnections
boolean inUse = false;
for (ApnContext apnContext : mApnContexts.values()) {
if (apnContext.getDataConnection() == dataConnection) {//有人用的话就标记好
inUse = true;
break;
}
}
if (!inUse) {
if (DBG) {
log("findFreeDataConnection: found free DataConnection=" + dataConnection);
}
return dataConnection;//直接返回
}
}
log("findFreeDataConnection: NO free DataConnection");
return null;
}
总结
上述就是DcTracker主要的逻辑处理和重要函数,在apn开启和关闭中起着重要的作用
接下来的任务就交给了DataConnection这个类来处理,后续会分析
该类职责更加明确清楚,任务更加聚焦,同样是一个非常重要的类,值得我们去研究。