Android P ConnectivityService框架----数据相关业务1

一、简介

从打开数据开关到手机桌面显示LTE,整个的流程是怎么样的?以此疑问为主线,开始接下来的分析。

路由配置信息的获取有多种方式:wifi,mobile data,Tethering,VPN。这里主要分析由mobile data获取路由配置信息的过程,结合ConnectivityService类,其大致的类图与流程图如下: 

Android P ConnectivityService框架----数据相关业务1_第1张图片
从打开数据开关到获取到路由配置信息的时序图如下:

Android P ConnectivityService框架----数据相关业务1_第2张图片

本篇博客只介绍DcTracker,DataConnection,GsmCdmaPhone,RIL层的相关内容,其他的ConnectivityService,NetworkFactory,NetworkMonitor等内容在下一篇博客继续分析。

DcTracker与DataConnection的分工不同,DcTracker主要处理APN参数,数据重连机制,通知上层等一些琐碎的工作;DataConnection则负责与RIL层通讯,它本身就是一个状态机,用于处理网络请求过程中产生的各种状态,网络请求成功后生成NetworkAgent对象参与ConnectivityService中的网络评分工作。

本篇分为两个小节来分析:
1) DcTracker 
2) DataConnection 

二、DcTracker 

DcTracker主要负责处理APN参数,数据重连机制,通知上层等工作。 这里分为两个小节分析:
1. 处理apn参数:
1). 初始化ApnContext 
2). 激活ApnContext 
3). 筛选apn 
4). 发起请求 

2. 请求网络的结果处理:
1). 网络请求成功 
2). 数据重连

2.1 处理APN参数

2.1.1 初始化ApnContext

ApnContext用来建立指定apn类型的apn上下文,ApnContext中定义了此apn类型所需的重要属性。

/**
 * Maintain the Apn context
 */
public class ApnContext {
    ...
    //apn的类型
    private final String mApnType; 
    //网络连接类型的优先级
    public final int priority; 
    private final RetryManager mRetryManager;
    private final DcTracker mDcTracker;
    private int mRefCount = 0;
    //此apn类型的apn参数
    private ApnSetting mApnSetting; 
    String mReason;
    //ApnContext所处的状态
    private DctConstants.State mState;
    ...
}

apn的类型定义在PhoneConstants.java中:

public class PhoneConstants {
    ...
    /**
     * APN types for data connections.  These are usage categories for an APN
     * entry.  One APN entry may support multiple APN types, eg, a single APN
     * may service regular internet traffic ("default") as well as MMS-specific
     * connections.
* APN_TYPE_ALL is a special type to indicate that this APN entry can * service all data connections. */ public static final String APN_TYPE_ALL = "*"; /** APN type for default data traffic */ public static final String APN_TYPE_DEFAULT = "default"; /** APN type for MMS traffic */ public static final String APN_TYPE_MMS = "mms"; /** APN type for SUPL assisted GPS */ public static final String APN_TYPE_SUPL = "supl"; /** APN type for DUN traffic */ public static final String APN_TYPE_DUN = "dun"; /** APN type for HiPri traffic */ public static final String APN_TYPE_HIPRI = "hipri"; /** APN type for FOTA */ public static final String APN_TYPE_FOTA = "fota"; /** APN type for IMS */ public static final String APN_TYPE_IMS = "ims"; /** APN type for CBS */ public static final String APN_TYPE_CBS = "cbs"; /** APN type for IA Initial Attach APN */ public static final String APN_TYPE_IA = "ia"; /** APN type for Emergency PDN. This is not an IA apn, but is used * for access to carrier services in an emergency call situation. */ public static final String APN_TYPE_EMERGENCY = "emergency"; /** Array of all APN types */ public static final String[] APN_TYPES = {APN_TYPE_DEFAULT, APN_TYPE_MMS, APN_TYPE_SUPL, APN_TYPE_DUN, APN_TYPE_HIPRI, APN_TYPE_FOTA, APN_TYPE_IMS, APN_TYPE_CBS, APN_TYPE_IA, APN_TYPE_EMERGENCY }; ... }

