从打开数据开关到手机桌面显示LTE,整个的流程是怎么样的?以此疑问为主线,开始接下来的分析。
路由配置信息的获取有多种方式:wifi,mobile data,Tethering,VPN。这里主要分析由mobile data获取路由配置信息的过程,结合ConnectivityService类,其大致的类图与流程图如下:
本篇博客只介绍DcTracker,DataConnection,GsmCdmaPhone,RIL层的相关内容,其他的ConnectivityService,NetworkFactory,NetworkMonitor等内容在下一篇博客继续分析。
DcTracker与DataConnection的分工不同,DcTracker主要处理APN参数,数据重连机制,通知上层等一些琐碎的工作;DataConnection则负责与RIL层通讯,它本身就是一个状态机,用于处理网络请求过程中产生的各种状态,网络请求成功后生成NetworkAgent对象参与ConnectivityService中的网络评分工作。
本篇分为两个小节来分析:
1) DcTracker
2) DataConnection
DcTracker主要负责处理APN参数,数据重连机制,通知上层等工作。 这里分为两个小节分析:
1. 处理apn参数:
1). 初始化ApnContext
2). 激活ApnContext
3). 筛选apn
4). 发起请求
2. 请求网络的结果处理:
1). 网络请求成功
2). 数据重连
2.1.1 初始化ApnContext
ApnContext用来建立指定apn类型的apn上下文,ApnContext中定义了此apn类型所需的重要属性。
/**
* Maintain the Apn context
*/
public class ApnContext {
...
//apn的类型
private final String mApnType;
//网络连接类型的优先级
public final int priority;
private final RetryManager mRetryManager;
private final DcTracker mDcTracker;
private int mRefCount = 0;
//此apn类型的apn参数
private ApnSetting mApnSetting;
String mReason;
//ApnContext所处的状态
private DctConstants.State mState;
...
}
apn的类型定义在PhoneConstants.java中:
public class PhoneConstants {
...
/**
* APN types for data connections. These are usage categories for an APN
* entry. One APN entry may support multiple APN types, eg, a single APN
* may service regular internet traffic ("default") as well as MMS-specific
* connections.
* APN_TYPE_ALL is a special type to indicate that this APN entry can
* service all data connections.
*/
public static final String APN_TYPE_ALL = "*";
/** APN type for default data traffic */
public static final String APN_TYPE_DEFAULT = "default";
/** APN type for MMS traffic */
public static final String APN_TYPE_MMS = "mms";
/** APN type for SUPL assisted GPS */
public static final String APN_TYPE_SUPL = "supl";
/** APN type for DUN traffic */
public static final String APN_TYPE_DUN = "dun";
/** APN type for HiPri traffic */
public static final String APN_TYPE_HIPRI = "hipri";
/** APN type for FOTA */
public static final String APN_TYPE_FOTA = "fota";
/** APN type for IMS */
public static final String APN_TYPE_IMS = "ims";
/** APN type for CBS */
public static final String APN_TYPE_CBS = "cbs";
/** APN type for IA Initial Attach APN */
public static final String APN_TYPE_IA = "ia";
/** APN type for Emergency PDN. This is not an IA apn, but is used
* for access to carrier services in an emergency call situation. */
public static final String APN_TYPE_EMERGENCY = "emergency";
/** Array of all APN types */
public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
APN_TYPE_MMS,
APN_TYPE_SUPL,
APN_TYPE_DUN,
APN_TYPE_HIPRI,
APN_TYPE_FOTA,
APN_TYPE_IMS,
APN_TYPE_CBS,
APN_TYPE_IA,
APN_TYPE_EMERGENCY
};
...
}
apn类型的优先级读取自文件frameworks\base\core\res\res\values\config.xml
- "wifi,1,1,2,-1,true"
- "tedongle,49,49,1,-1,true"
- "mobile,0,0,0,-1,true"
- "mobile_mms,2,0,2,300000,true"
- "mobile_supl,3,0,2,300000,true"
- "mobile_dun,4,0,3,300000,true"
- "mobile_hipri,5,0,3,300000,true"
- "bluetooth,7,7,0,-1,true"
- "mobile_fota,10,0,2,300000,true"
- "mobile_ims,11,0,-1,-1,true"
...
ApnContext的状态
public class DctConstants {
/**
* IDLE: ready to start data connection setup, default state
* CONNECTING: state of issued startPppd() but not finish yet
* SCANNING: data connection fails with one apn but other apns are available
* ready to start data connection on other apns (before INITING)
* CONNECTED: IP connection is setup
* DISCONNECTING: Connection.disconnect() has been called, but PDP
* context is not yet deactivated
* FAILED: data connection fail for all apns settings
* RETRYING: data connection failed but we're going to retry.
*
* getDataConnectionState() maps State to DataState
* FAILED or IDLE : DISCONNECTED
* RETRYING or CONNECTING or SCANNING: CONNECTING
* CONNECTED : CONNECTED or DISCONNECTING
*/
public enum State {
IDLE,
CONNECTING,
SCANNING,
CONNECTED,
DISCONNECTING,
FAILED,
// After moving retry manager to ApnContext, we'll never enter this state!
// Todo: Remove this state and other places that use this state and then
// rename SCANNING to RETRYING.
RETRYING
}
...
}
ApnContext的初始化:因为apn的类型有很多种,任意一种都可能使用到,因此需要初始化所有apn类型的ApnContext。在DcTracker对象创建的时候初始化
public class DcTracker extends Handler {
...
//***** Constructor
public DcTracker(Phone phone, int transportType) {
...
initApnContexts();
...
}
...
private void initApnContexts() {
log("initApnContexts: E");
// Load device network attributes from resources
String[] networkConfigStrings = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String networkConfigString : networkConfigStrings) {
NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
ApnContext apnContext = null;
switch (networkConfig.type) {
case ConnectivityManager.TYPE_MOBILE:
apnContext = addApnContext(PhoneConstants.APN_TYPE_DEFAULT, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_MMS:
apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_SUPL:
apnContext = addApnContext(PhoneConstants.APN_TYPE_SUPL, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_DUN:
apnContext = addApnContext(PhoneConstants.APN_TYPE_DUN, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_HIPRI:
apnContext = addApnContext(PhoneConstants.APN_TYPE_HIPRI, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_FOTA:
apnContext = addApnContext(PhoneConstants.APN_TYPE_FOTA, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_IMS:
apnContext = addApnContext(PhoneConstants.APN_TYPE_IMS, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_CBS:
apnContext = addApnContext(PhoneConstants.APN_TYPE_CBS, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_IA:
apnContext = addApnContext(PhoneConstants.APN_TYPE_IA, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
apnContext = addApnContext(PhoneConstants.APN_TYPE_EMERGENCY, networkConfig);
break;
default:
log("initApnContexts: skipping unknown type=" + networkConfig.type);
continue;
}
log("initApnContexts: apnContext=" + apnContext);
}
if (VDBG) log("initApnContexts: X mApnContexts=" + mApnContexts);
}
...
private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
ApnContext apnContext = new ApnContext(mPhone, type, LOG_TAG, networkConfig, this);
mApnContexts.put(type, apnContext);
mApnContextsById.put(ApnContext.apnIdForApnName(type), apnContext);
mPrioritySortedApnContexts.add(apnContext);
return apnContext;
}
...
}
2.1.2 激活ApnContext
激活ApnContext就是设置apnContext.setEnabled(true),整体流程如下:
①.激活默认apn类型的ApnContext
默认的apn类型是default。当SUBSCRIPTION_CHANGED状态变化时,激活默认的ApnContext。SUBSCRIPTION_CHANGED状态变化包括:插拔SIM卡,切换默认SIM卡。且ApnContext跟卡槽无关,默认apn类型的ApnContext被激活后,无论卡1还是卡2都可使用此默认类型的apn,无须再次激活。参见上图,PhoneSwitcher和TelephonyNetworkFactory都监听了SUBSCRIPTION_CHANGED状态的变化。
/**
* Utility singleton to monitor subscription changes and incoming NetworkRequests
* and determine which phone/phones are active.
*
* Manages the ALLOW_DATA calls to modems and notifies phones about changes to
* the active phones. Note we don't wait for data attach (which may not happen anyway).
*/
public class PhoneSwitcher extends Handler {
...
private final static int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101;
private final static int EVENT_SUBSCRIPTION_CHANGED = 102;
private final static int EVENT_REQUEST_NETWORK = 103;
private final static int EVENT_RELEASE_NETWORK = 104;
private final static int EVENT_EMERGENCY_TOGGLE = 105;
private final static int EVENT_RESEND_DATA_ALLOWED = 106;
...
public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
CommandsInterface[] cis, Phone[] phones) {
...
mContext.registerReceiver(mDefaultDataChangedReceiver, new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
...
}
...
}
public class TelephonyNetworkFactory extends NetworkFactory {
...
private static final int EVENT_ACTIVE_PHONE_SWITCH = 1;
private static final int EVENT_SUBSCRIPTION_CHANGED = 2;
private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 3;
private static final int EVENT_NETWORK_REQUEST = 4;
private static final int EVENT_NETWORK_RELEASE = 5;
...
public TelephonyNetworkFactory(PhoneSwitcher phoneSwitcher,
SubscriptionController subscriptionController, SubscriptionMonitor subscriptionMonitor,
Looper looper, Context context, int phoneId, DcTracker dcTracker) {
...
mSubscriptionMonitor.registerForSubscriptionChanged(mPhoneId, mInternalHandler,
EVENT_SUBSCRIPTION_CHANGED, null);
...
}
...
}
②.激活其他apn类型的ApnContext
主动发起网络请求needNetworkFor()时,激活其他类型的ApnContext。参见上图,PhoneSwitcher接收到EVENT_REQUEST_NETWORK和EVENT_RELEASE_NETWORK事件消息后会调用onEvaluate(),如果满足条件,将会激活特定apn类型的ApnContext。同理,如果有新的网络请求和网络评分变化的话,也会引发TelephonyNetworkFactory激活特定apn类型的ApnContext。关键log如下:
public class DcTracker extends Handler {
...
private void onEnableApn(int apnId, int enabled) {
ApnContext apnContext = mApnContextsById.get(apnId);
if (apnContext == null) {
loge("onEnableApn(" + apnId + ", " + enabled + "): NO ApnContext");
return;
}
// TODO change our retry manager to use the appropriate numbers for the new APN
if (DBG) log("onEnableApn: apnContext=" + apnContext + " call applyNewState");
applyNewState(apnContext, enabled == DctConstants.ENABLED, apnContext.getDependencyMet());
if ((enabled == DctConstants.DISABLED) &&
isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology()) &&
!isHigherPriorityApnContextActive(apnContext)) {
if(DBG) log("onEnableApn: isOnlySingleDcAllowed true & higher priority APN disabled");
// If the highest priority APN is disabled and only single
// data call is allowed, try to setup data call on other connectable APN.
setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION);
}
}
...
}
//default apn
04-17 14:09:53.985 2349 2349 D DCT-C: [0]onEnableApn: apnType=default, request type=NORMAL
//ims apn
04-17 14:10:01.354 2349 2349 D DCT-C: [0]onEnableApn: apnType=ims, request type=NORMAL
2.1.3 筛选apn
由于apns-conf.xml中我们配置了很多apn参数,同一个apn 类型可能有多个apn参数。以中国联通default类型为例:
apn参数列表中用户可以指定某个apn参数参与请求网络,这就产生了优先级。因此,buildWaitingApns()的作用就是把指定类型的所有apn参数都收集起来用ArrayList来保存,用户指定的apn参数放在首位,保证优先使用此apn参与请求网络过程。
public class DcTracker extends Handler {
...
mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
//注册监听sim卡的相关信息
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
...
private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
new OnSubscriptionsChangedListener() {
public final AtomicInteger mPreviousSubId =
new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
/**
* Callback invoked when there is any change to any SubscriptionInfo. Typically
* this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
*/
@Override
public void onSubscriptionsChanged() {
if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
// Set the network type, in case the radio does not restore it.
int subId = mPhone.getSubId();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
registerSettingsObserver();
}
if (mPreviousSubId.getAndSet(subId) != subId &&
SubscriptionManager.isValidSubscriptionId(subId)) {
onRecordsLoadedOrSubIdChanged();
}
}
};
...
private void onRecordsLoadedOrSubIdChanged() {
if (DBG) log("onRecordsLoadedOrSubIdChanged: createAllApnList");
mAutoAttachOnCreationConfig = mPhone.getContext().getResources()
.getBoolean(com.android.internal.R.bool.config_auto_attach_data_on_creation);
createAllApnList();
setInitialAttachApn();
if (mPhone.mCi.getRadioState().isOn()) {
if (DBG) log("onRecordsLoadedOrSubIdChanged: notifying data availability");
notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
}
setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);
}
...
private void setupDataOnConnectableApns(String reason) {
setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);
}
private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
if (VDBG) log("setupDataOnConnectableApns: " + reason);
...
for (ApnContext apnContext : mPrioritySortedApnContexts) {
//注释1
if (apnContext.getState() == DctConstants.State.FAILED
|| apnContext.getState() == DctConstants.State.SCANNING) {
if (retryFailures == RetryFailures.ALWAYS) {
apnContext.releaseDataConnection(reason);
} else if (apnContext.isConcurrentVoiceAndDataAllowed() == false &&
mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
// RetryFailures.ONLY_ON_CHANGE - check if voice concurrency has changed
apnContext.releaseDataConnection(reason);
}
}
//注释2
if (apnContext.isConnectable()) {
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
trySetupData(apnContext);
}
}
}
...
private boolean trySetupData(ApnContext apnContext) {
...
DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
//isDataAllowed sim卡加载、数据开关、默认数据选择、漫游、PS域是否注册成功
boolean isDataAllowed = isDataAllowed(apnContext, dataConnectionReasons);
...
apnContext.requestLog(logStr);
if (isDataAllowed) {
...
if (apnContext.getState() == DctConstants.State.IDLE) {
//首次使用此apn类型的apnContext,需要加载waitingApns
ArrayList waitingApns =
buildWaitingApns(apnContext.getApnType(), radioTech);
if (waitingApns.isEmpty()) {
notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
String str = "trySetupData: X No APN found retValue=false";
if (DBG) log(str);
apnContext.requestLog(str);
return false;
} else {
apnContext.setWaitingApns(waitingApns);
...
}
}
//发起请求
boolean retValue = setupData(apnContext, radioTech, dataConnectionReasons.contains(
DataAllowedReasonType.UNMETERED_APN));
notifyOffApnsOfAvailability(apnContext.getReason());
return retValue;
} else {
...
}
}
...
/*
* Build a list of APNs to be used to create PDP's.
*
* @param requestedApnType
* @return waitingApns list to be used to create PDP
* error when waitingApns.isEmpty()
*/
private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
ArrayList apnList = new ArrayList();
if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
ArrayList dunApns = fetchDunApns();
if (dunApns.size() > 0) {
for (ApnSetting dun : dunApns) {
apnList.add(dun);
}
return sortApnListByPreferred(apnList);
}
}
IccRecords r = mIccRecords.get();
String operator = (r != null) ? r.getOperatorNumeric() : "";
// This is a workaround for a bug (7305641) where we don't failover to other
// suitable APNs if our preferred APN fails. On prepaid ATT sims we need to
// failover to a provisioning APN, but once we've used their default data
// connection we are locked to it for life. This change allows ATT devices
// to say they don't want to use preferred at all.
boolean usePreferred = true;
try {
usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
internal.R.bool.config_dontPreferApn);
} catch (Resources.NotFoundException e) {
usePreferred = true;
}
if (usePreferred) {
mPreferredApn = getPreferredApn();
}
...
if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
mPreferredApn.canHandleType(requestedApnType)) {
if (mPreferredApn.numeric.equals(operator)) {
if (ServiceState.bitmaskHasTech(mPreferredApn.networkTypeBitmask,
ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
apnList.add(mPreferredApn);
apnList = sortApnListByPreferred(apnList);
if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
return apnList;
} else {
setPreferredApn(-1);
mPreferredApn = null;
}
} else {
setPreferredApn(-1);
mPreferredApn = null;
}
}
if (mAllApnSettings != null) {
for (ApnSetting apn : mAllApnSettings) {
if (apn.canHandleType(requestedApnType)) {
if (ServiceState.bitmaskHasTech(apn.networkTypeBitmask,
ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
apnList.add(apn);
} else {
...
} else if (DBG) {
...
}
}
} else {
...
}
apnList = sortApnListByPreferred(apnList);
return apnList;
}
...
}
public class ApnContext {
...
/**
* Check if the data call is in the state which allow connecting.
* @return True if allowed, otherwise false.
*/
public boolean isConnectable() {
return isReady() && ((mState == DctConstants.State.IDLE)
|| (mState == DctConstants.State.SCANNING)
|| (mState == DctConstants.State.RETRYING)
|| (mState == DctConstants.State.FAILED));
}
...
}
筛选apn主要由两个方法完成:setupDataOnConnectableApns()和trySetupData() 。
setupDataOnConnectableApns 为筛选apn做准备工作:循环所有apn类型的ApnContext
注释1:如果循环到的ApnContext状态为FAILED和SCANNING,说明此apn类型的ApnContext先前使用过,且处于以下状态:一直尝试重连、数据不可用状态。重新加载waitingApns并与此ApnContext保存的waitingApns对比,如果不相同,说明apn发生了变化,执行releaseDataConnection()释放掉此ApnContext的连接。ApnContext的状态重新设置为Idle。 首次使用此apn类型的ApnContext,waitingApns为null
注释2:ApnContext必须为激活状态且处于IDLE,SCANNING,RETRYING,FAILED状态才可以trySetupData。
trySetupData 筛选出合适的apn和做条件判断
2.1.4 发起请求
/**
* Setup a data connection based on given APN type.
*
* @param apnContext APN context
* @param radioTech RAT of the data connection
* @param unmeteredUseOnly True if this data connection should be only used for unmetered
* purposes only.
* @return True if successful, otherwise false.
*/
private boolean setupData(ApnContext apnContext, int radioTech, boolean unmeteredUseOnly) {
...
//创建DataConnection对象
dcac = findFreeDataConnection();
...
apnContext.setDataConnectionAc(dcac);
apnContext.setApnSetting(apnSetting);
//更新ApnContext的状态为CONNECTIN
apnContext.setState(DctConstants.State.CONNECTING);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
Message msg = obtainMessage();
//DataConnection处理拨号过程结束,会通过EVENT_DATA_SETUP_COMPLETE事件通知DcTracker
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = new Pair(apnContext, generation);
//调用bringUp()发起下一步的拨号动作
dcac.bringUp(apnContext, profileId, radioTech, unmeteredUseOnly, msg, generation);
return true;
}
2.2.1 网络请求成功
如果DataConnection处理网络请求过程结束,会通过EVENT_DATA_SETUP_COMPLETE事件通知DcTracker并调用onDataSetupComplete
/**
* A SETUP (aka bringUp) has completed, possibly with an error. If
* there is an error this method will call {@link #onDataSetupCompleteError}.
*/
private void onDataSetupComplete(AsyncResult ar) {
DcFailCause cause = DcFailCause.UNKNOWN;
boolean handleError = false;
ApnContext apnContext = getValidApnContext(ar, "onDataSetupComplete");
if (apnContext == null) return;
if (ar.exception == null) {
DcAsyncChannel dcac = apnContext.getDcAc();
...
if (dcac == null) {
log("onDataSetupComplete: no connection to DC, handle as error");
cause = DcFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
handleError = true;
} else {
ApnSetting apn = apnContext.getApnSetting();
if (apn != null && apn.proxy != null && apn.proxy.length() != 0) {
try {
String port = apn.port;
//拨号成功,通过DcAsyncChannel设置proxy,如果apn参数中proxy为空,默认为8080
if (TextUtils.isEmpty(port)) port = "8080";
ProxyInfo proxy = new ProxyInfo(apn.proxy,
Integer.parseInt(port), null);
dcac.setLinkPropertiesHttpProxySync(proxy);
} catch (NumberFormatException e) {
...
}
}
// everything is setup
if (TextUtils.equals(apnContext.getApnType(), PhoneConstants.APN_TYPE_DEFAULT)) {
try {
SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "true");
} catch (RuntimeException ex) {
...
}
if (mCanSetPreferApn && mPreferredApn == null) {
if (mPreferredApn != null) {
//设置默认的apn参数
setPreferredApn(mPreferredApn.id);
}
}
} else {
...
}
// A connection is setup
//设置ApnContext的状态为CONNECTED
apnContext.setState(DctConstants.State.CONNECTED);
...
if ((!isProvApn) || mIsProvisioning) {
// Hide any provisioning notification.
cm.setProvisioningNotificationVisible(false, ConnectivityManager.TYPE_MOBILE,
mProvisionActionName);
// Complete the connection normally notifying the world we're connected.
// We do this if this isn't a special provisioning apn or if we've been
// told its time to provision.
//调用completeConnection()实现:通知Phone拨号成功
completeConnection(apnContext);
} else {
...
}
...
}
} else {
...
}
...
}
周期读取底层文件,判断终端是否有接收和发送数据包,启动一个线程,更新UI界面,主要是上下行图标,以及流量结算页面。
private void completeConnection(ApnContext apnContext) {
...
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
startNetStatPoll();
startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
}
2.2.2 数据重连
当网络请求发生异常时,framework层会根据异常的类型去重新建立数据连接。
目前的数据重连主要有四个方面:
①.SETUP_DATA_CALL成功,但是携带返回的数据异常。
②.EVENT_DISCONNECT_DONE数据连接断开后,尝试重新连接。
③.隔一段时间收不到数据包后触发doRecover机制
④.pdp链路中断
以上的数据重连,最后都是使用了startAlarmForReconnect()方法,借助于定时器,间隔一定的时间重新发起连接。
DcTracker.java
private void startAlarmForReconnect(long delay, ApnContext apnContext) {
String apnType = apnContext.getApnType();
Intent intent = new Intent(INTENT_RECONNECT_ALARM + "." + apnType);
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// Get current sub id.
int subId = mPhone.getSubId();
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
PendingIntent alarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
apnContext.setReconnectIntent(alarmIntent);
// Use the exact timer instead of the inexact one to provide better user experience.
// In some extreme cases, we saw the retry was delayed for few minutes.
// Note that if the stated trigger time is in the past, the alarm will be triggered
// immediately.
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delay, alarmIntent);
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
// TODO: Evaluate hooking this up with DeviceStateMonitor
if (DBG) log("screen on");
mIsScreenOn = true;
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (DBG) log("screen off");
mIsScreenOn = false;
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
} else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
if (DBG) log("Reconnect alarm. Previous state was " + mState);
onActionIntentReconnectAlarm(intent);
} else if (action.equals(INTENT_DATA_STALL_ALARM)) {
if (DBG) log("Data stall alarm");
onActionIntentDataStallAlarm(intent);
} else if (action.equals(INTENT_PROVISIONING_APN_ALARM)) {
if (DBG) log("Provisioning apn alarm");
onActionIntentProvisioningApnAlarm(intent);
} else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
setDefaultDataRoamingEnabled();
}
} else {
if (DBG) log("onReceive: Unknown action=" + action);
}
}
};
private void onActionIntentReconnectAlarm(Intent intent) {
Message msg = obtainMessage(DctConstants.EVENT_DATA_RECONNECT);
msg.setData(intent.getExtras());
sendMessage(msg);
}
①.SETUP_DATA_CALL成功,但是携带返回的数据异常。
如果返回的结果ar.exception为空,且DataConnection的对象dcac 为空,说明DcTracker与DataConnection的交互出了问题,设置handleError为true,启动重连机制。如果ar.exception不为空,说明请求的结果有误,通知上层并标记请求失败的apn,启动重连机制。
/**
* A SETUP (aka bringUp) has completed, possibly with an error. If
* there is an error this method will call {@link #onDataSetupCompleteError}.
*/
private void onDataSetupComplete(AsyncResult ar) {
DcFailCause cause = DcFailCause.UNKNOWN;
boolean handleError = false;
ApnContext apnContext = getValidApnContext(ar, "onDataSetupComplete");
if (apnContext == null) return;
if (ar.exception == null) {
DcAsyncChannel dcac = apnContext.getDcAc();
...
if (dcac == null) {
log("onDataSetupComplete: no connection to DC, handle as error");
cause = DcFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
handleError = true;
} else {
...
}
} else {
...
//通知上层SETUP_DATA_CALL失败
ApnSetting apn = apnContext.getApnSetting();
mPhone.notifyPreciseDataConnectionFailed(apnContext.getReason(),
apnContext.getApnType(), apn != null ? apn.apn : "unknown", cause.toString());
// Compose broadcast intent send to the specific carrier signaling receivers
Intent intent = new Intent(TelephonyIntents
.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED);
intent.putExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY, cause.getErrorCode());
intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnContext.getApnType());
mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
//是否重新启动Radio
if (cause.isRestartRadioFail(mPhone.getContext(), mPhone.getSubId()) ||
apnContext.restartOnError(cause.getErrorCode())) {
if (DBG) log("Modem restarted.");
sendRestartRadio();
}
// If the data call failure cause is a permanent failure, we mark the APN as permanent
// failed.
//标记SETUP_DATA_CALL失败的apn
if (isPermanentFailure(cause)) {
log("cause = " + cause + ", mark apn as permanent failed. apn = " + apn);
apnContext.markApnPermanentFailed(apn);
}
handleError = true;
}
if (handleError) {
onDataSetupCompleteError(ar);
}
}
尝试使用下一个合适的apn发起重连,delay 值取决于当前waitingApns。如果已经没有合适的apn发起重连,则apnContext设置状态为FAILED并通知上层SETUP_DATA_CALL失败。
/**
* Error has occurred during the SETUP {aka bringUP} request and the DCT
* should either try the next waiting APN or start over from the
* beginning if the list is empty. Between each SETUP request there will
* be a delay defined by {@link #getApnDelay()}.
*/
private void onDataSetupCompleteError(AsyncResult ar) {
ApnContext apnContext = getValidApnContext(ar, "onDataSetupCompleteError");
if (apnContext == null) return;
long delay = apnContext.getDelayForNextApn(mFailFast);
// Check if we need to retry or not.
if (delay >= 0) {
if (DBG) log("onDataSetupCompleteError: Try next APN. delay = " + delay);
apnContext.setState(DctConstants.State.SCANNING);
// Wait a bit before trying the next APN, so that
// we're not tying up the RIL command channel
startAlarmForReconnect(delay, apnContext);
} else {
// If we are not going to retry any APN, set this APN context to failed state.
// This would be the final state of a data connection.
apnContext.setState(DctConstants.State.FAILED);
mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());
apnContext.setDataConnectionAc(null);
log("onDataSetupCompleteError: Stop retrying APNs.");
}
}
②.EVENT_DISCONNECT_DONE数据连接断开后,尝试重新连接。
/**
* Called when EVENT_DISCONNECT_DONE is received.
*/
private void onDisconnectDone(AsyncResult ar) {
ApnContext apnContext = getValidApnContext(ar, "onDisconnectDone");
if (apnContext == null) return;
if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE apnContext=" + apnContext);
apnContext.setState(DctConstants.State.IDLE);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
// if all data connection are gone, check whether Airplane mode request was
// pending.
if (isDisconnected()) {
if (mPhone.getServiceStateTracker().processPendingRadioPowerOffAfterDataOff()) {
if (DBG) log("onDisconnectDone: radio will be turned off, no retries");
// Radio will be turned off. No need to retry data setup
apnContext.setApnSetting(null);
apnContext.setDataConnectionAc(null);
// Need to notify disconnect as well, in the case of switching Airplane mode.
// Otherwise, it would cause 30s delayed to turn on Airplane mode.
if (mDisconnectPendingCount > 0) {
mDisconnectPendingCount--;
}
if (mDisconnectPendingCount == 0) {
notifyDataDisconnectComplete();
notifyAllDataDisconnected();
}
return;
}
}
// If APN is still enabled, try to bring it back up automatically
//判断是否符合重连条件,并设置重连的间隔事件
//mAttached.get() :当前仍然为attach状态
//apnContext.isReady() :apnContext仍然为激活状态
//retryAfterDisconnected(apnContext) :radio没有关闭
if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
try {
SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "false");
} catch (RuntimeException ex) {
log("Failed to set PUPPET_MASTER_RADIO_STRESS_TEST to false");
}
// Wait a bit before trying the next APN, so that
// we're not tying up the RIL command channel.
// This also helps in any external dependency to turn off the context.
if (DBG) log("onDisconnectDone: attached, ready and retry after disconnect");
//getRetryAfterDisconnectDelay() : 重连的时间间隔
long delay = apnContext.getRetryAfterDisconnectDelay();
if (delay > 0) {
// Data connection is in IDLE state, so when we reconnect later, we'll rebuild
// the waiting APN list, which will also reset/reconfigure the retry manager.
startAlarmForReconnect(delay, apnContext);
}
} else {
...
}
...
}
③.隔一段时间收不到数据包后触发doRecover机制
SETUP_DATA_CALL成功后,在DcTracker.java的completeConnection()中会启动一个线程,周期性读取底层文件,判断一个时间段内手机的收发包情况,如果隔一段时间收不到数据包,将会触发doRecover机制。
// Default for the data stall alarm while non-aggressive stall detection
private static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
// Default for the data stall alarm for aggressive stall detection
private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
private void completeConnection(ApnContext apnContext) {
...
startNetStatPoll();
startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
}
private void startDataStallAlarm(boolean suspectedStall) {
int nextAction = getRecoveryAction();
int delayInMs;
if (mDataStallDetectionEnabled && getOverallState() == DctConstants.State.CONNECTED) {
// If screen is on or data stall is currently suspected, set the alarm
// with an aggressive timeout.
if (mIsScreenOn || suspectedStall || RecoveryAction.isAggressiveRecovery(nextAction)) {
delayInMs = Settings.Global.getInt(mResolver,
Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
} else {
delayInMs = Settings.Global.getInt(mResolver,
Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
}
mDataStallAlarmTag += 1;
Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
intent.putExtra(DATA_STALL_ALARM_TAG_EXTRA, mDataStallAlarmTag);
mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
} else {
...
}
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
...
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
...
} else if (action.equals(INTENT_DATA_STALL_ALARM)) {
onActionIntentDataStallAlarm(intent);
}
...
}
};
private void onActionIntentDataStallAlarm(Intent intent) {
Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
intent.getAction());
msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
sendMessage(msg);
}
@Override
public void handleMessage (Message msg) {
switch (msg.what) {
...
case DctConstants.EVENT_DATA_STALL_ALARM:
onDataStallAlarm(msg.arg1);
break;
...
}
}
private void onDataStallAlarm(int tag) {
...
updateDataStallInfo();
int hangWatchdogTrigger = Settings.Global.getInt(mResolver,
Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
NUMBER_SENT_PACKETS_OF_HANG);
boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
if (mSentSinceLastRecv >= hangWatchdogTrigger) {
suspectedStall = DATA_STALL_SUSPECTED;
//调用doRecover
sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
} else {
...
}
startDataStallAlarm(suspectedStall);
}
正常情况下,TX/RX都不为零,AP每隔一定时间向modem发起GET_DATA_CALL_LIST查询相关信息。如果TX/RX异常,则依次循环以下的case事件(startDataStallAlarm是循环的),clean所有的旧连接,重新注册网络,重启radio,直到TX/RX正常。
private void doRecovery() {
if (getOverallState() == DctConstants.State.CONNECTED) {
// Go through a series of recovery steps, each action transitions to the next action
final int recoveryAction = getRecoveryAction();
TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
broadcastDataStallDetected(recoveryAction);
switch (recoveryAction) {
case RecoveryAction.GET_DATA_CALL_LIST:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
mSentSinceLastRecv);
if (DBG) log("doRecovery() get data call list");
mDataServiceManager.getDataCallList(obtainMessage());
putRecoveryAction(RecoveryAction.CLEANUP);
break;
case RecoveryAction.CLEANUP:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
mSentSinceLastRecv);
if (DBG) log("doRecovery() cleanup all connections");
cleanUpAllConnections(Phone.REASON_PDP_RESET);
putRecoveryAction(RecoveryAction.REREGISTER);
break;
case RecoveryAction.REREGISTER:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
mSentSinceLastRecv);
if (DBG) log("doRecovery() re-register");
mPhone.getServiceStateTracker().reRegisterNetwork(null);
putRecoveryAction(RecoveryAction.RADIO_RESTART);
break;
case RecoveryAction.RADIO_RESTART:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
mSentSinceLastRecv);
if (DBG) log("restarting radio");
restartRadio();
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
break;
default:
throw new RuntimeException("doRecovery: Invalid recoveryAction="
+ recoveryAction);
}
mSentSinceLastRecv = 0;
}
}
详细流程如下:
由此可见,这里的数据重连也是依据于onDisconnectDone()方法实现,与第二种数据重连的实现方式是一样的,只不过是其触发的原理不一样。
DcTracker与DataConnection是通过DcAsyncChannel来通讯的。DataConnection是一个状态机,新创建的对象,其状态初始化在DcDefaultState,初始化结束后运行在DcInactiveState状态,因此从DcAsyncChannel发出bringUp()拨号后,会在DcInactiveState处理DataConnection.EVENT_CONNECT事件。DataConnection各状态机的流程参见本篇第二章图,有详细的留下。