数据连接Retry机制


Platform:Android-7.1.1_r22

参考:
[Android6.0] 数据业务重试机制


data retry的触发条件:
1.SETUP_DATA_CALL返回错误
2.modem上报Data断开
3.一段时间内只有发送数据而没有接收数据

切换APN会retry吗


重试的间隔时间是在DcTracker.trySetupData中调用apnContext.setWaitingApns()进行设置的。


1.SETUP_DATA_CALL返回错误
12cbd9f3b0def3da572d221d214b614ef33.jpg
private class DcActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
......
switch (msg.what) {
......
case EVENT_SETUP_DATA_CONNECTION_DONE:
......
DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar); // 获取数据建立的结果
......
switch (result) {
......
case ERR_RilError:
......
long delay = getSuggestedRetryDelay(ar);
cp.mApnContext.setModemSuggestedDelay(delay);
......
mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
transitionTo(mInactiveState);
break;
......
}
}

DcActivatingState处理EVENT_SETUP_DATA_CONNECTION_DONE事件时,发现连接未建立成功,
就会把状态切换到InactiveState,并在其enter()方法中调用notifyConnectCompleted(),

private class DcInactiveState extends State {
// Inform all contexts we've failed connecting
public void setEnterNotificationParams(ConnectionParams cp, DcFailCause cause) {
if (VDBG) log("DcInactiveState: setEnterNotificationParams cp,cause");
mConnectionParams = cp;
mDisconnectParams = null;
mDcFailCause = cause;
}setupDataOnConnectableApns

@Override
public void enter() {
......
if (mConnectionParams != null) {
......
notifyConnectCompleted(mConnectionParams, mDcFailCause, true);
}
......
// Remove ourselves from cid mapping, before clearSettings
mDcController.removeActiveDcByCid(DataConnection.this); // 清除DataConnection

clearSettings(); // 清除设置
}

对于当前ApnContext,会发送EVENT_DATA_SETUP_COMPLETE到DcTracker,
而对于DataConnection中的其他ApnContext,则会发送EVENT_DATA_SETUP_COMPLETE_ERROR。

DcTracker.java
private void onDataSetupComplete(AsyncResult ar) {
......
if (ar.exception == null) {
......
} else {
......
mPhone.notifyPreciseDataConnectionFailed(apnContext.getReason(),
apnContext.getApnType(), apn != null ? apn.apn : "unknown", cause.toString()); // 通知监听者,好像没有

// 依赖CarrierConfigManager.KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY,默认为空
// 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);

// 判断是否需要重启modem(根据错误类型,重试次数,配置文件等)
if (cause.isRestartRadioFail() || 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.
if (isPermanentFail(cause)) {
log("cause = " + cause + ", mark apn as permanent failed. apn = " + apn);
apnContext.markApnPermanentFailed(apn);
}

handleError = true;
}

if (handleError) {
onDataSetupCompleteError(ar);
}

/* If flag is set to false after SETUP_DATA_CALL is invoked, we need
* to clean data connections.
*/
if (!mDataEnabledSettings.isInternalDataEnabled()) {
cleanUpAllConnections(Phone.REASON_DATA_DISABLED);
}

}



private void onDataSetupCompleteError(AsyncResult ar) {
// 和setupData中建立的Pair进行比较,若generation一样则返回ApnContext
ApnContext apnContext = getValidApnContext(ar, "onDataSetupCompleteError");

if (apnContext == null) return;

// 从RetryManager获取重试间隔时间
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);
// SCANNING: data connection fails with one apn but other apns are available
// ready to start data connection on other apns (before INITING)

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); // 发送retry的广播
} else {
// 不再重试,比如说mWaitingApns为空,或者modem要求不要retry等
// 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.");
}
}

接收到retry的广播:
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
@Override
public void onReceive(Context context, Intent intent) {
......
} else if (action.startsWith(INTENT_RECONNECT_ALARM)) { // 处理startAlarmForReconnect()
if (DBG) log("Reconnect alarm. Previous state was " + mState);
onActionIntentReconnectAlarm(intent);
......
}

private void onActionIntentReconnectAlarm(Intent intent) {
......
if ((apnContext != null) && (apnContext.isEnabled())) {
......
if ((apnContextState == DctConstants.State.FAILED)
|| (apnContextState == DctConstants.State.IDLE)) { // 当前状态为SCANNING
......
DcAsyncChannel dcac = apnContext.getDcAc();
if (dcac != null) {
......
dcac.tearDown(apnContext, "", null);
}
apnContext.setDataConnectionAc(null);
apnContext.setState(DctConstants.State.IDLE);
} else {
if (DBG) log("onActionIntentReconnectAlarm: keep associated");
}
// TODO: IF already associated should we send the EVENT_TRY_SETUP_DATA???
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));

apnContext.setReconnectIntent(null);
}
}

接下来又调用onTrySetupData()->trySetupData()。
再往后就和初次建立数据连接的流程类似了。


2.modem上报Data断开
DcController.DccDefaultState会监听RIL_UNSOL_DATA_CALL_LIST_CHANGED事件。