apn类型的优先级读取自文件frameworks\base\core\res\res\values\config.xml

    
    
        "wifi,1,1,2,-1,true"
        "tedongle,49,49,1,-1,true"
        "mobile,0,0,0,-1,true"
        "mobile_mms,2,0,2,300000,true"
        "mobile_supl,3,0,2,300000,true"
        "mobile_dun,4,0,3,300000,true"
        "mobile_hipri,5,0,3,300000,true"
        "bluetooth,7,7,0,-1,true"
        "mobile_fota,10,0,2,300000,true"
        "mobile_ims,11,0,-1,-1,true"
        ...

ApnContext的状态

public class DctConstants {
    /**
     * 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,
        // After moving retry manager to ApnContext, we'll never enter this state!
        // Todo: Remove this state and other places that use this state and then
        // rename SCANNING to RETRYING.
        RETRYING
    }
    ...
}

ApnContext的初始化:因为apn的类型有很多种,任意一种都可能使用到,因此需要初始化所有apn类型的ApnContext。在DcTracker对象创建的时候初始化

public class DcTracker extends Handler {
    ...
    //***** Constructor
    public DcTracker(Phone phone, int transportType) {
        ...
        initApnContexts();
        ...
    }
    ...
    private void initApnContexts() {
        log("initApnContexts: E");
        // Load device network attributes from resources
        String[] networkConfigStrings = mPhone.getContext().getResources().getStringArray(
                com.android.internal.R.array.networkAttributes);
        for (String networkConfigString : networkConfigStrings) {
            NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
            ApnContext apnContext = null;

            switch (networkConfig.type) {
            case ConnectivityManager.TYPE_MOBILE:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_DEFAULT, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_MMS:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_SUPL:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_SUPL, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_DUN:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_DUN, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_HIPRI:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_HIPRI, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_FOTA:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_FOTA, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_IMS:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_IMS, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_CBS:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_CBS, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_IA:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_IA, networkConfig);
                break;
            case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
                apnContext = addApnContext(PhoneConstants.APN_TYPE_EMERGENCY, networkConfig);
                break;
            default:
                log("initApnContexts: skipping unknown type=" + networkConfig.type);
                continue;
            }
            log("initApnContexts: apnContext=" + apnContext);
        }

        if (VDBG) log("initApnContexts: X mApnContexts=" + mApnContexts);
    }
    ...
    private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
        ApnContext apnContext = new ApnContext(mPhone, type, LOG_TAG, networkConfig, this);
        mApnContexts.put(type, apnContext);
        mApnContextsById.put(ApnContext.apnIdForApnName(type), apnContext);
        mPrioritySortedApnContexts.add(apnContext);
        return apnContext;
    }
    ...
}

2.1.2 激活ApnContext

激活ApnContext就是设置apnContext.setEnabled(true),整体流程如下:

Android P ConnectivityService框架----数据相关业务1_第3张图片

①.激活默认apn类型的ApnContext

默认的apn类型是default。当SUBSCRIPTION_CHANGED状态变化时,激活默认的ApnContext。SUBSCRIPTION_CHANGED状态变化包括:插拔SIM卡,切换默认SIM卡。且ApnContext跟卡槽无关,默认apn类型的ApnContext被激活后,无论卡1还是卡2都可使用此默认类型的apn,无须再次激活。参见上图,PhoneSwitcher和TelephonyNetworkFactory都监听了SUBSCRIPTION_CHANGED状态的变化。

/**
 * Utility singleton to monitor subscription changes and incoming NetworkRequests
 * and determine which phone/phones are active.
 *
 * Manages the ALLOW_DATA calls to modems and notifies phones about changes to
 * the active phones.  Note we don't wait for data attach (which may not happen anyway).
 */
public class PhoneSwitcher extends Handler {
    ...
    private final static int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101;
    private final static int EVENT_SUBSCRIPTION_CHANGED         = 102;
    private final static int EVENT_REQUEST_NETWORK              = 103;
    private final static int EVENT_RELEASE_NETWORK              = 104;
    private final static int EVENT_EMERGENCY_TOGGLE             = 105;
    private final static int EVENT_RESEND_DATA_ALLOWED          = 106;
    ...
    public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
            CommandsInterface[] cis, Phone[] phones) {
        ...
            mContext.registerReceiver(mDefaultDataChangedReceiver, new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
        ...
    }
    ...
}

public class TelephonyNetworkFactory extends NetworkFactory {
    ...
    private static final int EVENT_ACTIVE_PHONE_SWITCH          = 1;
    private static final int EVENT_SUBSCRIPTION_CHANGED         = 2;
    private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 3;
    private static final int EVENT_NETWORK_REQUEST              = 4;
    private static final int EVENT_NETWORK_RELEASE              = 5;
    ...
    public TelephonyNetworkFactory(PhoneSwitcher phoneSwitcher,
            SubscriptionController subscriptionController, SubscriptionMonitor subscriptionMonitor,
            Looper looper, Context context, int phoneId, DcTracker dcTracker) {
            ...
            mSubscriptionMonitor.registerForSubscriptionChanged(mPhoneId, mInternalHandler,
            EVENT_SUBSCRIPTION_CHANGED, null);
            ...
        }
    ...
}

②.激活其他apn类型的ApnContext

主动发起网络请求needNetworkFor()时,激活其他类型的ApnContext。参见上图,PhoneSwitcher接收到EVENT_REQUEST_NETWORK和EVENT_RELEASE_NETWORK事件消息后会调用onEvaluate(),如果满足条件,将会激活特定apn类型的ApnContext。同理,如果有新的网络请求和网络评分变化的话,也会引发TelephonyNetworkFactory激活特定apn类型的ApnContext。关键log如下:

public class DcTracker extends Handler {
    ...
    private void onEnableApn(int apnId, int enabled) {
        ApnContext apnContext = mApnContextsById.get(apnId);
        if (apnContext == null) {
            loge("onEnableApn(" + apnId + ", " + enabled + "): NO ApnContext");
            return;
        }
        // TODO change our retry manager to use the appropriate numbers for the new APN
        if (DBG) log("onEnableApn: apnContext=" + apnContext + " call applyNewState");
        applyNewState(apnContext, enabled == DctConstants.ENABLED, apnContext.getDependencyMet());

        if ((enabled == DctConstants.DISABLED) &&
            isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology()) &&
            !isHigherPriorityApnContextActive(apnContext)) {

            if(DBG) log("onEnableApn: isOnlySingleDcAllowed true & higher priority APN disabled");
            // If the highest priority APN is disabled and only single
            // data call is allowed, try to setup data call on other connectable APN.
            setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION);
        }
    }
    ...
}
//default apn
04-17 14:09:53.985  2349  2349 D DCT-C: [0]onEnableApn: apnType=default, request type=NORMAL
//ims apn
04-17 14:10:01.354  2349  2349 D DCT-C: [0]onEnableApn: apnType=ims, request type=NORMAL

