二、Android 數(shù)據(jù)鏈接的動態(tài)分析

經(jīng)過前面這些過程,網(wǎng)絡(luò)連接所需要的條件就全部準(zhǔn)備就緒,接下來就是等待網(wǎng)絡(luò)接入
把網(wǎng)絡(luò)接入過程簡單分為三個階段
觸發(fā)階段
----該階段是由各種不同事件觸發(fā)的,比如SIM載入完畢、PS域Attach成功、通話結(jié)束、APN改變等,該階段的最終都是要調(diào)用setupDataOnConnectableApns()方法;
準(zhǔn)備連接階段
----該階段是指,在DcTracker收到建立連接的請求之后,需要進行一系列有效性檢測,比如APN是否已經(jīng)激活、PS是否已經(jīng)就緒、用戶是否打開網(wǎng)絡(luò)開關(guān)等,然后創(chuàng)建/找到核實的DataConnection對象,準(zhǔn)備發(fā)起連接請求。
發(fā)送連接命令階段
----該階段是指,在DataConnection收到DcTracker的請求之后,將請求轉(zhuǎn)交給RILJ的過程,RILJ通過socket將請求發(fā)送到了RILD,RILD根據(jù)請求類型再將AT指令發(fā)送到Modem層,由底層完成信令的發(fā)送和接收。簡單概括就是如下步驟,此處創(chuàng)建的DcAsyncChannel是雙向的。


DcAsyncChannel

1、觸發(fā)條件

//漫游打開關(guān)閉(EVENT_ROAMING_ON/EVENT_ROAMING_OFF事件)
static final String REASON_ROAMING_ON = "roamingOn";  
static final String REASON_ROAMING_OFF = "roamingOff";  
//PS attach(STATE_IN_SERVICE)
static final String REASON_DATA_ATTACHED = "dataAttached";  
//APN Changed(EVENT_APN_CHANGED)
static final String REASON_APN_CHANGED = "apnChanged";  
//通話結(jié)束  
static final String REASON_VOICE_CALL_ENDED = "2GVoiceCallEnded";  
//SIM載入完畢 (EVENT_RECORDS_LOADED 事件)
static final String REASON_SIM_LOADED = "simLoaded";  
//網(wǎng)絡(luò)模式改變 (EVENT_DATA_RAT_CHANGED 事件)
static final String REASON_NW_TYPE_CHANGED = "nwTypeChanged";  

2、有效性及狀態(tài)檢查##

在觸發(fā)條件滿足后都會調(diào)用setupDataOnConnectableApns(String reason)
之后會調(diào)用trySetupData,在trySetupData里面會進行有效性檢測

    private boolean trySetupData(ApnContext apnContext, ArrayList<ApnSetting> waitingApns) {
        // Allow SETUP_DATA request for E-APN to be completed during emergency call and MOBILE DATA On/Off cases as well.
        boolean isEmergencyApn = apnContext.getApnType().equals(PhoneConstants.APN_TYPE_EMERGENCY);
        final ServiceStateTracker sst = mPhone.getServiceStateTracker();
        boolean desiredPowerState = sst.getDesiredPowerState();
        boolean checkUserDataEnabled = !(apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS));

        if (apnContext.isConnectable() && (isEmergencyApn ||
                (isDataAllowed(apnContext) &&
                getAnyDataEnabled(checkUserDataEnabled) && !isEmergency()))) {

            if (apnContext.getState() == DctConstants.State.FAILED) {
                apnContext.setState(DctConstants.State.IDLE);
            }
            int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
            apnContext.setConcurrentVoiceAndDataAllowed(sst.isConcurrentVoiceAndDataAllowed());
            if (apnContext.getState() == DctConstants.State.IDLE) { 
                if (waitingApns == null) {
                    waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
                }
                if (waitingApns.isEmpty()) {
                    notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
                    notifyOffApnsOfAvailability(apnContext.getReason());
                    return false; 
                } else {
                    apnContext.setWaitingApns(waitingApns);
                } 
            }
            boolean retValue = setupData(apnContext, radioTech);
            notifyOffApnsOfAvailability(apnContext.getReason());

            return retValue;
        }
}

條件1:apnContext.isConnectable()

    public boolean isConnectable() {
        return isReady() && ((mState == DctConstants.State.IDLE)
                                || (mState == DctConstants.State.SCANNING)
                                || (mState == DctConstants.State.RETRYING)
                                || (mState == DctConstants.State.FAILED));
    }   

