安卓Telephony - Data Stall Recovery机制
首页 > 安卓Telephony   作者:皮皮华  2022年6月30日 10:42 星期四  热度:1665°  字号:   评论:0 条
时间:2022-6-30 10:42   热度:1665°  评论:0 条 

    1. 触发机制

安卓 Telephony 触发 Data Stall Recovery有以下三种情况:

1.  ConnectivityService 报告Validation Failed 错误

2.  Data Stall Alarm Trigerred 一段时间内收不到数据

3.  Data Stall Alarm Trigerred-90秒内连续DNS检查失败超过3

    2. ConnectivityService 报告Validation Failed

        

【NetworkMonitor.java】
private class ProbingState extends State {
    private Thread mThread;
    @Override
    public void enter() {
        if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
            //Don't continue to blame UID forever.
            TrafficStats.clearThreadStatsUid();
        }

        final int token = ++mProbeToken;
        mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
                isCaptivePortal())));//发送CMD_PROBE_COMPLETE消息
        mThread.start();
    }

    @Override
    public boolean processMessage(Message message) {
        switch (message.what) {
            case CMD_PROBE_COMPLETE:
                ...
                } else if (probeResult.isPortal()) {//200 <= HTTP响应码 <= 399
                    //agenew add by wucheng 20200122
                    mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID,
                            probeResult.redirectUrl);//报告评估结果NETWORK_VALIDATION_RESULT_VALID
                    mLastPortalProbeResult = probeResult;
                    transitionTo(mCaptivePortalState);
                } 
                ...
        }
    }
    ...
}
protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
    mEvaluationResult = result;
    mRedirectUrl = redirectUrl;
    notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
}
private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
    try {//跨进程回调搜索"INetworkMonitorCallbacks.stub"
        mCallback.notifyNetworkTested(result, redirectUrl);//
    } catch (RemoteException e) {
        Log.e(TAG, "Error sending network test result", e);
    }
}
【ConnectivityService.java】
public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
    mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
            testResult, mNetId, redirectUrl));//发送消息EVENT_NETWORK_TESTED
}
//消息处理
private boolean maybeHandleNetworkMonitorMessage(Message msg) {
    switch (msg.what) {
        default:
            return false;
        case EVENT_NETWORK_TESTED: {
            ...
            //前面传递的消息为NETWORK_VALIDATION_RESULT_VALID,所以valid值为0
            final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
            final boolean wasValidated = nai.lastValidated;
            final boolean wasDefault = isDefaultNetwork(nai);
            // Only show a connected notification if the network is pending validation
            // after the captive portal app was open, and it has now validated.
            if (nai.captivePortalValidationPending && valid) {
                // User is now logged in, network validated.
                nai.captivePortalValidationPending = false;
                showNetworkNotification(nai, NotificationType.LOGGED_IN);
            }

            final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";

            if (DBG) {
                final String logMsg = !TextUtils.isEmpty(redirectUrl)
                         ? " with redirect to " + redirectUrl
                         : "";
                log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
            }
            if (valid != nai.lastValidated) {//与上一次的状态不一致
                if (wasDefault) {
                    metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
                            SystemClock.elapsedRealtime(), valid);
                }
                final int oldScore = nai.getCurrentScore();
                nai.lastValidated = valid;
                nai.everValidated |= valid;
                updateCapabilities(oldScore, nai, nai.networkCapabilities);
                // If score has changed, rebroadcast to NetworkFactories. b/17726566
                if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                if (valid) {
                    handleFreshlyValidatedNetwork(nai);
                    // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
                    // notifications if network becomes valid.
                    mNotifier.clearNotification(nai.network.netId,
                            NotificationType.NO_INTERNET);
                    mNotifier.clearNotification(nai.network.netId,
                            NotificationType.LOST_INTERNET);
                    mNotifier.clearNotification(nai.network.netId,
                            NotificationType.PARTIAL_CONNECTIVITY);
                }
            } else if (partialConnectivityChanged) {
                updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
            }
            updateInetCondition(nai);
            // Let the NetworkAgent know the state of its network
            Bundle redirectUrlBundle = new Bundle();
            redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
            // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
            nai.asyncChannel.sendMessage(
                    NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                    (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                    0, redirectUrlBundle);//发送CMD_REPORT_NETWORK_STATUS消息
            ...
        }
        ...
    }
    return true;
}
【NetworkAgent.java】
public void handleMessage(Message msg) {
    switch (msg.what) {
        ...
        case CMD_REPORT_NETWORK_STATUS: {
            String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
            if (VDBG) {
                log("CMD_REPORT_NETWORK_STATUS(" +
                        (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
            }
            networkStatus(msg.arg1, redirectUrl);//抽象类的方法搜索"extends NetworkAgent"
            break;
        }
        ...
    }
}
【DcNetworkAgent.java】
@Override
protected synchronized void networkStatus(int status, String redirectUrl) {
    if (mDataConnection == null) {
        loge("networkStatus called on no-owner DcNetworkAgent!");
        return;
    }

    logd("validation status: " + status + " with redirection URL: " + redirectUrl);
    DcTracker dct = mPhone.getDcTracker(mTransportType);
    if (dct != null) {
        Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
                status, 0, redirectUrl);//发送消息EVENT_NETWORK_STATUS_CHANGED
        msg.sendToTarget();//全局搜索"DctConstants.EVENT_NETWORK_STATUS_CHANGED"
    }
}
【DcTracker.java】
public void handleMessage (Message msg) {
    ...
    switch (msg.what) {
        ...
        case DctConstants.EVENT_NETWORK_STATUS_CHANGED:
            int status = msg.arg1;
            String url = (String) msg.obj;
            onNetworkStatusChanged(status, url);
            break;
            ...
    }
}
private void onNetworkStatusChanged(int status, String redirectUrl) {
    if (!TextUtils.isEmpty(redirectUrl)) {
        Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
        intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, redirectUrl);
        mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
        log("Notify carrier signal receivers with redirectUrl: " + redirectUrl);
    } else {
        ...
        mDsRecoveryHandler.processNetworkStatusChanged(isValid);
    }
}
public void processNetworkStatusChanged(boolean isValid) {
    if (isValid) {
        mIsValidNetwork = true;
        reset();
    } else {
        if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
            mIsValidNetwork = false;
            // Check and trigger a recovery if network switched from good
            // to bad or recovery is already started before.
            if (checkRecovery()//间隔需要小于3分钟
                    /// M: check if skip data stall alarm @{
                    && !mtkSkipDataStallAlarm()) {
                    /// @}
                if (DBG) log("trigger data stall recovery");
                triggerRecovery();
            }
        }
    }
}
private void triggerRecovery() {
    //发送消息EVENT_DO_RECOVERY去执行Do Recovery机制
    sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
}

    3. Data Stall Alarm Trigerred (一段时间内收不到数据)

        数据连接成功后,DcTracker会定期检测发送和接收的字节数,当一定时间内没有接收到数据,并且发送的数据大于设定的值则开始执行Do Recovery恢复机制。

        

        Data Stall Alarm Trigerred的大致流程如下:

        