2.1.3 筛选apn

由于apns-conf.xml中我们配置了很多apn参数,同一个apn 类型可能有多个apn参数。以中国联通default类型为例:

  

  

apn参数列表中用户可以指定某个apn参数参与请求网络,这就产生了优先级。因此,buildWaitingApns()的作用就是把指定类型的所有apn参数都收集起来用ArrayList来保存,用户指定的apn参数放在首位,保证优先使用此apn参与请求网络过程。

public class DcTracker extends Handler {
    ...
    mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
    //注册监听sim卡的相关信息
    mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
    ...
    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
        new OnSubscriptionsChangedListener() {
            public final AtomicInteger mPreviousSubId =
                    new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
            /**
             * Callback invoked when there is any change to any SubscriptionInfo. Typically
             * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
             */
            @Override
            public void onSubscriptionsChanged() {
                if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
                // Set the network type, in case the radio does not restore it.
                int subId = mPhone.getSubId();
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    registerSettingsObserver();
                }
                if (mPreviousSubId.getAndSet(subId) != subId &&
                        SubscriptionManager.isValidSubscriptionId(subId)) {
                    onRecordsLoadedOrSubIdChanged();
                }
            }
    };
    ...
    private void onRecordsLoadedOrSubIdChanged() {
        if (DBG) log("onRecordsLoadedOrSubIdChanged: createAllApnList");
        mAutoAttachOnCreationConfig = mPhone.getContext().getResources()
                .getBoolean(com.android.internal.R.bool.config_auto_attach_data_on_creation);

        createAllApnList();
        setInitialAttachApn();
        if (mPhone.mCi.getRadioState().isOn()) {
            if (DBG) log("onRecordsLoadedOrSubIdChanged: notifying data availability");
            notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
        }
        setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);
    }
    ...
    private void setupDataOnConnectableApns(String reason) {
        setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);
    }

    private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
        if (VDBG) log("setupDataOnConnectableApns: " + reason);
        ...
        for (ApnContext apnContext : mPrioritySortedApnContexts) {
            //注释1
            if (apnContext.getState() == DctConstants.State.FAILED
                    || apnContext.getState() == DctConstants.State.SCANNING) {
                if (retryFailures == RetryFailures.ALWAYS) {
                    apnContext.releaseDataConnection(reason);
                } else if (apnContext.isConcurrentVoiceAndDataAllowed() == false &&
                        mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
                    // RetryFailures.ONLY_ON_CHANGE - check if voice concurrency has changed
                    apnContext.releaseDataConnection(reason);
                }
            }
            //注释2
            if (apnContext.isConnectable()) {
                log("isConnectable() call trySetupData");
                apnContext.setReason(reason);
                trySetupData(apnContext);
            }
        }
    }
    ...
    private boolean trySetupData(ApnContext apnContext) {
        ...
        DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
        //isDataAllowed sim卡加载、数据开关、默认数据选择、漫游、PS域是否注册成功
        boolean isDataAllowed = isDataAllowed(apnContext, dataConnectionReasons);
        ...
        apnContext.requestLog(logStr);
        if (isDataAllowed) {
            ...
            if (apnContext.getState() == DctConstants.State.IDLE) {
                //首次使用此apn类型的apnContext,需要加载waitingApns
                ArrayList waitingApns =
                        buildWaitingApns(apnContext.getApnType(), radioTech);
                if (waitingApns.isEmpty()) {
                    notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
                    notifyOffApnsOfAvailability(apnContext.getReason());
                    String str = "trySetupData: X No APN found retValue=false";
                    if (DBG) log(str);
                    apnContext.requestLog(str);
                    return false;
                } else {
                    apnContext.setWaitingApns(waitingApns);
                    ...
                }
            }
            //发起请求
            boolean retValue = setupData(apnContext, radioTech, dataConnectionReasons.contains(
                    DataAllowedReasonType.UNMETERED_APN));
            notifyOffApnsOfAvailability(apnContext.getReason());
            return retValue;
        } else {
            ...
        }
    }
    ...
    /*
     * Build a list of APNs to be used to create PDP's.
     *
     * @param requestedApnType
     * @return waitingApns list to be used to create PDP
     *          error when waitingApns.isEmpty()
     */
    private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
        ArrayList apnList = new ArrayList();
        if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
            ArrayList dunApns = fetchDunApns();
            if (dunApns.size() > 0) {
                for (ApnSetting dun : dunApns) {
                    apnList.add(dun);
                }
                return sortApnListByPreferred(apnList);
            }
        }

        IccRecords r = mIccRecords.get();
        String operator = (r != null) ? r.getOperatorNumeric() : "";

        // This is a workaround for a bug (7305641) where we don't failover to other
        // suitable APNs if our preferred APN fails.  On prepaid ATT sims we need to
        // failover to a provisioning APN, but once we've used their default data
        // connection we are locked to it for life.  This change allows ATT devices
        // to say they don't want to use preferred at all.
        boolean usePreferred = true;
        try {
            usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
                    internal.R.bool.config_dontPreferApn);
        } catch (Resources.NotFoundException e) {
            usePreferred = true;
        }
        if (usePreferred) {
            mPreferredApn = getPreferredApn();
        }
        ...
        if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
                mPreferredApn.canHandleType(requestedApnType)) {
            if (mPreferredApn.numeric.equals(operator)) {
                if (ServiceState.bitmaskHasTech(mPreferredApn.networkTypeBitmask,
                        ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
                    apnList.add(mPreferredApn);
                    apnList = sortApnListByPreferred(apnList);
                    if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
                    return apnList;
                } else {
                    setPreferredApn(-1);
                    mPreferredApn = null;
                }
            } else {
                setPreferredApn(-1);
                mPreferredApn = null;
            }
        }
        if (mAllApnSettings != null) {
            for (ApnSetting apn : mAllApnSettings) {
                if (apn.canHandleType(requestedApnType)) {
                    if (ServiceState.bitmaskHasTech(apn.networkTypeBitmask,
                            ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
                        apnList.add(apn);
                    } else {
                        ...
                } else if (DBG) {
                    ...
                }
            }
        } else {
            ...
        }

        apnList = sortApnListByPreferred(apnList);
        return apnList;
    }
    ...
}

public class ApnContext {
    ...
    /**
     * Check if the data call is in the state which allow connecting.
     * @return True if allowed, otherwise false.
     */
    public boolean isConnectable() {
        return isReady() && ((mState == DctConstants.State.IDLE)
                                || (mState == DctConstants.State.SCANNING)
                                || (mState == DctConstants.State.RETRYING)
                                || (mState == DctConstants.State.FAILED));
    }
    ...
}

筛选apn主要由两个方法完成:setupDataOnConnectableApns()和trySetupData() 。

setupDataOnConnectableApns 为筛选apn做准备工作:循环所有apn类型的ApnContext

注释1:如果循环到的ApnContext状态为FAILED和SCANNING,说明此apn类型的ApnContext先前使用过,且处于以下状态:一直尝试重连、数据不可用状态。重新加载waitingApns并与此ApnContext保存的waitingApns对比,如果不相同,说明apn发生了变化,执行releaseDataConnection()释放掉此ApnContext的连接。ApnContext的状态重新设置为Idle。 首次使用此apn类型的ApnContext,waitingApns为null

注释2:ApnContext必须为激活状态且处于IDLE,SCANNING,RETRYING,FAILED状态才可以trySetupData。

trySetupData 筛选出合适的apn和做条件判断 

2.1.4 发起请求

    /**
     * Setup a data connection based on given APN type.
     *
     * @param apnContext APN context
     * @param radioTech RAT of the data connection
     * @param unmeteredUseOnly True if this data connection should be only used for unmetered
     *                         purposes only.
     * @return True if successful, otherwise false.
     */
    private boolean setupData(ApnContext apnContext, int radioTech, boolean unmeteredUseOnly) {
        ...
        //创建DataConnection对象
        dcac = findFreeDataConnection();
        ...
        apnContext.setDataConnectionAc(dcac);
        apnContext.setApnSetting(apnSetting);
        //更新ApnContext的状态为CONNECTIN
        apnContext.setState(DctConstants.State.CONNECTING);
        mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());

        Message msg = obtainMessage();
        //DataConnection处理拨号过程结束,会通过EVENT_DATA_SETUP_COMPLETE事件通知DcTracker
        msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
        msg.obj = new Pair(apnContext, generation);
        //调用bringUp()发起下一步的拨号动作
        dcac.bringUp(apnContext, profileId, radioTech, unmeteredUseOnly, msg, generation);
        
        return true;
    }