其中isReady()就是檢查APN type有沒有使能,所以在數(shù)據(jù)鏈接之前一定要調(diào)用ApnContext的setEnabled()將要打開的數(shù)據(jù)類型對應(yīng)的APN使能;然后就是檢查APN的狀態(tài),這個狀態(tài)會在DataConnection狀態(tài)機隨著聯(lián)網(wǎng)狀態(tài)的變遷而改變。
條件2:isEmergencyApn 是否是緊急APN
條件3:isDataAllowed(apnContext)

  private boolean isDataAllowed(ApnContext apnContext) {
        return apnContext.isReady() && isDataAllowed(); 
    }

    protected boolean isDataAllowed() {
        final boolean internalDataEnabled;
        synchronized (mDataEnabledLock) {
            internalDataEnabled = mInternalDataEnabled;
        }

        boolean attachedState = mAttached.get();
        boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
        IccRecords r = mIccRecords.get();
        boolean recordsLoaded = false;
        if (r != null) {
            recordsLoaded = r.getRecordsLoaded();
        }

        //FIXME always attach
        boolean psRestricted = mIsPsRestricted;
        int phoneNum = TelephonyManager.getDefault().getPhoneCount();
        if (phoneNum > 1) {
            attachedState = true;
            psRestricted = false;
        }
        int dataSub = SubscriptionManager.getDefaultDataSubId();
        boolean defaultDataSelected = SubscriptionManager.isValidSubscriptionId(dataSub);
        PhoneConstants.State state = PhoneConstants.State.IDLE;
        if (mPhone.getCallTracker() != null) {
            state = mPhone.getCallTracker().getState(); 
        }            
        boolean allowed =
                    (attachedState || mAutoAttachOnCreation) &&
                    recordsLoaded &&
                    (state == PhoneConstants.State.IDLE ||
                     mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) &&
                    internalDataEnabled &&
                    defaultDataSelected &&
                    (!mPhone.getServiceState().getDataRoaming() || getDataOnRoamingEnabled()) &&
                    //!mIsPsRestricted &&
                    !psRestricted &&
                    desiredPowerState;

----判斷是否已經(jīng)ATTACH成功,SIM是否初始化完畢,當(dāng)前手機服務(wù)是否支持,漫游下是否允許上網(wǎng)等
條件4:getAnyDataEnabled(checkUserDataEnabled)
----該條件主要判斷用戶是否打開了數(shù)據(jù)開關(guān)
這部分代碼很混亂,如果出現(xiàn)問題得一一排查

3、找到可用的APN

其中waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech)便是找到可用的APN
buildWaitingApns中主要的邏輯有兩個:
1、如果用戶是否設(shè)置了Preferred,該值通過以下代碼讀取的:

        usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
                internal.R.bool.config_dontPreferApn);

如果用戶設(shè)置了Preferred APN,并且mPreferredApn不為空(mPreferredApn來自于數(shù)據(jù)業(yè)務(wù)APN參數(shù)的創(chuàng)建createAllApnList中保存的,例如用戶手動選擇了APN TYPE那么這個APN就會被保留下來)因此此時waitingApns就會等于mPreferredApn
2、如果用戶沒有設(shè)置Preferred APN,那么就會在mAllApnSettings(來自于createAllApnList)中去尋找可用的APN,并將所有的可用的APN放在waitingApns中
最終聯(lián)網(wǎng)采用的是waitingApns0

4、找到/創(chuàng)建DataConnection狀態(tài)機

    private boolean setupData(ApnContext apnContext, int radioTech) {
        if (dcac == null) {
            dcac = findFreeDataConnection();
            if (dcac == null) {
                dcac = createDataConnection();
            }
        }
        apnContext.setDataConnectionAc(dcac);
        apnContext.setApnSetting(apnSetting);
        apnContext.setState(DctConstants.State.CONNECTING);
        mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());

        Message msg = obtainMessage();
        msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
        msg.obj = apnContext;
        dcac.bringUp(apnContext, getInitialMaxRetry(), profileId, radioTech, mAutoAttachOnCreation,msg);

        return true;

這個方法內(nèi)部主要完成了兩個任務(wù):
1、更新當(dāng)前APN參數(shù)的狀態(tài)并把狀態(tài)發(fā)送到系統(tǒng)中(還是通過notifyDataConnection()來完成)
2、通過DcAsyncChannel的bringUp()方法發(fā)起連接請求
這里顯示通過findFreeDataConnection()方法搜索可用的DcAsyncChannel,找不到的話就通過createDataConnection()創(chuàng)建,如果沒有找到,就需要創(chuàng)建新的DcAsyncChannel
/** Return the DC AsyncChannel for the new data connection */
private DcAsyncChannel createDataConnection() {

    int id = mUniqueIdGenerator.getAndIncrement();
    DataConnection conn = DataConnection.makeDataConnection(mPhone, id,this, mDcTesterFailBringUpAll, mDcc);
    mDataConnections.put(id, conn);
    DcAsyncChannel dcac = new DcAsyncChannel(conn, LOG_TAG);
    int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
    if (status == AsyncChannel.STATUS_SUCCESSFUL) {
        mDataConnectionAcHashMap.put(dcac.getDataConnectionIdSync(), dcac); //將創(chuàng)建好的DcAsyncChannel保存起來,以便下一次鏈接的時候可以找到可用的DcAsyncChannel
    }
    return dcac;
}