private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
......
// 把modem上报的连接保存到Map中,用cid作为Key
// Create hashmap of cid to DataCallResponse
HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
new HashMap<Integer, DataCallResponse>();
for (DataCallResponse dcs : dcsList) {
dataCallResponseListByCid.put(dcs.cid, dcs);
}

// 如果当前active的链接不在modem上报的列表中,则加入retry列表
// Add a DC that is active but not in the
// dcsList to the list of DC's to retry
ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
for (DataConnection dc : mDcListActiveByCid.values()) {
if (dataCallResponseListByCid.get(dc.mCid) == null) {
if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
dcsToRetry.add(dc);
}
}
......
for (DataCallResponse newState : dcsList) {

DataConnection dc = mDcListActiveByCid.get(newState.cid);
if (dc == null) { // 未知链接,忽略
// UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
loge("onDataStateChanged: no associated DC yet, ignore");
continue;
}

if (dc.mApnContexts.size() == 0) {
if (DBG) loge("onDataStateChanged: no connected apns, ignore");
} else {
// Determine if the connection/apnContext should be cleaned up
// or just a notification should be sent out.
if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid
+ " newState=" + newState.toString());
if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) { // 现有连接断开了
if (mDct.isCleanupRequired.get()) { // 始终为false
apnsToCleanup.addAll(dc.mApnContexts.keySet());
mDct.isCleanupRequired.set(false);
} else {
DcFailCause failCause = DcFailCause.fromInt(newState.status); // 获取错误原因
if (failCause.isRestartRadioFail()) { // 重启radio
......
mDct.sendRestartRadio();
} else if (mDct.isPermanentFail(failCause)) { // 永久失败,加入cleanup列表
......
apnsToCleanup.addAll(dc.mApnContexts.keySet());
} else { // 其他原因,加入retry列表
......
dcsToRetry.add(dc);
}
}
} else { // 现有链接仍然处于连接状态,更新链接属性
// Its active so update the DataConnections link properties
UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
if (result.oldLp.equals(result.newLp)) { // 属性未改变
if (DBG) log("onDataStateChanged: no change");
} else { // 属性有改变
......
}
}
}
......
}
......
// Cleanup connections that have changed
for (ApnContext apnContext : apnsToCleanup) {
mDct.sendCleanUpConnection(true, apnContext);
}

// Retry connections that have disappeared
for (DataConnection dc : dcsToRetry) {
if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
}
}


DcActiveState处理EVENT_LOST_CONNECTION事件
case EVENT_LOST_CONNECTION: {
if (DBG) {
log("DcActiveState EVENT_LOST_CONNECTION dc=" + DataConnection.this);
}

mInactiveState.setEnterNotificationParams(DcFailCause.LOST_CONNECTION); // 设置failCause
transitionTo(mInactiveState);

DcInactiveState
public void enter() {
......
if (mDisconnectParams == null && mConnectionParams == null && mDcFailCause != null) {
......
notifyAllDisconnectCompleted(mDcFailCause);
}
}

private void notifyAllDisconnectCompleted(DcFailCause cause) {
notifyAllWithEvent(null, DctConstants.EVENT_DISCONNECT_DONE, cause.toString());
}


DcTracker.java
case DctConstants.EVENT_DISCONNECT_DONE:
log("DataConnectionTracker.handleMessage: EVENT_DISCONNECT_DONE msg=" + msg);
onDisconnectDone((AsyncResult) msg.obj);
break;


private void onDisconnectDone(AsyncResult ar) {
......
apnContext.setState(DctConstants.State.IDLE);
......
// if all data connection are gone, check whether Airplane mode request was
// pending.
if (isDisconnected()) { // 链接已断开,判断是否关radio
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
......
return;
}
}
// If APN is still enabled, try to bring it back up automatically
if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) { // 需要retry
......
// 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");
long delay = apnContext.getInterApnDelay(mFailFast);
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); // 准备retry,后面会重新配置RetryManager
}
} else { // 这部分没明白
boolean restartRadioAfterProvisioning = mPhone.getContext().getResources().getBoolean(
com.android.internal.R.bool.config_restartRadioAfterProvisioning);

if (apnContext.isProvisioningApn() && restartRadioAfterProvisioning) { // What is Provisioning Apn???
log("onDisconnectDone: restartRadio after provisioning");
restartRadio();
}
apnContext.setApnSetting(null);
apnContext.setDataConnectionAc(null);
if (isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology())) {
if(DBG) log("onDisconnectDone: isOnlySigneDcAllowed true so setup single apn");
setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION);
} else {
if(DBG) log("onDisconnectDone: not retrying");
}
}
......
}


3.一段时间内只有发送数据而没有接收数据
Data建立成功后会调用DcTracker.startDataStallAlarm()来启动监控上/下行数据的任务,
当满足一定条件时,就会认为当前数据连接可能出现了问题,需要进行一些操作来恢复数据连接。