2.2 请求网络的结果处理

2.2.1 网络请求成功

如果DataConnection处理网络请求过程结束,会通过EVENT_DATA_SETUP_COMPLETE事件通知DcTracker并调用onDataSetupComplete

    /**
     * A SETUP (aka bringUp) has completed, possibly with an error. If
     * there is an error this method will call {@link #onDataSetupCompleteError}.
     */
    private void onDataSetupComplete(AsyncResult ar) {

        DcFailCause cause = DcFailCause.UNKNOWN;
        boolean handleError = false;
        ApnContext apnContext = getValidApnContext(ar, "onDataSetupComplete");
        if (apnContext == null) return;
        if (ar.exception == null) {
            DcAsyncChannel dcac = apnContext.getDcAc();
            ...
            if (dcac == null) {
                log("onDataSetupComplete: no connection to DC, handle as error");
                cause = DcFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
                handleError = true;
            } else {
                ApnSetting apn = apnContext.getApnSetting();
                if (apn != null && apn.proxy != null && apn.proxy.length() != 0) {
                    try {
                        String port = apn.port;
                        //拨号成功,通过DcAsyncChannel设置proxy,如果apn参数中proxy为空,默认为8080
                        if (TextUtils.isEmpty(port)) port = "8080";
                        ProxyInfo proxy = new ProxyInfo(apn.proxy,
                                Integer.parseInt(port), null);
                        dcac.setLinkPropertiesHttpProxySync(proxy);
                    } catch (NumberFormatException e) {
                        ...
                    }
                }
                // everything is setup
                if (TextUtils.equals(apnContext.getApnType(), PhoneConstants.APN_TYPE_DEFAULT)) {
                    try {
                        SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "true");
                    } catch (RuntimeException ex) {
                        ...
                    }
                    if (mCanSetPreferApn && mPreferredApn == null) {
                        if (mPreferredApn != null) {
                            //设置默认的apn参数
                            setPreferredApn(mPreferredApn.id);
                        }
                    }
                } else {
                    ...
                }
                // A connection is setup
                //设置ApnContext的状态为CONNECTED
                apnContext.setState(DctConstants.State.CONNECTED);
                ...
                if ((!isProvApn) || mIsProvisioning) {
                    // Hide any provisioning notification.
                    cm.setProvisioningNotificationVisible(false, ConnectivityManager.TYPE_MOBILE,
                            mProvisionActionName);
                    // Complete the connection normally notifying the world we're connected.
                    // We do this if this isn't a special provisioning apn or if we've been
                    // told its time to provision.
                    //调用completeConnection()实现:通知Phone拨号成功
                    completeConnection(apnContext);
                } else {
                    ...
                }
                ...
            }
        } else {
            ...
        }
        ...
    }