創(chuàng)建DcAsyncChannel通道,是鏈接DcTracker和DataConnection的通道,但是他們不在同一個線程中,在DcTrackerBase的構(gòu)造中new了一個新的HandlerThread,并將looper給了DcController和DataConnection。
為什么要闖將DcAsyncChannel通道,而不直接將DcTracker消息傳到DataConnection中去呢?
因為在DcTracker中有需要同步獲取DataConnection中的情況,比如在findFreeDataConnection中調(diào)用了

dcac.isInactiveSync()
    private DcAsyncChannel findFreeDataConnection() {
        for (DcAsyncChannel dcac : mDataConnectionAcHashMap.values()) {
            if (dcac.isInactiveSync() && dataConnectionNotInUse(dcac)) {
                return dcac;
            }   
        }
        return null;
    }

    public boolean isInactiveSync() {
        boolean value;
        if (isCallerOnDifferentThread()) {
            Message response = sendMessageSynchronously(REQ_IS_INACTIVE);
            if ((response != null) && (response.what == RSP_IS_INACTIVE)) {
                value = rspIsInactive(response);
            } else {
                value = false;
            }
        } else {
            value = mDc.getIsInactive();
        }   
        return value;
    }  

其中的sendMessageSynchronously(REQ_IS_INACTIVE);即需要獲取DataConnection中的是否處于INACTIVE狀態(tài),這不能是異步的。

5、通過DcAsyncChannel將消息傳入狀態(tài)機

將apnContext通過dcac傳入DataConnection狀態(tài)機進行數(shù)據(jù)連接

dcac.bringUp(apnContext, getInitialMaxRetry(), profileId, radioTech, mAutoAttachOnCreation,msg);

6、狀態(tài)機狀態(tài)輪轉(zhuǎn)

DataConnection默認(rèn)狀態(tài)是DcInactiveState,首先會在DcInactiveState調(diào)用onConnect去調(diào)用setupDataCall

private void onConnect(ConnectionParams cp) {
    // msg.obj will be returned in AsyncResult.userObj;
    Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
    msg.obj = cp; 

    int authType = mApnSetting.authType;
    if (authType == -1) {
        authType = TextUtils.isEmpty(mApnSetting.user) ? RILConstants.SETUP_DATA_AUTH_NONE
                : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
    }

    String protocol;
    if (mPhone.getServiceState().getDataRoaming()) {
        protocol = mApnSetting.roamingProtocol;
    } else {    
        protocol = mApnSetting.protocol;
    }

    mPhone.mCi.setupDataCall(
            Integer.toString(cp.mRilRat + 2),
            Integer.toString(cp.mProfileId),
            mApnSetting.apn, mApnSetting.user, mApnSetting.password,
            Integer.toString(authType),
            protocol, msg);
}

在onConnect會包裝一個EVENT_SETUP_DATA_CONNECTION_DONE消息在msg中,這個消息會一并發(fā)往RILJ->RILD,當(dāng)RILD返回響應(yīng)時會攜帶這消息并發(fā)出來,DataConnection狀態(tài)機的DcActivatingState會撲捉到此消息進行狀態(tài)機輪狀,然后這里面全是狀態(tài)機輪狀來維護網(wǎng)絡(luò)的鏈接狀態(tài):


正常打開
正常斷開

至此數(shù)據(jù)連接的發(fā)起過程完畢,但是理上網(wǎng)還有很遠(yuǎn),后續(xù)工作還要更新路由表等等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,079評論 6 13
  • 再回到客廳后,蘇菲站在五斗柜上方的鏡子前。鏡面已經(jīng)失去光澤,而且刮痕累累,因此她在鏡中的影像也顯得模糊不清。蘇菲開...
    Tianjincui_閱讀 268評論 0 0
  • 香檳代表著喜氣、熱鬧、歡快。F1賽事頒獎,冠軍車手不斷晃動香檳瓶,嘭的一聲,香檳洶涌噴薄而出,灑滿歡呼的人群;公司...
    阿中2016閱讀 317評論 0 0
  • 妺(mò)喜,姓嬉(喜),生卒年不詳,亦作妺嬉、末喜[1] 、末嬉,有施氏之女,夏朝最后一位君主夏桀的王后。 傳說...
    兜兜說閱讀 505評論 0 1