private void startDataStallAlarm(boolean suspectedStall) {
int nextAction = getRecoveryAction(); // 获取recovery的操作,初始值为GET_DATA_CALL_LIST
int delayInMs;

if (mDataStallDetectionEnabled && getOverallState() == DctConstants.State.CONNECTED) {
// 若是如下几种情况,就设置一个较短的检测周期(默认60s),否则设置一个较长的检测周期(默认360s)
// 1.屏幕亮着
// 2.数据连接状态可疑,当无接收数据且发送数据累积到设定值后的状态
// 3.RecoveryAction是除了GET_DATA_CALL_LIST之外的其它任意状态
// 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);
}
......
// 启动上/下行数据检测任务,最终会调用到onDataStallAlarm()
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_WAKEUP,
SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
} else {
if (VDBG_STALL) {
log("startDataStallAlarm: NOT started, no connection tag=" + mDataStallAlarmTag);
}
}
}

检测上/下行数据。
private void onDataStallAlarm(int tag) {
......
updateDataStallInfo(); // 更新无接收数据时累计的发送数据量

// 获取无接收数据时累计的发送数据量的最大值,默认10字节
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) { // 累计值大于设定值,触发Recovery
......
suspectedStall = DATA_STALL_SUSPECTED;
sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
} else {
......
}
startDataStallAlarm(suspectedStall); // 准备启动下一次检测任务
}


private void updateDataStallInfo() {
......
if ( sent > 0 && received > 0 ) {
if (VDBG_STALL) log("updateDataStallInfo: IN/OUT");
mSentSinceLastRecv = 0;
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
} else if (sent > 0 && received == 0) {
if (mPhone.getState() == PhoneConstants.State.IDLE) {
mSentSinceLastRecv += sent;
} else {
mSentSinceLastRecv = 0;
}
if (DBG) {
log("updateDataStallInfo: OUT sent=" + sent +
" mSentSinceLastRecv=" + mSentSinceLastRecv);
}
} else if (sent == 0 && received > 0) {
if (VDBG_STALL) log("updateDataStallInfo: IN");
mSentSinceLastRecv = 0;
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
} else {
if (VDBG_STALL) log("updateDataStallInfo: NONE");
}
}

在一个检测周期内,如果只有发送数据,没有接收到数据,且Phone的通话状态为IDLE,那么就把发送的字节数累计下来;
若既无接收也无发送数据,则不做处理;其它情况则清空之前的累计值;
当累计数值达到一定值(默认10byte)后,说明数据连接可能出现了问题,就需要执行recovery操作。
recovery操作包含以下几种,默认会先使用第一种进行恢复,若无效,则使用下一种方式,直至成功。
public static final int GET_DATA_CALL_LIST = 0; // 向modem查询数据连接状态
public static final int CLEANUP = 1; // 清楚连接
public static final int REREGISTER = 2; // 重新注网
public static final int RADIO_RESTART = 3; // 重启radio
public static final int RADIO_RESTART_WITH_PROP = 4; // 深度重启radio

RADIO_RESTART_WITH_PROP:
当之前的重启radio操作仍然无法恢复数据连接时,就会先设置一个RADIO_RESET_PROPERTY属性值,RIL或者系统
需要检测这个值来做进一步操作,至于做啥,Android原生未实现,需要各厂商自己来实现。

recovery操作:
private void doRecovery() {
if (getOverallState() == DctConstants.State.CONNECTED) {
......
switch (recoveryAction) {
case RecoveryAction.GET_DATA_CALL_LIST:
......
// 处理EVENT_DATA_STATE_CHANGED消息的地方啥也没做,相当于这个已经没用了
mPhone.mCi.getDataCallList(obtainMessage(DctConstants.EVENT_DATA_STATE_CHANGED));
putRecoveryAction(RecoveryAction.CLEANUP);
break;
case RecoveryAction.CLEANUP: // cleanup 后在DcTracker.onDisconnectDone()中会发起retry
......
cleanUpAllConnections(Phone.REASON_PDP_RESET);
putRecoveryAction(RecoveryAction.REREGISTER);
break;
case RecoveryAction.REREGISTER: // 先把网络模式设为GLOBAL,再设回原来的
......
mPhone.getServiceStateTracker().reRegisterNetwork(null);
putRecoveryAction(RecoveryAction.RADIO_RESTART);
break;
case RecoveryAction.RADIO_RESTART: // 重启RADIO
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
mSentSinceLastRecv);
if (DBG) log("restarting radio");
putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
restartRadio();
break;
case RecoveryAction.RADIO_RESTART_WITH_PROP: // 深度重启RADIO
// This is in case radio restart has not recovered the data.
// It will set an additional "gsm.radioreset" property to tell
// RIL or system to take further action.
// The implementation of hard reset recovery action is up to OEM product.
// Once RADIO_RESET property is consumed, it is expected to set back
// to false by RIL.
......
SystemProperties.set(RADIO_RESET_PROPERTY, "true");
......
restartRadio();
// 若仍然无法恢复,则再重GET_DATA_CALL_LIST开始恢复
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
break;
default:
throw new RuntimeException("doRecovery: Invalid recoveryAction=" +
recoveryAction);
}
mSentSinceLastRecv = 0;
}
}


未完待续。。。。。。








 

转载于:https://my.oschina.net/igiantpanda/blog/2222654

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值