周期读取底层文件,判断终端是否有接收和发送数据包,启动一个线程,更新UI界面,主要是上下行图标,以及流量结算页面。

    private void completeConnection(ApnContext apnContext) {
        ...
        mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
        startNetStatPoll();
        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
    }

Android P ConnectivityService框架----数据相关业务1_第4张图片

2.2.2  数据重连

当网络请求发生异常时,framework层会根据异常的类型去重新建立数据连接。 
目前的数据重连主要有四个方面: 
①.SETUP_DATA_CALL成功,但是携带返回的数据异常。 
②.EVENT_DISCONNECT_DONE数据连接断开后,尝试重新连接。 
③.隔一段时间收不到数据包后触发doRecover机制 
④.pdp链路中断 
以上的数据重连,最后都是使用了startAlarmForReconnect()方法,借助于定时器,间隔一定的时间重新发起连接。
 

DcTracker.java

    private void startAlarmForReconnect(long delay, ApnContext apnContext) {
        String apnType = apnContext.getApnType();

        Intent intent = new Intent(INTENT_RECONNECT_ALARM + "." + apnType);
        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

        // Get current sub id.
        int subId = mPhone.getSubId();
        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);

        PendingIntent alarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0,
                                        intent, PendingIntent.FLAG_UPDATE_CURRENT);
        apnContext.setReconnectIntent(alarmIntent);

        // Use the exact timer instead of the inexact one to provide better user experience.
        // In some extreme cases, we saw the retry was delayed for few minutes.
        // Note that if the stated trigger time is in the past, the alarm will be triggered
        // immediately.
        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime() + delay, alarmIntent);
    }
    
    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)) {
                if (DBG) log("Reconnect alarm. Previous state was " + mState);
                onActionIntentReconnectAlarm(intent);
            } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
                if (DBG) log("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 (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
                    setDefaultDataRoamingEnabled();
                }
            } else {
                if (DBG) log("onReceive: Unknown action=" + action);
            }
        }
    };
    
    private void onActionIntentReconnectAlarm(Intent intent) {
        Message msg = obtainMessage(DctConstants.EVENT_DATA_RECONNECT);
        msg.setData(intent.getExtras());
        sendMessage(msg);
    }

