https://blog.csdn.net/u012864297/article/details/53423379
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;
-
}
-
}