Android 4.0.4 data call fail - 数据激活失败处理机制

来源:互联网 发布:java中求字符串的长度 编辑:程序博客网 时间:2024/06/05 04:15

Android数据激活步骤,此处不做详述,主要介绍激活失败后的处理:

1. Android数据连接分为两种:默认连接和个别应用需要时建立的连接两种。默认连接在开机是建立并始终保持,可通过一些控制来断开,另一种连接是应用请求时才建立的连接。

ConnectivityManager.java  包含了基本所有的API:

申请建立连接 API: public int startUsingNetworkFeature(int networkType, String feature)。

申请断开连接 API:public int stopUsingNetworkFeature(int networkType, String feature)。


2. PDP激活成功比较容易解读,但是数据连接激活失败后Android的处理机制(PDP 激活失败),从代码看激活失败后是有重试的而且默认连接和特殊连接的重试次数不同的。数据处理的核心代码在 GsmDataConnectionTracker.java里,如果想要深入了解可以从这里开始。

    @Override    protected void onDataSetupComplete(AsyncResult ar) {        DataConnection.FailCause cause = DataConnection.FailCause.UNKNOWN;        boolean handleError = false;        ApnContext apnContext = null;        if(ar.userObj instanceof ApnContext){            apnContext = (ApnContext)ar.userObj;        } else {            throw new RuntimeException("onDataSetupComplete: No apnContext");        }        if (isDataSetupCompleteOk(ar)) {            mPdpFailCount = 0;            DataConnectionAc dcac = apnContext.getDataConnectionAc();            if (RADIO_TESTS) {                // Note: To change radio.test.onDSC.null.dcac from command line you need to                // adb root and adb remount and from the command line you can only change the                // value to 1 once. To change it a second time you can reboot or execute                // adb shell stop and then adb shell start. The command line to set the value is:                //   adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "insert into system (name,value) values ('radio.test.onDSC.null.dcac', '1');"                ContentResolver cr = mPhone.getContext().getContentResolver();                String radioTestProperty = "radio.test.onDSC.null.dcac";                if (Settings.System.getInt(cr, radioTestProperty, 0) == 1) {                    log("onDataSetupComplete: " + radioTestProperty +                            " is true, set dcac to null and reset property to false");                    dcac = null;                    Settings.System.putInt(cr, radioTestProperty, 0);                    log("onDataSetupComplete: " + radioTestProperty + "=" +                            Settings.System.getInt(mPhone.getContext().getContentResolver(),                                    radioTestProperty, -1));                }            }            if (dcac == null) {                log("onDataSetupComplete: no connection to DC, handle as error");                cause = DataConnection.FailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;                handleError = true;            } else {                DataConnection dc = apnContext.getDataConnection();                if (DBG) {                    // TODO We may use apnContext.getApnSetting() directly                    // instead of getWaitingApns().get(0)                    String apnStr = "<unknown>";                    if (apnContext.getWaitingApns() != null                            && !apnContext.getWaitingApns().isEmpty()){                        apnStr = apnContext.getWaitingApns().get(0).apn;                    }                    log("onDataSetupComplete: success apn=" + apnStr);                }                ApnSetting apn = apnContext.getApnSetting();                if (apn.proxy != null && apn.proxy.length() != 0) {                    try {                        String port = apn.port;                        if (TextUtils.isEmpty(port)) port = "8080";                        ProxyProperties proxy = new ProxyProperties(apn.proxy,                                Integer.parseInt(port), null);                        dcac.setLinkPropertiesHttpProxySync(proxy);                    } catch (NumberFormatException e) {                        loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" +                                apn.port + "): " + e);                    }                }                // everything is setup                if(TextUtils.equals(apnContext.getApnType(),Phone.APN_TYPE_DEFAULT)) {                    SystemProperties.set("gsm.defaultpdpcontext.active", "true");                    if (canSetPreferApn && mPreferredApn == null) {                        if (DBG) log("onDataSetupComplete: PREFERED APN is null");                        mPreferredApn = apnContext.getApnSetting();                        if (mPreferredApn != null) {                            setPreferredApn(mPreferredApn.id);                        }                    }                } else {                    SystemProperties.set("gsm.defaultpdpcontext.active", "false");                }                notifyDefaultData(apnContext);            }        } else {            String apnString;            cause = (DataConnection.FailCause) (ar.result);            if (DBG) {                try {                    apnString = apnContext.getWaitingApns().get(0).apn;                } catch (Exception e) {                    apnString = "<unknown>";                }                log(String.format("onDataSetupComplete: error apn=%s cause=%s", apnString, cause));            }            if (cause.isEventLoggable()) {                // Log this failure to the Event Logs.                int cid = getCellLocationId();                EventLog.writeEvent(EventLogTags.PDP_SETUP_FAIL,                        cause.ordinal(), cid, TelephonyManager.getDefault().getNetworkType());            }            // Count permanent failures and remove the APN we just tried            if (cause.isPermanentFail()) apnContext.decWaitingApnsPermFailCount();            apnContext.removeNextWaitingApn();            if (DBG) {                log(String.format("onDataSetupComplete: WaitingApns.size=%d" +                        " WaitingApnsPermFailureCountDown=%d",                        apnContext.getWaitingApns().size(),                        apnContext.getWaitingApnsPermFailCount()));            }            handleError = true;        }        if (handleError) {            // See if there are more APN's to try            if (apnContext.getWaitingApns().isEmpty()) {                if (apnContext.getWaitingApnsPermFailCount() == 0) {                    if (DBG) {                        log("onDataSetupComplete: All APN's had permanent failures, stop retrying");                    }                    apnContext.setState(State.FAILED);                    mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());                    apnContext.setDataConnection(null);                    apnContext.setDataConnectionAc(null);                } else {                    if (DBG) log("onDataSetupComplete: Not all permanent failures, retry");                    // check to see if retry should be overridden for this failure.                    int retryOverride = -1;                    if (ar.exception instanceof DataConnection.CallSetupException) {                        retryOverride =                            ((DataConnection.CallSetupException)ar.exception).getRetryOverride();                    }                    if (retryOverride == RILConstants.MAX_INT) {                        if (DBG) log("No retry is suggested.");                    } else {                        startDelayedRetry(cause, apnContext, retryOverride);                    }                }            } else {                if (DBG) log("onDataSetupComplete: Try next APN");                apnContext.setState(State.SCANNING);                // Wait a bit before trying the next APN, so that                // we're not tying up the RIL command channel                startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);            }        }    }

激活失败后,首先判断是否是永久故障,如果是 apnContext.removeNextWaitingApn(),从数组里移除第一个APNSETTING:

// Count permanent failures and remove the APN we just tried

        public boolean isPermanentFail() {            return (this == OPERATOR_BARRED) || (this == MISSING_UNKNOWN_APN) ||                   (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||                   (this == SERVICE_OPTION_NOT_SUPPORTED) ||                   (this == SERVICE_OPTION_NOT_SUBSCRIBED) || (this == NSAPI_IN_USE) ||                   (this == PROTOCOL_ERRORS);        }

查看WaitingApns是否为空: apnContext.getWaitingApns().isEmpty(),如果不是5秒后重试下一个APN激活PDP:startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);

如果WaitingApns为空,查看 if (apnContext.getWaitingApnsPermFailCount() == 0)  是否为零,mWaitingApnsPermanentFailureCountDown的值在此函数里设置:


    public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) {        mWaitingApns = waitingApns;        mWaitingApnsPermanentFailureCountDown.set(mWaitingApns.size());    }

如果预置的APNSeting里有此类型 type的APN,或者APN设置类型type为空,此值就会大于零。当不为零时,请检查如果重试应该重写此故障

故障处理实现在:Dataconnection.java

    public static class CallSetupException extends Exception {        private int mRetryOverride = -1;        CallSetupException (int retryOverride) {            mRetryOverride = retryOverride;        }        public int getRetryOverride() {            return mRetryOverride;        }    }


        public void setEnterNotificationParams(ConnectionParams cp, FailCause cause,                                               int retryOverride) {            if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");            mConnectionParams = cp;            mFailCause = cause;            mRetryOverride = retryOverride;        }

    /**     * The state machine is activating a connection.     */    private class DcActivatingState extends State {        @Override        public boolean processMessage(Message msg) {            boolean retVal;            AsyncResult ar;            ConnectionParams cp;            switch (msg.what) {                case EVENT_CONNECT:                    if (DBG) log("DcActivatingState deferring msg.what=EVENT_CONNECT refCount = "                            + mRefCount);                    deferMessage(msg);                    retVal = HANDLED;                    break;                case EVENT_SETUP_DATA_CONNECTION_DONE:                    if (DBG) log("DcActivatingState msg.what=EVENT_SETUP_DATA_CONNECTION_DONE");                    ar = (AsyncResult) msg.obj;                    cp = (ConnectionParams) ar.userObj;                    DataCallState.SetupResult result = onSetupConnectionCompleted(ar);                    if (DBG) log("DcActivatingState onSetupConnectionCompleted result=" + result);                    switch (result) {                        case SUCCESS:                            // All is well                            mActiveState.setEnterNotificationParams(cp, FailCause.NONE);                            transitionTo(mActiveState);                            break;                        case ERR_BadCommand:                            // Vendor ril rejected the command and didn't connect.                            // Transition to inactive but send notifications after                            // we've entered the mInactive state.                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause, -1);                            transitionTo(mInactiveState);                            break;                        case ERR_UnacceptableParameter:                            // The addresses given from the RIL are bad                            tearDownData(cp);                            transitionTo(mDisconnectingErrorCreatingConnection);                            break;                        case ERR_GetLastErrorFromRil:                            // Request failed and this is an old RIL                            phone.mCM.getLastDataCallFailCause(                                    obtainMessage(EVENT_GET_LAST_FAIL_DONE, cp));                            break;                        case ERR_RilError:                            // Request failed and mFailCause has the reason                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause,                                                                      getSuggestedRetryTime(ar));                            transitionTo(mInactiveState);                            break;                        case ERR_Stale:                            // Request is stale, ignore.                            break;                        default:                            throw new RuntimeException("Unknown SetupResult, should not happen");                    }                    retVal = HANDLED;                    break;                case EVENT_GET_LAST_FAIL_DONE:                    ar = (AsyncResult) msg.obj;                    cp = (ConnectionParams) ar.userObj;                    FailCause cause = FailCause.UNKNOWN;                    if (cp.tag == mTag) {                        if (DBG) log("DcActivatingState msg.what=EVENT_GET_LAST_FAIL_DONE");                        if (ar.exception == null) {                            int rilFailCause = ((int[]) (ar.result))[0];                            cause = FailCause.fromInt(rilFailCause);                        }                        // Transition to inactive but send notifications after                        // we've entered the mInactive state.                        mInactiveState.setEnterNotificationParams(cp, cause, -1);                        transitionTo(mInactiveState);                    } else {                        if (DBG) {                            log("DcActivatingState EVENT_GET_LAST_FAIL_DONE is stale cp.tag="                                + cp.tag + ", mTag=" + mTag);                        }                    }                    retVal = HANDLED;                    break;                default:                    if (VDBG) {                        log("DcActivatingState not handled msg.what=0x" +                                Integer.toHexString(msg.what));                    }                    retVal = NOT_HANDLED;                    break;            }            return retVal;        }    }

重写故障用意不清楚。接下来又是重试激活了:startDelayedRetry(cause, apnContext, retryOverride); 检查 if (!apnContext.getDataConnection().isRetryNeeded()) 是否需要重试激活操作。(疑点)默认连接,判断是否已经注册到网络,如果已经注册,根据配置可以设置为永远重试也可在一定的次数后结束,不是默认连接可以设置为激活失败状态结束:


    private void startDelayedRetry(GsmDataConnection.FailCause cause,                                   ApnContext apnContext, int retryOverride) {        notifyNoData(cause, apnContext);        reconnectAfterFail(cause, apnContext, retryOverride);    }


    private void reconnectAfterFail(FailCause lastFailCauseCode,                                    ApnContext apnContext, int retryOverride) {        if (apnContext == null) {            loge("reconnectAfterFail: apnContext == null, impossible");            return;        }        if ((apnContext.getState() == State.FAILED) &&            (apnContext.getDataConnection() != null)) {            if (!apnContext.getDataConnection().isRetryNeeded()) {                if (!apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT)) {                    mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());                    return;                }                if (mReregisterOnReconnectFailure) {                    // We've re-registerd once now just retry forever.                    apnContext.getDataConnection().retryForeverUsingLastTimeout();                } else {                    // Try to Re-register to the network.                    if (DBG) log("reconnectAfterFail: activate failed, Reregistering to network");                    mReregisterOnReconnectFailure = true;                    mPhone.getServiceStateTracker().reRegisterNetwork(null);                    apnContext.getDataConnection().resetRetryCount();                    return;                }            }            // If retry needs to be backed off for specific case (determined by RIL/Modem)            // use the specified timer instead of pre-configured retry pattern.            int nextReconnectDelay = retryOverride;            if (nextReconnectDelay < 0) {                nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();                apnContext.getDataConnection().increaseRetryCount();            }            startAlarmForReconnect(nextReconnectDelay, apnContext);            if (!shouldPostNotification(lastFailCauseCode)) {                if (DBG) {                    log("reconnectAfterFail: NOT Posting GPRS Unavailable notification "                                + "-- likely transient error");                }            } else {                notifyNoData(lastFailCauseCode, apnContext);            }        }    }

还有一个重试管理的类 RetryManager.java, 判断是否有重试的必要,而且重试次数是可配置的:

    /**     * Report whether data reconnection should be retried     *     * @return {@code true} if the max retries has not been reached. {@code     *         false} otherwise.     */    public boolean isRetryNeeded() {        boolean retVal = mRetryForever || (mRetryCount < mMaxRetryCount);        if (DBG) log("isRetryNeeded: " + retVal);        return retVal;    }


    /**     * Configure for a simple linear sequence of times plus     * a random value.     *     * @param maxRetryCount is the maximum number of retries     *        before isRetryNeeded returns false.     * @param retryTime is a time that will be returned by getRetryTime.     * @param randomizationTime a random value between 0 and     *        randomizationTime will be added to retryTime. this     *        parameter may be 0.     * @return true if successful     */    public boolean configure(int maxRetryCount, int retryTime, int randomizationTime) {


    /**     * Configure for using string which allow arbitrary     * sequences of times. See class comments for the     * string format.     *     * @return true if successful     */    public boolean configure(String configStr) {


重试次数的配置变量和函数,可见默认连接(配置:增加了一倍重试时间5秒30分钟),其它连接(配置二级网络:4次尝试在20秒内),异常情况:不应该发生记录一个错误,默认情况下,一个简单的线性序列,如真的发生了,就重新配置。

    /** Retry configuration: A doubling of retry times from 5secs to 30minutes */    protected static final String DEFAULT_DATA_RETRY_CONFIG = "default_randomization=2000,"        + "5000,10000,20000,40000,80000:5000,160000:5000,"        + "320000:5000,640000:5000,1280000:5000,1800000:5000";    /** Retry configuration for secondary networks: 4 tries in 20 sec */    protected static final String SECONDARY_DATA_RETRY_CONFIG =            "max_retries=3, 5000, 5000, 5000";

    private void configureRetry(DataConnection dc, boolean forDefault) {        if (dc == null) return;        if (!dc.configureRetry(getReryConfig(forDefault))) {            if (forDefault) {                if (!dc.configureRetry(DEFAULT_DATA_RETRY_CONFIG)) {  //默认两千                    // Should never happen, log an error and default to a simple linear sequence.                    loge("configureRetry: Could not configure using " +                            "DEFAULT_DATA_RETRY_CONFIG=" + DEFAULT_DATA_RETRY_CONFIG);                    dc.configureRetry(20, 2000, 1000);                }            } else {                if (!dc.configureRetry(SECONDARY_DATA_RETRY_CONFIG)) {                    // Should never happen, log an error and default to a simple sequence.                    loge("configureRetry: Could note configure using " +                            "SECONDARY_DATA_RETRY_CONFIG=" + SECONDARY_DATA_RETRY_CONFIG);                    dc.configureRetry("max_retries=3, 333, 333, 333");                }            }        }    }


设置重试的地方:

    private void setupDataOnReadyApns(String reason) {        // Stop reconnect alarms on all data connections pending        // retry. Reset ApnContext state to IDLE.        for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {            if (dcac.getReconnectIntentSync() != null) {                cancelReconnectAlarm(dcac);            }            // update retry config for existing calls to match up            // ones for the new RAT.            if (dcac.dataConnection != null) {                Collection<ApnContext> apns = dcac.getApnListSync();                boolean hasDefault = false;                for (ApnContext apnContext : apns) {                    if (apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT)) {                        hasDefault = true;                        break;                    }                }                //此处设置重试                configureRetry(dcac.dataConnection, hasDefault);


  private boolean setupData(ApnContext apnContext) {        if (DBG) log("setupData: apnContext=" + apnContext);        ApnSetting apn;        GsmDataConnection dc;        int profileId = getApnProfileID(apnContext.getApnType());        apn = apnContext.getNextWaitingApn();        if (apn == null) {            if (DBG) log("setupData: return for no apn found!");            return false;        }        // First, check to see if ApnContext already has DC.        // This could happen if the retries are currently  engaged.        dc = (GsmDataConnection)apnContext.getDataConnection();        if (dc == null) {            dc = (GsmDataConnection) checkForConnectionForApnContext(apnContext);            if (dc == null) {                dc = findReadyDataConnection(apn);            }            if (dc == null) {                if (DBG) log("setupData: No ready GsmDataConnection found!");                // TODO: When allocating you are mapping type to id. If more than 1 free,                // then could findFreeDataConnection get the wrong one??                dc = findFreeDataConnection();            }            if (dc == null) {                dc = createDataConnection();            }            if (dc == null) {                if (DBG) log("setupData: No free GsmDataConnection found!");                return false;            }            DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());            dc.setProfileId( profileId );            dc.setActiveApnType(apnContext.getApnType());            int refCount = dcac.getRefCountSync();            if (DBG) log("setupData: init dc and apnContext refCount=" + refCount);            // configure retry count if no other Apn is using the same connection.            if (refCount == 0) {                log("refCount=0.");                 configureRetry(dc, apn.canHandleType(Phone.APN_TYPE_DEFAULT));  //此处设置重试            }

个人学习记录,如有理解错误之处请不吝指出,谢谢!