①.SETUP_DATA_CALL成功,但是携带返回的数据异常。

如果返回的结果ar.exception为空,且DataConnection的对象dcac 为空,说明DcTracker与DataConnection的交互出了问题,设置handleError为true,启动重连机制。如果ar.exception不为空,说明请求的结果有误,通知上层并标记请求失败的apn,启动重连机制。

    /**
     * A SETUP (aka bringUp) has completed, possibly with an error. If
     * there is an error this method will call {@link #onDataSetupCompleteError}.
     */
    private void onDataSetupComplete(AsyncResult ar) {

        DcFailCause cause = DcFailCause.UNKNOWN;
        boolean handleError = false;
        ApnContext apnContext = getValidApnContext(ar, "onDataSetupComplete");

        if (apnContext == null) return;

        if (ar.exception == null) {
            DcAsyncChannel dcac = apnContext.getDcAc();
            ...
            if (dcac == null) {
                log("onDataSetupComplete: no connection to DC, handle as error");
                cause = DcFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
                handleError = true;
            } else {
                ...
            }
        } else {
            ...
            //通知上层SETUP_DATA_CALL失败
            ApnSetting apn = apnContext.getApnSetting();
            mPhone.notifyPreciseDataConnectionFailed(apnContext.getReason(),
                    apnContext.getApnType(), apn != null ? apn.apn : "unknown", cause.toString());

            // 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);
            //是否重新启动Radio
            if (cause.isRestartRadioFail(mPhone.getContext(), mPhone.getSubId()) ||
                    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.
            //标记SETUP_DATA_CALL失败的apn
            if (isPermanentFailure(cause)) {
                log("cause = " + cause + ", mark apn as permanent failed. apn = " + apn);
                apnContext.markApnPermanentFailed(apn);
            }

            handleError = true;
        }

        if (handleError) {
            onDataSetupCompleteError(ar);
        }
    }

尝试使用下一个合适的apn发起重连,delay 值取决于当前waitingApns。如果已经没有合适的apn发起重连,则apnContext设置状态为FAILED并通知上层SETUP_DATA_CALL失败。

    /**
     * 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()}.
     */
    private void onDataSetupCompleteError(AsyncResult ar) {

        ApnContext apnContext = getValidApnContext(ar, "onDataSetupCompleteError");

        if (apnContext == null) return;

        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);
            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);
        } else {
            // 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.");
        }
    }

