Android 6.0 Framework telephony中数据业务链接错误处理一般分3种情况:
1. SETUP_DATA_CALL 时返回错误
2. Modem上报DATA_CALL_LIST包含错误码或者链接中断
3. 一段时间内没有上下行数据(TX/RX)
下面具体来看每种情况的处理。
1. SETUP_DATA_CALL失败
DataConnection在收到SETUP_DATA_CALL结果后,用Message通知DcTracker处理:
-
protected void onDataSetupComplete(AsyncResult ar) { -
if (ar.exception == null) { -
//链接成功 -
}else{ -
... -
//标记permanent fail的次数,会影响后面onDataSetupCompleteError的判断 -
if (isPermanentFail(cause)) apnContext.decWaitingApnsPermFailCount(); -
apnContext.removeWaitingApn(apnContext.getApnSetting()); //从waiting列表中移除已经失败的APN -
onDataSetupCompleteError(ar);//继续处理错误 -
... -
} -
}
处理Error的逻辑:
1. 如果apnContext中的所有waiting APN都失败了,且不是每个都发生permanent fail(永久性错误),则设置delay并重新发起这次连接
2. 如果apnContext中仍有没有尝试的waiting APN,则设置delay并尝试用下一个APN去连接
-
/** -
* 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()}. -
*/ -
@Override -
protected void onDataSetupCompleteError(AsyncResult ar) { -
String reason = ""; -
ApnContext apnContext = getValidApnContext(ar, "onDataSetupCompleteError"); -
if (apnContext == null) return; -
//已经尝试过所有APN -
if (apnContext.getWaitingApns().isEmpty()) { -
apnContext.setState(DctConstants.State.FAILED);//apnContext state设置成FAILED -
mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType()); -
//清除DataConnection -
apnContext.setDataConnectionAc(null); -
//如果所有APN都发生Permanent fail,则不做重试 -
if (apnContext.getWaitingApnsPermFailCount() == 0) { -
if (DBG) { -
log("onDataSetupCompleteError: All APN's had permanent failures, stop retrying"); -
} -
} else {//执行重试 -
int delay = getApnDelay(Phone.REASON_APN_FAILED); -
if (DBG) { -
log("onDataSetupCompleteError: Not all APN's had permanent failures delay=" -
+ delay); -
} -
startAlarmForRestartTrySetup(delay, apnContext); -
} -
} else {//waitingAPN中还有没有尝试的APN,继续尝试下一个 -
if (DBG) log("onDataSetupCompleteError: Try next APN"); -
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(getApnDelay(Phone.REASON_APN_FAILED), apnContext);//试下一个APN -
} -
}
附:ApnContext的所有状态
-
/** -
* 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, -
RETRYING -
}
2. 链接中断
DcController监听RIL_UNSOL_DATA_CALL_LIST_CHANGED消息,获得每一个数据连接的更新:
-
mPhone.mCi.registerForDataNetworkStateChanged(getHandler(), -
DataConnection.EVENT_DATA_STATE_CHANGED, null);
RIL上报DATA_CALL_LIST_CHANGED时会带上当前的Modem中的DataCall list,DcController将此dataCall list和上层的active list做对比:
1. 已经丢失 及 断开 的连接将会重试
2. 发生变化 和 发生永久错误的链接则需要清除
-
private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) { -
// Create hashmap of cid to DataCallResponse -
HashMap<Integer, DataCallResponse> dataCallResponseListByCid = -
new HashMap<Integer, DataCallResponse>(); -
for (DataCallResponse dcs : dcsList) { -
dataCallResponseListByCid.put(dcs.cid, dcs); -
} -
//如果上报的dcsList中并没有找到对应的active的链接,则默认连接丢失并加入重试List -
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); -
} -
} -
// Find which connections have changed state and send a notification or cleanup -
// and any that are in active need to be retried. -
ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>(); -
boolean isAnyDataCallDormant = false; -
boolean isAnyDataCallActive = false; -
for (DataCallResponse newState : dcsList) { -
DataConnection dc = mDcListActiveByCid.get(newState.cid); -
//不在Active MAP中的连接,表明这个连接还没同步到上层,会有其他地方处理。 -
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 (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) { -
//连接INACTIVE,按照错误类型区分处理 -
DcFailCause failCause = DcFailCause.fromInt(newState.status); -
if (failCause.isRestartRadioFail()) { -
//恢复需要重启radio -
mDct.sendRestartRadio(); -
} else if (mDct.isPermanentFail(failCause)) { -
//链接发生不可恢复的错误,需要Cleanup -
apnsToCleanup.addAll(dc.mApnContexts.keySet()); -
} else { -
for (ApnContext apnContext : dc.mApnContexts.keySet()) { -
if (apnContext.isEnabled()) { -
//apn是enabled状态,重试 -
dcsToRetry.add(dc); -
break; -
} else { -
//apn已经disabled,需要cleanup -
apnsToCleanup.add(apnContext); -
} -
} -
} -
} else { -
//LinkProperty发生变化 -
UpdateLinkPropertyResult result = dc.updateLinkProperty(newState); -
if (result.oldLp.equals(result.newLp)) { -
if (DBG) log("onDataStateChanged: no change"); -
} else { -
//判断interface是否一致 -
if (result.oldLp.isIdenticalInterfaceName(result.newLp)) { -
if (! result.oldLp.isIdenticalDnses(result.newLp) || -
! result.oldLp.isIdenticalRoutes(result.newLp) || -
! result.oldLp.isIdenticalHttpProxy(result.newLp) || -
! result.oldLp.isIdenticalAddresses(result.newLp)) { -
// If the same address type was removed and -
// added we need to cleanup -
CompareResult<LinkAddress> car = -
result.oldLp.compareAddresses(result.newLp); -
if (DBG) { -
log("onDataStateChanged: oldLp=" + result.oldLp + -
" newLp=" + result.newLp + " car=" + car); -
} -
boolean needToClean = false; -
//如果address发生变化,需要清除这个old connection -
for (LinkAddress added : car.added) { -
for (LinkAddress removed : car.removed) { -
if (NetworkUtils.addressTypeMatches( -
removed.getAddress(), -
added.getAddress())) { -
needToClean = true; -
break; -
} -
} -
} -
if (needToClean) { -
apnsToCleanup.addAll(dc.mApnContexts.keySet()); -
} else { -
if (DBG) log("onDataStateChanged: simple change"); -
//其他的LP变化,只做notify -
for (ApnContext apnContext : dc.mApnContexts.keySet()) { -
mPhone.notifyDataConnection( -
PhoneConstants.REASON_LINK_PROPERTIES_CHANGED, -
apnContext.getApnType()); -
} -
} -
} else { -
if (DBG) { -
log("onDataStateChanged: no changes"); -
} -
} -
} else { -
//interface发生改变,cleanUp这个old connection -
apnsToCleanup.addAll(dc.mApnContexts.keySet()); -
if (DBG) { -
log("onDataStateChanged: interface change, cleanup apns=" -
+ dc.mApnContexts); -
} -
} -
} -
} -
} -
... -
} -
... -
//清除链接 -
for (ApnContext apnContext : apnsToCleanup) { -
mDct.sendCleanUpConnection(true, apnContext); -
} -
//通知DataConnection链接丢失,需要发起重连 -
for (DataConnection dc : dcsToRetry) { -
dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag); -
} -
} -
}
DataConnection ActiveState在收到LOST_CONNECTION消息后:
1. 如果重试次数没有达到上限,则设置定时重试,并切换到RetryingState
2. 如果不需要重试,则切换到Inactive状态,并可能通知DcTracker处理(onDataSetupCompleteError,可看第一种情况)
-
case EVENT_LOST_CONNECTION: { -
if (DBG) { -
log("DcActiveState EVENT_LOST_CONNECTION dc=" + DataConnection.this); -
} -
if (mRetryManager.isRetryNeeded()) { -
// We're going to retry -
int delayMillis = mRetryManager.getRetryTimer(); -
//重试 -
mDcRetryAlarmController.startRetryAlarm(EVENT_RETRY_CONNECTION, mTag, -
delayMillis); -
transitionTo(mRetryingState); -
} else { -
mInactiveState.setEnterNotificationParams(DcFailCause.LOST_CONNECTION); -
transitionTo(mInactiveState); -
} -
retVal = HANDLED; -
break; -
}
RetryingState 收到RETRY消息后,发起连接并切换到ActivatingState
-
case EVENT_RETRY_CONNECTION: { -
if (msg.arg1 == mTag) { -
mRetryManager.increaseRetryCount();//计数 -
onConnect(mConnectionParams);//开始连接 -
transitionTo(mActivatingState);//切换到Activating State -
} else { -
if (DBG) { -
log("DcRetryingState stale EVENT_RETRY_CONNECTION" -
+ " tag:" + msg.arg1 + " != mTag:" + mTag); -
} -
} -
retVal = HANDLED; -
break; -
}
RetryManager负责重试相关的计数:
-
public boolean isRetryNeeded() { -
boolean retVal = mRetryForever || (mRetryCount < mCurMaxRetryCount); -
if (DBG) log("isRetryNeeded: " + retVal); -
return retVal; -
}
3. 一段时间内持续没有接收到新的数据包
在Data完成连接后,DcTracker会定时检查TX/RX的更新,如果RX的值持续没有更新并超过设置的上限值,就会触发Recovery动作。

首先来看方法onDataStallAlarm,它由Alarm定时触发,执行这些操作:
更新TX/RX数据 -> 判断是否需要Recover并执行 -> 重新设置Alarm来触发下一次检查。
-
protected void onDataStallAlarm(int tag) { -
if (mDataStallAlarmTag != tag) { -
if (DBG) { -
log("onDataStallAlarm: ignore, tag=" + tag + " expecting " + mDataStallAlarmTag); -
} -
return; -
} -
//更新mSentSinceLastRecv -
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) { -
//一段时间没有RX,且超过watchdog的值,需要recover -
suspectedStall = DATA_STALL_SUSPECTED; -
sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY)); -
} else { -
if (VDBG_STALL) { -
log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) + -
" pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger); -
} -
} -
//重新设置Alarm任务,一段时间后再次执行本方法(onDataStallAlarm) -
startDataStallAlarm(suspectedStall); -
}
updateDataStallInfo()负责记数,处理分3种情况:
1. 有TX 也有RX -> 正常,重置计数和Recovery action(Recovery action后面会写到)
2. 有TX没有RX -> 异常,累计TX数据
3. 没有TX 只有RX -> 正常,重置计数和Recovery action
-
private void updateDataStallInfo() { -
long sent, received; -
TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum); -
mDataStallTxRxSum.updateTxRxSum(); -
sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts; -
received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts; -
//收发正常,RecoveryAction重置 -
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) { -
//没有RX;若不在通话状态则需要累计本次发送量 -
if (isPhoneStateIdle()) { -
mSentSinceLastRecv += sent; -
} else { -
mSentSinceLastRecv = 0; -
} -
//没有发数据,RecoveryAction重置 -
} 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"); -
} -
}
TX/RX数据由TrafficStats提供的静态方法获得,是native层方法统计所有Mobile的iface后返回的数据:
-
public void updateTxRxSum() { -
this.txPkts = TrafficStats.getMobileTcpTxPackets(); -
this.rxPkts = TrafficStats.getMobileTcpRxPackets(); -
}
最后看下doRecovery方法如何执行恢复数据。
doRecovery方法中有5种不同的Recovery action对应着各自的处理:
1. 向Modem主动查询DATA CALL LIST
2. 清除现有的数据链接
3. 重新驻网
4. 重启Radio
5. 深度重启Radio(根据高通的注释,这个操作涉及到RIL的设计)
如果一种方法执行之后,连接依然有问题,则执行下一种恢复方法,顺序类似于循环链表,直到恢复正常后updateDataStallInfo()将Action重置:
-
protected void doRecovery() { -
if (getOverallState() == DctConstants.State.CONNECTED) { -
// Go through a series of recovery steps, each action transitions to the next action -
int recoveryAction = getRecoveryAction(); -
switch (recoveryAction) { -
case RecoveryAction.GET_DATA_CALL_LIST: -
mPhone.mCi.getDataCallList(obtainMessage(DctConstants.EVENT_DATA_STATE_CHANGED)); -
putRecoveryAction(RecoveryAction.CLEANUP); -
break; -
case RecoveryAction.CLEANUP: -
cleanUpAllConnections(Phone.REASON_PDP_RESET); -
putRecoveryAction(RecoveryAction.REREGISTER); -
break; -
case RecoveryAction.REREGISTER: -
mPhone.getServiceStateTracker().reRegisterNetwork(null); -
putRecoveryAction(RecoveryAction.RADIO_RESTART); -
break; -
case RecoveryAction.RADIO_RESTART: -
putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP); -
restartRadio(); -
break; -
case RecoveryAction.RADIO_RESTART_WITH_PROP: -
// 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. -
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1); -
if (DBG) log("restarting radio with gsm.radioreset to true"); -
SystemProperties.set(RADIO_RESET_PROPERTY, "true"); -
// give 1 sec so property change can be notified. -
try { -
Thread.sleep(1000); -
} catch (InterruptedException e) {} -
restartRadio(); -
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST); -
break; -
default: -
throw new RuntimeException("doRecovery: Invalid recoveryAction=" + -
recoveryAction); -
} -
mSentSinceLastRecv = 0; -
}
本文详细解析了Android 6.0 Framework telephony中数据连接错误的三种常见处理方式:SETUP_DATA_CALL失败后的处理流程、数据连接中断时的处理机制以及长时间无数据传输时的恢复策略。
4171

被折叠的 条评论
为什么被折叠?