【DcTracker.java】
//***** Constructor构造函数
public DcTracker(Phone phone, @TransportType int transportType) {
    ...
    registerForAllEvents();//注册所有的事件
    ...
}
protected void registerForAllEvents() {
    ...
    registerServiceStateTrackerEvents();//注册网络状相关态的事件处理
    mDataServiceManager.registerForServiceBindingChanged(this,
            DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED, null);
}
public void registerServiceStateTrackerEvents() {
    mPhone.getServiceStateTracker().registerForDataConnectionAttached(mTransportType, this,
            DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
    ...
}
//在handleMessage种处理网络状态相关的事件
public void handleMessage (Message msg) {
    ...
    switch (msg.what) {
        ...
        case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
            onDataConnectionAttached();//网络附着成功
            break;
        ...
    }
}
protected void onDataConnectionAttached() {
    mAttached.set(true);
    if (getOverallState() == DctConstants.State.CONNECTED) {
        startNetStatPoll();
        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);//开始启动Data Stall Alarm
        mPhone.notifyDataConnection();
    }
    if (mAutoAttachOnCreationConfig) {
        mAutoAttachEnabled.set(true);
    }
    setupDataOnAllConnectableApns(Phone.REASON_DATA_ATTACHED, RetryFailures.ALWAYS);
}
protected void startDataStallAlarm(boolean suspectedStall) {
    int delayInMs;

    if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()//是否启用
            && getOverallState() == DctConstants.State.CONNECTED) {
        // If screen is on or data stall is currently suspected, set the alarm
        // with an aggressive timeout.
        if (mIsScreenOn || suspectedStall || mDsRecoveryHandler.isAggressiveRecovery()) {
            delayInMs = Settings.Global.getInt(mResolver,
                    Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
                    DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);//1000 * 60 = 1分钟
        } 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);//1000 * 60 * 6 = 6分钟
        }

        mDataStallAlarmTag += 1;
        if (VDBG_STALL) {
            log("startDataStallAlarm: tag=" + mDataStallAlarmTag +
                    " delay=" + (delayInMs / 1000) + "s");
        }
        Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
        intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, mDataStallAlarmTag);
        intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE, mTransportType);
        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
        // AOSP will create an PendingIntent with requestCode = 0 when phoneId is 1.
        // Then phone 0's PendingIntent will be overrided.
        // Hence, shift phoneId with 1 to make phone 0's PendingIntent be identical.
        mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(),
                mPhone.getPhoneId() + 1, intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,//设置一个定时器,delayInMs毫秒后发送INTENT_DATA_STALL_ALARM
                SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
    } else {
        if (VDBG_STALL) {
            log("startDataStallAlarm: NOT started, no connection tag=" + mDataStallAlarmTag);
        }
    }
}
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)) {
            onActionIntentReconnectAlarm(intent);
        } else if (action.equals(INTENT_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 (DBG) log("received carrier config change");
            if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
                setDefaultDataRoamingEnabled();
            }
        } else {
            if (DBG) log("onReceive: Unknown action=" + action);
        }
    }
}
private void onActionIntentDataStallAlarm(Intent intent) {
    ...
    Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
            intent.getAction());//发送EVENT_DATA_STALL_ALARM事件
    msg.arg1 = intent.getIntExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, 0);
    sendMessage(msg);
}
//在handleMessage种处理网络状态相关的事件
public void handleMessage (Message msg) {
    ...
    switch (msg.what) {
        ...
        case DctConstants.EVENT_DATA_STALL_ALARM:
            onDataStallAlarm(msg.arg1);//处理Data Stall Alarm
            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);//10

    boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
    if (mSentSinceLastRecv >= hangWatchdogTrigger) {
        if (DBG) {
            log("onDataStallAlarm: tag=" + tag + " do recovery action="
                    + mDsRecoveryHandler.getRecoveryAction());
        }
        suspectedStall = DATA_STALL_SUSPECTED;
        sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));//启动Do Recovery恢复机制
    } else {
        if (VDBG_STALL) {
            log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) +
                " pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger);
        }
    }
    //重置Alarm任务,一段时间后再次执行
    startDataStallAlarm(suspectedStall);
}