②.EVENT_DISCONNECT_DONE数据连接断开后,尝试重新连接。 

    /**
     * Called when EVENT_DISCONNECT_DONE is received.
     */
    private void onDisconnectDone(AsyncResult ar) {
        ApnContext apnContext = getValidApnContext(ar, "onDisconnectDone");
        if (apnContext == null) return;

        if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE apnContext=" + apnContext);
        apnContext.setState(DctConstants.State.IDLE);

        mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());

        // if all data connection are gone, check whether Airplane mode request was
        // pending.
        if (isDisconnected()) {
            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
                apnContext.setApnSetting(null);
                apnContext.setDataConnectionAc(null);

                // Need to notify disconnect as well, in the case of switching Airplane mode.
                // Otherwise, it would cause 30s delayed to turn on Airplane mode.
                if (mDisconnectPendingCount > 0) {
                    mDisconnectPendingCount--;
                }

                if (mDisconnectPendingCount == 0) {
                    notifyDataDisconnectComplete();
                    notifyAllDataDisconnected();
                }
                return;
            }
        }
        // If APN is still enabled, try to bring it back up automatically
        //判断是否符合重连条件,并设置重连的间隔事件
        //mAttached.get() :当前仍然为attach状态 
        //apnContext.isReady() :apnContext仍然为激活状态 
        //retryAfterDisconnected(apnContext) :radio没有关闭 
        if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
            try {
                SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "false");
            } catch (RuntimeException ex) {
                log("Failed to set PUPPET_MASTER_RADIO_STRESS_TEST to false");
            }
            // 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");
            //getRetryAfterDisconnectDelay() : 重连的时间间隔 
            long delay = apnContext.getRetryAfterDisconnectDelay();
            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);
            }
        } else {
            ...
        }
        ...
    }

③.隔一段时间收不到数据包后触发doRecover机制 

SETUP_DATA_CALL成功后,在DcTracker.java的completeConnection()中会启动一个线程,周期性读取底层文件,判断一个时间段内手机的收发包情况,如果隔一段时间收不到数据包,将会触发doRecover机制。 

Android P ConnectivityService框架----数据相关业务1_第5张图片

    // Default for the data stall alarm while non-aggressive stall detection
    private static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
    // Default for the data stall alarm for aggressive stall detection
    private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;

    private void completeConnection(ApnContext apnContext) {
        ...
        startNetStatPoll();
        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
    }
    
    private void startDataStallAlarm(boolean suspectedStall) {
        int nextAction = getRecoveryAction();
        int delayInMs;

        if (mDataStallDetectionEnabled && getOverallState() == DctConstants.State.CONNECTED) {
            // 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);
            }

            mDataStallAlarmTag += 1;
            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,
                    SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
        } else {
            ...
        }
    }

    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)) {
                ...
            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                ...
            } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
                onActionIntentDataStallAlarm(intent);
            }
            ...
        }
    };
    
    private void onActionIntentDataStallAlarm(Intent intent) {
        Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
                intent.getAction());
        msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
        sendMessage(msg);
    }
    
    @Override
    public void handleMessage (Message msg) {
        switch (msg.what) {
            ...
            case DctConstants.EVENT_DATA_STALL_ALARM:
                onDataStallAlarm(msg.arg1);
                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);
        boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
        if (mSentSinceLastRecv >= hangWatchdogTrigger) {
            suspectedStall = DATA_STALL_SUSPECTED;
            //调用doRecover
            sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
        } else {
            ...
        }
        startDataStallAlarm(suspectedStall);
    }

 正常情况下,TX/RX都不为零,AP每隔一定时间向modem发起GET_DATA_CALL_LIST查询相关信息。如果TX/RX异常,则依次循环以下的case事件(startDataStallAlarm是循环的),clean所有的旧连接,重新注册网络,重启radio,直到TX/RX正常。

   private void doRecovery() {
        if (getOverallState() == DctConstants.State.CONNECTED) {
            // Go through a series of recovery steps, each action transitions to the next action
            final int recoveryAction = getRecoveryAction();
            TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
            broadcastDataStallDetected(recoveryAction);

            switch (recoveryAction) {
                case RecoveryAction.GET_DATA_CALL_LIST:
                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
                            mSentSinceLastRecv);
                    if (DBG) log("doRecovery() get data call list");
                    mDataServiceManager.getDataCallList(obtainMessage());
                    putRecoveryAction(RecoveryAction.CLEANUP);
                    break;
                case RecoveryAction.CLEANUP:
                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
                            mSentSinceLastRecv);
                    if (DBG) log("doRecovery() cleanup all connections");
                    cleanUpAllConnections(Phone.REASON_PDP_RESET);
                    putRecoveryAction(RecoveryAction.REREGISTER);
                    break;
                case RecoveryAction.REREGISTER:
                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
                            mSentSinceLastRecv);
                    if (DBG) log("doRecovery() re-register");
                    mPhone.getServiceStateTracker().reRegisterNetwork(null);
                    putRecoveryAction(RecoveryAction.RADIO_RESTART);
                    break;
                case RecoveryAction.RADIO_RESTART:
                    EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
                            mSentSinceLastRecv);
                    if (DBG) log("restarting radio");
                    restartRadio();
                    putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
                    break;
                default:
                    throw new RuntimeException("doRecovery: Invalid recoveryAction="
                            + recoveryAction);
            }
            mSentSinceLastRecv = 0;
        }
    }

详细流程如下: 

Android P ConnectivityService框架----数据相关业务1_第6张图片

由此可见,这里的数据重连也是依据于onDisconnectDone()方法实现,与第二种数据重连的实现方式是一样的,只不过是其触发的原理不一样。

三、 DataConnection

DcTracker与DataConnection是通过DcAsyncChannel来通讯的。DataConnection是一个状态机,新创建的对象,其状态初始化在DcDefaultState,初始化结束后运行在DcInactiveState状态,因此从DcAsyncChannel发出bringUp()拨号后,会在DcInactiveState处理DataConnection.EVENT_CONNECT事件。DataConnection各状态机的流程参见本篇第二章图,有详细的留下。


 

你可能感兴趣的:(Android P ConnectivityService框架----数据相关业务1)