private void updateDataStallInfo() {
    long sent, received;

    TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);//获取之前的统计结果
    mDataStallTxRxSum.updateTcpTxRxSum();//更新TCP数据的所有的发送和接收的总和

    /// M: check the current operator if need to update total Tx/Rx packets @{
    mtkUpdateTotalTxRxSum();
    /// @}

    if (VDBG_STALL) {
        log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
                " preTxRxSum=" + preTxRxSum);
    }
    //计算这一段期间内发送和接收的字节数 = 新统计的字节数 - 上一次的字节数
    sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts;
    received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts;

    if (RADIO_TESTS) {
        if (SystemProperties.getBoolean("radio.test.data.stall", false)) {
            log("updateDataStallInfo: radio.test.data.stall true received = 0;");
            received = 0;
        }
    }
    if ( sent > 0 && received > 0 ) {//有发送     有接收
        if (VDBG_STALL) log("updateDataStallInfo: IN/OUT");
        mSentSinceLastRecv = 0;
        mDsRecoveryHandler.reset();
    } else if (sent > 0 && received == 0) {//有发送    无接收
        if (isPhoneStateIdle()) {
            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;
        mDsRecoveryHandler.reset();
    } else {
        if (VDBG_STALL) log("updateDataStallInfo: NONE");
    }
}


                 以下为Data Stall的日志示例,

        微信图片_20220630134410.jpg

    4. Data Stall Alarm Trigerred( 90秒内连续DNS检查失败超过3次)

        

    5.  Do Recovery 的4大策略

恢复机制分为以下四大策略:

1.  向Modem主动查询 DATA_CALL_LIST

2.  清除现有的数据链接

3.  重新驻网

4.  重启Radio

执行顺序从上往下依次执行。


【DcTracker.java】
public void doRecovery() {
    if (getOverallState() == DctConstants.State.CONNECTED) {
        // Go through a series of recovery steps, each action transitions to the next action
        @RecoveryAction final int recoveryAction = getRecoveryAction();
        ...
        switch (recoveryAction) {
            case RECOVERY_ACTION_GET_DATA_CALL_LIST:
                ...//向Modem主动查询 DATA_CALL_LIST
                mDataServiceManager.requestDataCallList(obtainMessage());
                putRecoveryAction(RECOVERY_ACTION_CLEANUP);
                break;
            case RECOVERY_ACTION_CLEANUP:
                ...//清除现有的数据链接
                cleanUpConnection(mApnContexts.get(ApnSetting.getApnTypeString(
                        ApnSetting.TYPE_DEFAULT)));
                putRecoveryAction(RECOVERY_ACTION_REREGISTER);
                break;
            case RECOVERY_ACTION_REREGISTER:
                ...//重新驻网
                mPhone.getServiceStateTracker().reRegisterNetwork(null);
                putRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
                break;
            case RECOVERY_ACTION_RADIO_RESTART:
                ...//重启Radio
                restartRadio();
                reset();
                break;
            default:
                throw new RuntimeException("doRecovery: Invalid recoveryAction="
                    + recoveryAction);
        }
        mSentSinceLastRecv = 0;
        mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
    }
}




 您阅读这篇文章共花了: 
捐赠支持:如果觉得这篇文章对您有帮助,请“扫一扫”鼓励作者!
二维码加载中...
本文作者:皮皮华      文章标题: 安卓Telephony - Data Stall Recovery机制
本文地址:http://huazai.eleuu.com/?post=58
版权声明:若无注明,本文皆为“皮皮华博客”原创,转载请保留文章出处。

发表吐槽

你肿么看?

你还可以输入 250 / 250 个字

嘻嘻 大笑 可怜 吃惊 害羞 调皮 鄙视 示爱 大哭 开心 偷笑 嘘 奸笑 委屈 抱抱 愤怒 思考 日了狗

评论信息框


既然没有吐槽,那就赶紧抢沙发吧!