Android建立GPRS通信的流程

(1).應用程序中的入口

從settings應用程序中,選擇移動網絡,進入到 Phone應用程序的移動網絡設置界面 (actionbar的應用程序圖標可以明確看出,開始的時候以爲還是在 settings程序中,直接去找,找了好半天也沒有找到,最後纔想起根據 settings列表的點擊事件去查看執行了什麼操作,才發現已經進入了 Phone程序,看來很多東西不能只憑眼睛看到的就下結論 ),在這裏啓用數據連接。對應源碼爲:
alps/packages/apps/Phone/src/com/android/phone/MobileNetworkSettings.java
現看看該類的註釋:
Mobile network settings” screen. This preference screen lets you enable/disable mobile data, and control data
roaming and other network-specific mobile data features. It’s used on non-voice-capable tablets as well as regular
phone devices. Note that this PreferenceActivity is part of the phone app, even though you reach it from the “Wireless
& Networks” section of the main Settings app. It’s not part of the “Call settings” hierarchy that’s available from the
Phone app (see CallFeaturesSetting for that.)
移動網絡設置界面。此首選項允許啓用禁用移動數據網絡,並且控制數據漫遊以及其他指定的移動數據網絡。它是用於 non-voice-capable平板電腦以及常規電話設備。 注意,這個PreferenceActivity 是phone應用程序 的一部分,即使你是通過 settings程序的“無線&網絡”選項到達這裏。這不是” Call Setting”層次結構的一部分。
//String keys for preference lookup
private static final String BUTTON_DATA_ENABLED_KEY = “button_data_enabled_key” ; //啓用數據網絡的key
private static final String BUTTON_DATA_USAGE_KEY = “button_data_usage_key” ;
private static final String BUTTON_PREFERED_NETWORK_MODE = “preferred_network_mode_key” ;
private static final String BUTTON_ROAMING_KEY = “button_roaming_key”;
private static final String BUTTON_CDMA_LTE_DATA_SERVICE_KEY = “cdma_lte_data_service_key” ;

通過 BUTTON_DATA_ENABLED_KEY 找到相應的 CheckBoxPreference,在onCreate() 方法中有:
@Override
protected void onCreate(Bundle icicle) {
super .onCreate(icicle);

    addPreferencesFromResource(R.xml.network_setting);

……
mDataConnPref = (DefaultSimPreference) prefSet.findPreference( KEY_DATA_CONN);
mDataConnPref .setOnPreferenceChangeListener(this);
mButtonDataEnabled = (CheckBoxPreference) prefSet.findPreference(BUTTON_DATA_ENABLED_KEY );
mButtonDataRoam = (CheckBoxPreference) prefSet.findPreference(BUTTON_ROAMING_KEY);
mButtonDataRoam .setSummaryOn(mExtension .getRoamingSummary( this,R.string.roaming_enable));
mButtonDataRoam .setSummaryOff(mExtension .getRoamingSummary( this,R.string.roaming_disable));
mButtonPreferredNetworkMode = (ListPreference) prefSet.findPreference(
BUTTON_PREFERED_NETWORK_MODE );
mButtonDataUsage = prefSet.findPreference(BUTTON_DATA_USAGE_KEY);
……
}
從oncreate方法中看到 mButtonDataEnabled 沒有註冊 PreferenceChangeListener事件,因此將不會觸發onPreferenceChange()方法,僅觸發 onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference)方法。在 preferenceActivity中如果監聽了PreferenceChangeListener和 OnPreferenceClickListener,那麼這onPreferenceTreeClick ,onPreferenceChange, onPreferenceClick三者的觸發是怎麼回事呢?

a.先調用onPreferenceClick() 方法,如果該方法返回 true,則不再調用onPreferenceTreeClick方法 ; 如果 onPreferenceClick方法返回false ,則繼續調用 onPreferenceTreeClick方法。
b.onPreferenceChange的方法獨立與其他兩種方法的運行。也就是說,它總是會運行。
補充:點擊某個Preference 控件後,會先回調 onPreferenceChange()方法,即是否保存值,然後再回調 onPreferenceClick以及onPreferenceTreeClick() 方法,因此在 onPreferenceClick/onPreferenceTreeClick方法中我們得到的控件值就是最新的 Preference控件值。
詳細可以看: http://blog.csdn.net/qinjuning/article/details/6710003

接下來看看當前 MobileNetworkSettings的onPreferenceTreeClick 方法:
public boolean onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference) {

……..
else if (preference == mButtonDataEnabled) {
if (DBG ) {
log(“onPreferenceTreeClick: preference == mButtonDataEnabled.”);
}
///M: change the interface definition for consistent_UI
if (!mExtension .dataEnableReminder(mButtonDataEnabled.isChecked(), this)) {
Log.d( LOG_TAG, “onPreferenceTreeClick: preference == mButtonDataEnabled.” );
if (mButtonDataEnabled .isChecked() && isSimLocked()) {
mCellConnMgr.handleCellConn(0, PIN1_REQUEST_CODE);
Log.d( LOG_TAG, “Data enable check change request pin single card” );
mButtonDataEnabled.setChecked( false);
} else {
mIsChangeData = true ;
NetworkInfo networkInfo = mConnService.getActiveNetworkInfo();
if (!(networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI
&& networkInfo.isConnected())) {
showDialog( PROGRESS_DIALOG);
}
mConnService.setMobileDataEnabled( mButtonDataEnabled .isChecked());
mH.sendMessageDelayed( mH .obtainMessage( DATA_STATE_CHANGE_TIMEOUT), 30000);
if (mButtonDataEnabled .isChecked() &&
isNeedtoShowRoamingMsg()) {
mExtension.showWarningDlg( this,R.string.data_conn_under_roaming_hint);
}
///M: add for ATT requirement
mExtension.disableDataRoaming( mButtonDataRoam , mButtonDataEnabled.isChecked());
}
}
return true ;
}
…….
}

mConnService .setMobileDataEnabled( mButtonDataEnabled .isChecked()); 這裏mConnService是 ConnectivityManager的實例,從這個方法之後,數據的啓用將進入到 framework層.根據是否check決定開啓或關閉數據網絡。

(2).ConnectivityManager 中的setMobileDataEnabled(boolean)的調用實際是 ConnectivityService中的SetMobileDataEnable 方法
public void setMobileDataEnabled( boolean enabled) {
try {
mService.setMobileDataEnabled(enabled);
} catch (RemoteException e) {
}
}

(3).ConnectivityService 纔是實際的網絡管理核心。(標識 MTK的部分是MTK 進行的修改,這裏可以跳過不看)該方法中先通過 enforceChangePermission()對調用程序進行是否有改變網絡狀態的權限的檢查,如果沒有,應用程序將會拋出異常被終止掉。然後發送消息 EVENT_SET_MOBILE_DATA(Message.what), 該消息將被內部類 InternalHandler的handleMessgae() 方法進行處理。
(/frameworks/base/services/java/com/android/server/ConnectivityService.java)

public void setMobileDataEnabled( boolean enabled) {
// M: To check permission for Mobile Manager Service/Application. @{
if (FeatureOption .MTK_MOBILE_MANAGEMENT) {
if (enabled && !checkMoMSSubPermission(SubPermissions.CHANGE_NETWORK_STATE_ON)) {
Slog. e(TAG, “setMobileDataEnabled(” + enabled + “) is lack of permission CHANGE_NETWORK_STATE_ON”);
return;
}
}
// @}
enforceChangePermission();
// MTK start
if (FeatureOption.MTK_GEMINI_SUPPORT) {
int curSlotId = Settings.System.getInt( mContext.getContentResolver(), Settings.System.GPRS_CONNECTION_SETTING , Settings.System.GPRS_CONNECTION_SETTING_DEFAULT ) - 1;
Log. e(“SJ”, “setMobileDataEnabled(” + enabled + “): curSlotId=” + curSlotId);
if (DBG ) Slog.d ( TAG, “setMobileDataEnabled(” + enabled + “): curSlotId=” + curSlotId);
if (enabled && (curSlotId == SimInfo. SLOT_NONE)) {
try{
mITelephony = getITelephony();
if( mITelephony == null){
Slog. e(TAG, “NULL in mITelephony”);
return;
}
for (int simId=PhoneConstants.GEMINI_SIM_1; simId

}

public boolean getAnyDataEnabled() {
final boolean result;
synchronized (mDataEnabledLock ) {
mUserDataEnabled = Settings.Global.getInt(
mPhone. getContext().getContentResolver(), Settings.Global. MOBILE_DATA, 1) == 1;
result = ( mInternalDataEnabled && sPolicyDataEnabled
&& ( mUserDataEnabled
|| ( mRequestedApnType.equals(PhoneConstants. APN_TYPE_MMS) && dataEnabled[DctConstants.APN_MMS_ID]))
&& ( enabledCount != 0));
}
if (!result && DBG ) log(“getAnyDataEnabled ” + result);
return result;
}
(9).調用onTrySetupData(String reason) 方法是DataConnectionTracker定義的抽象方法,將由其子類進行具體的實現。 DataConnectionTracker有2 個子類,分別是: GsmDataConnectionTracker和CdmaDataConnectionTracker 。這裏將根據 sim支持的網絡制式進行分支了。
(frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java)
(frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java )

 GsmDataConnectionTracker 中的實現如下 :
 protected boolean onTrySetupData(String reason) {
    if (DBG ) log("onTrySetupData: reason=" + reason);     
    setupDataOnReadyApns(reason);
    return true ;
}
 CdmaDataConnectionTracker中實現如下:

protected boolean onTrySetupData(String reason) {
return trySetupData(reason);
}
兩個分支中都是又調了其他的方法。 GsmDataConnectionTracker比CdmaDataConnectionTracker 多調了setupDataOnReadyApns方法,之後也會調用 trySetupdata()方法。

(10).先來看GsmDataConnectionTracker 分支,setupDataOnReadyApns(String reason)方法:根據當中的註釋可知:這裏循環遍歷 mDataConnectionAsyncChannels 停止所有的數據鏈接的重連嘗試鬧鐘。此後再一個遍歷將 apnContext重置狀態爲idle ,以便能夠執行到 trySetupdata()方法。
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 apns = dcac.getApnListSync();

            boolean hasDefault = false ;
            for (ApnContext apnContext : apns) {
                if (apnContext.getApnType().equals(PhoneConstants. APN_TYPE_DEFAULT)) {
                    hasDefault = true;
                    break;
                }
            }
            configureRetry(dcac. dataConnection, hasDefault, 0);
        }
    }
    // Be sure retry counts for Apncontexts and DC's are sync'd.
    // When DCT/ApnContexts are refactored and we cleanup retrying
    // this won't be needed.
    resetAllRetryCounts();
    // Only check for default APN state
    for (ApnContext apnContext : mApnContexts .values()) {
        if (apnContext.getState() == DctConstants.State. FAILED) {
            // By this time, alarms for all failed Apns
            // should be stopped if any.
            // Make sure to set the state back to IDLE
            // so that setup data can happen.
            apnContext.setState(DctConstants.State. IDLE);
        }
        if (apnContext.isReady()) {
            if (apnContext.getState() == DctConstants.State. IDLE ||
                    apnContext.getState() == DctConstants.State. SCANNING) {
                            Log. e("SJ", "setupDataOnReadyApns-->apnType" +apnContext.getApnType()+ "apnContext "+apnContext.toString()); 
                            apnContext.setReason(reason);
                            Log. e("SJ", "then enter trySetupData()" );
                trySetupData(apnContext);
            }
        }
    }
}

(11).Gsm的trySetupData 看起來感覺非常複雜。如果代碼是在模擬器上的話,這裏就更新 apnContext的連接狀態爲Connected,並且通過 mPhone提示數據連接已經改變。之後若成功的話將調用 setupData(apnContext);
private boolean trySetupData(ApnContext apnContext) {
String apnType = apnContext.getApnType();

    if (mPhone .getSimulatedRadioControl() != null ) {             
        // Assume data is connected on the simulator
        // FIXME  this can be improved
        apnContext.setState(DctConstants.State. CONNECTED);
        mPhone.notifyDataConnection(apnContext.getReason(), apnType);        
        return true ;
    }
    //MTK begin
    if( FeatureOption.MTK_GEMINI_SUPPORT && PhoneConstants. APN_TYPE_DEFAULT .equals(apnType)){
        int gprsDefaultSIM = getDataConnectionFromSetting();
        GeminiPhone mGeminiPhone = (GeminiPhone)PhoneFactory.getDefaultPhone ();                                               
        logd( "gprsDefaultSIM:" + gprsDefaultSIM);
        if(gprsDefaultSIM != mGsmPhone .getMySimId()){
              logd( "The setting is off(1)" );
              Log. e("SJ", "MTK stoped it,gprsDefaultSIM!=mGsmPhone.getMySimId" );
              return false ;
        } else if (gprsDefaultSIM < 0){
           logd( "The setting is off(2)" );
           Log. e("SJ", "MTK stoped it,gprsDefaultSIM<0" );
           return false ;
        } else if ( mGeminiPhone != null && mGeminiPhone.isGprsDetachingOrDetached( mGsmPhone .getMySimId()) &&
                !TextUtils. equals(apnContext.getReason(), Phone.REASON_DATA_ATTACHED)) {
            logd( "trySetupData: detaching or detached state." );
            Log. e("SJ", "MTK stoped it,mGeminiPhone  " );
            return false ;
        }
     }
    //MTK end
    if (apnContext.getState() == DctConstants.State. DISCONNECTING) {        
        apnContext.setReactive( true);
    }
    boolean desiredPowerState = mPhone .getServiceStateTracker().getDesiredPowerState();
    boolean anyDataEnabled = (FeatureOption.MTK_BSP_PACKAGE ||
            !isDataAllowedAsOff(apnType))? getAnyDataEnabled() : isNotDefaultTypeDataEnabled();             
    if ((apnContext.getState() == DctConstants.State. IDLE ||
            apnContext.getState() == DctConstants.State. SCANNING) &&
            isDataAllowed(apnContext) && anyDataEnabled && !isEmergency()) {

        if (apnContext.getState() == DctConstants.State. IDLE) {
            ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnType);
            if (waitingApns.isEmpty()) {
                if (DBG ) log("trySetupData: No APN found");        
                notifyNoData(GsmDataConnection.FailCause. MISSING_UNKNOWN_APN, apnContext);
                notifyOffApnsOfAvailability(apnContext.getReason());
                return false ;
            } else {
                apnContext.setWaitingApns(waitingApns);
                if (DBG ) {
                    log ( "trySetupData: Create from mAllApns : " + apnListToString( mAllApns));
                }
            }
        }
        if (DBG ) {
            log ( "Setup watingApns : " + apnListToString(apnContext.getWaitingApns()));
        }

        boolean retValue = setupData(apnContext);       
        notifyOffApnsOfAvailability(apnContext.getReason());
        return retValue;
    } else {
        // TODO: check the condition.            
        if (!apnContext.getApnType().equals(PhoneConstants. APN_TYPE_DEFAULT)
            && (apnContext.getState() == DctConstants.State. IDLE
                || apnContext.getState() == DctConstants.State. SCANNING))
            mPhone.notifyDataConnectionFailed(apnContext.getReason(), apnType);
        notifyOffApnsOfAvailability(apnContext.getReason());
        return false ;
    }
}

(12).setupData()中將通過bringup 方法建立數據連接.這個方法相當複雜,沒有仔細去研究,留待後面有時間再處理吧。
private boolean 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 ;
    }  
    dc = (GsmDataConnection) checkForConnectionForApnContext(apnContext);

    // M: check if the dc's APN setting is prefered APN for default connection.
    if (dc != null && PhoneConstants. APN_TYPE_DEFAULT.equals(apnContext.getApnType())) {
        ApnSetting dcApnSetting = dc.getApnSetting();
        if (dcApnSetting != null && !dcApnSetting. apn.equals(apn.apn )) {
            if (DBG ) log("The existing DC is not using prefered APN.");
            Log. e("SJ", "SetUpData-->The existing DC is not using prefered APN." );
            dc = null;
        }
    }

    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 (PhoneFactory.isDualTalkMode()) {
                //M: in dual-talk project, we only have single pdp ability.
                if (apnContext.getApnType() == PhoneConstants. APN_TYPE_DEFAULT)
                {                             
                    if (DBG ) log("setupData: No free GsmDataConnection found!");
                    return false ;
                }
                    ApnContext DisableapnContext = mApnContexts.get(PhoneConstants.APN_TYPE_DEFAULT);
                    clearWaitingApn();
                    cleanUpConnection( true, DisableapnContext);
                    //disableApnType(PhoneConstants.APN_TYPE_DEFAULT);
                    mWaitingApnList.add(apnContext.getApnType());
                    return true ;
                }
            } else {                              
                if (DBG ) log("setupData: No free GsmDataConnection found!");
                return false ;
            }
        }
    } else {
        apn = mDataConnectionAsyncChannels.get(dc.getDataConnectionId()).getApnSettingSync();
    }
    DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
    dc.setProfileId( profileId );  //  assumed no connection sharing on profiled types

    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) {
        configureRetry(dc, apn.canHandleType(PhoneConstants. APN_TYPE_DEFAULT),
                apnContext.getRetryCount());
    }

    if (apnContext.getDataConnectionAc() != null && apnContext.getDataConnectionAc() != dcac) {
        if (DBG ) log("setupData: dcac not null and not equal to assigned dcac.");
        apnContext.setDataConnectionAc( null);
    }

    apnContext.setDataConnectionAc(dcac);
    apnContext.setDataConnection(dc);

    apnContext.setApnSetting(apn);
    apnContext.setState(DctConstants.State. INITING);
    mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
    // If reconnect alarm is active on this DataConnection, wait for the alarm being
    // fired so that we don't disruppt data retry pattern engaged.
    if (apnContext.getDataConnectionAc().getReconnectIntentSync() != null) {
        if (DBG ) log("setupData: data reconnection pending");    
        apnContext.setState(DctConstants.State. FAILED);
        if (PhoneConstants.APN_TYPE_MMS.equals(apnContext.getApnType())) {
            mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType(), PhoneConstants.DataState.CONNECTING );
        } else {
            mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
        }
        return true ;
    }
    if (apnContext.getApnType() == PhoneConstants. APN_TYPE_MMS) {
        mWaitingApnList.clear();

        /**
         * M: if MMS's proxy IP address is the same as current connected interface
         *    and their APN is not the same, try to disable the existed one.
         *    Then, setup MMS's interface.
         */
        for (ApnContext currApnCtx : mApnContexts .values()) {
            ApnSetting apnSetting = currApnCtx.getApnSetting();

            if (currApnCtx == apnContext)
                continue;          
            if ((apnSetting != null ) && !currApnCtx.isDisconnected() &&
                        !apnSetting.equals(apn) && (isSameProxy(apnSetting, apn) && !apnSetting.apn.equals(apn.apn ))) {
                if (DBG ) logd("setupData: disable conflict APN " + currApnCtx.getApnType());
                disableApnType(currApnCtx.getApnType());
                mWaitingApnList.add(currApnCtx.getApnType());
            }
        }
    }

    Message msg = obtainMessage();    
    msg. what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
    msg. obj = apnContext;   
    dc.bringUp(msg, apn);

    if (DBG ) log("setupData: initing!");
    return true ;
}

(13).再回過頭來看看CdmaDataConnectionTracker的 trySetupData()方法。與Gsm 相同,這裏也處理了模擬器的連接狀態處理。之後也會調用 setupData(String reason),此後調用notifyoffApnsOfAvailability方法通知一些 apn的連接狀態是DISCONNECTED,在 notifyoffApnsOfAvailability方法中循環遍歷所有的apn,通過 isApnIdEnabled(int id)將所有未啓用的apn篩選出來並利用 avail/unavail notificiations(notifyApnIdDisconnected方法 )送出去。—-當然這裏爲什麼要發出去,目的是什麼有什麼作用,我還不太能理解清楚。
private boolean trySetupData(String reason) {
if (DBG ) log(“***trySetupData due to ” + (reason == null ? “(unspecified)” : reason));
if (mPhone .getSimulatedRadioControl() != null ) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(DctConstants.State. CONNECTED);
notifyDataConnection(reason);
notifyOffApnsOfAvailability(reason);

        log( "(fix?) We're on the simulator; assuming data is connected" );
        return true ;
    }

    int psState = mCdmaPhone .mSST .getCurrentDataConnectionState();
    boolean roaming = mPhone .getServiceState().getRoaming();
    boolean desiredPowerState = mCdmaPhone .mSST .getDesiredPowerState();

    if ((mState == DctConstants.State.IDLE || mState == DctConstants.State.SCANNING) &&
            isDataAllowed() && getAnyDataEnabled() && !isEmergency()) {
        boolean retValue = setupData(reason);
        notifyOffApnsOfAvailability(reason);
        return retValue;
    } else {
        notifyOffApnsOfAvailability(reason);
        return false ;
    }

}

protected void notifyOffApnsOfAvailability(String reason) {
if (DBG ) log(“notifyOffApnsOfAvailability - reason= ” + reason);
for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
if (id == apnTypeToId(apnIdToType(id)) && !isApnIdEnabled(id)) {
notifyApnIdDisconnected(reason, id);// 通知該apn的狀態是斷開的
Log. e(“SJ”, “notify disconnected–> id ” +id+” reason “+reason);
}
}

}

// since we normally don’t send info to a disconnected APN, we need to do this specially
//通常我們不會給一個斷開的 apn發送消息,因此我們需要專門來做這個
private void notifyApnIdDisconnected(String reason, int apnId) {
mPhone.notifyDataConnection(reason, apnIdToType(apnId), PhoneConstants.DataState.DISCONNECTED );
}

(14).接下來在看一下CdmaDataConnectionTracker的 setupData(reason)方法。和gsm 相同,都是尋找對應的 DataConnection再根據其bringup() 方法建立數據連接,並帶上了 DctConstants. EVENT_DATA_SETUP_COMPLETE 的消息。注意:當該動作完成後才被回調該消息

 private boolean setupData(String reason) {
        CdmaDataConnection conn = findFreeDataConnection();
        if (conn == null ) {
            if (DBG ) log("setupData: No free CdmaDataConnection found!");
            return false ;
        }
        /** TODO: We probably want the connection being setup to a parameter passed around */
        mPendingDataConnection = conn;
        String[] types;
        int apnId;
        if (mRequestedApnType .equals(PhoneConstants.APN_TYPE_DUN)) {
            types = mDunApnTypes;
            apnId = DctConstants. APN_DUN_ID;
        } else {
            types = mDefaultApnTypes;
            apnId = mDefaultApnId;
        }
        mActiveApn = new ApnSetting(apnId, "", "", "" , "", "", "" , "" , "" , "" ,
                                    "", 0, types, "IP" , "IP" , true , 0);
        if (DBG ) log("call conn.bringUp mActiveApn=" + mActiveApn );

        Message msg = obtainMessage();
        msg. what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
        msg. obj = reason;
        conn.bringUp(msg, mActiveApn);

        setState(DctConstants.State. INITING);
        notifyDataConnection(reason);
        return true ;
    }

(15).在GsmDataConnectionTracker 和CdmaDataConnectionTracker的 setup之後,回調到DataConnection的 bringUp方法。它將之前標記what爲 DctConstants. EVENT_DATA_SETUP_COMPLETE 的消息和傳遞過來的 apnSetting實例構造成新的連接參數 ConnectionParams併發連接消息讓DataConnection狀態機對應的狀態進行處理。 ConnectionParams是在 DataConnection的內部類,用來保存連接的參數信息。

     public void bringUp (Message onCompletedMsg, ApnSetting apn) {        
                sendMessage(obtainMessage( EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg)));
    }

    /**
     * Used internally for saving connecting parameters.
     */
    protected static class ConnectionParams {
        public ConnectionParams(ApnSetting apn, Message onCompletedMsg) {
            this. apn = apn;
            this. onCompletedMsg = onCompletedMsg;
        }
        public int tag ;//這個tag 還不知道有什麼作用
        public ApnSetting apn ;
        public Message onCompletedMsg ;
    }

(16).DataConnection 繼承了StateMachine,關於狀態機可以參考: http://blog.csdn.net/wsb1321/article/details/8021620
DataConnection一共有6 個狀態,比起 wifi的狀態要少很多,結構也相對簡單很多。看一下其結構圖:
(frameworks/opt/telephony/src/java/com/android/internal/telephony/DataConnection.java)

上面圖結構根據構造方法可得.根據 setInitialState( mInactiveState )可知mInactiveState 爲狀態機初始狀態

 protected DataConnection(PhoneBase phone, String name, int id, RetryManager rm,
            DataConnectionTracker dct) {
        super(name);
        setLogRecSize(100);
        if (DBG ) log("DataConnection constructor E");
        this. phone = phone;
        this. mDataConnectionTracker = dct;
        mId = id;
        mRetryMgr = rm;
        this. cid = -1;
        setDbg( false);
        addState( mDefaultState);
            addState( mInactiveState, mDefaultState);
            addState( mActivatingState, mDefaultState);
            addState( mActiveState, mDefaultState);
            addState( mDisconnectingState, mDefaultState);
            addState( mDisconnectingErrorCreatingConnection , mDefaultState );
        setInitialState( mInactiveState);// 設置初始狀態
        mApnList = new ArrayList<ApnContext>();
        if (DBG ) log("DataConnection constructor X");
    }

(17).mInactiveState爲初始狀態,因此當bringUp()調用時,發過來的EVENT_CONNECT消息將被它處理。看它的 processMessage方法:

@Override
        public boolean processMessage (Message msg) {
            boolean retVal;
......
  case EVENT_CONNECT :
                    ConnectionParams cp = (ConnectionParams) msg. obj;
                    cp. tag = mTag ;//首次進入的話, mTag值應該爲1
                    if (DBG ) {
                        log( "DcInactiveState msg.what=EVENT_CONNECT." + "RefCount = "
                                + mRefCount);
                    }
                    mRefCount = 1;            
                    onConnect(cp);
                    transitionTo( mActivatingState);
                    retVal = HANDLED;
                    break;
.....
}

關於 mTag的作用不是很理解,整個 DataConnction中在 僅mInactiveState的 enter方法中進行了值修改。

 public void enter() {
            mTag += 1;
....
}

此後調用傳入connectionParams到onConnect方法,該方法是一個抽象方法,由GsmDataConnection和CdmaDataConnection實現。
(/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmDataConnection.java )
(frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java )

(18).先看GsmDataConnection 的onConnect方法。該方法通過調用 setupDataCal()開始進行數據連接,並且將連接參數 cp通過EVENT_SETUP_DATA_CONNECTION_DONE 消息在AsyncResult.userObj返回出去。

protected void onConnect(ConnectionParams cp) {       
        mApn = cp.apn ;
        if (DBG ) log("Connecting to carrier: '" + mApn .carrier
                + "' APN: '" + mApn .apn
                + "' proxy: '" + mApn .proxy + "' port: '" + mApn .port);
        createTime = -1;
        lastFailTime = -1;
        lastFailCause = FailCause.NONE;
        // msg.obj will be returned in AsyncResult.userObj;
        Message msg = obtainMessage( EVENT_SETUP_DATA_CONNECTION_DONE , cp);
        msg. obj = cp;
        int authType = mApn .authType ;
        if (authType == -1) {
            authType = TextUtils. isEmpty(mApn. user) ? RILConstants. SETUP_DATA_AUTH_NONE
                    : RILConstants. SETUP_DATA_AUTH_PAP_CHAP;
        }
        /*String protocol;
        if (phone.getServiceState().getRoaming()) {
            protocol = mApn.roamingProtocol;
        } else {
            protocol = mApn.protocol;
        }*/
        //since in APN Editor, the roaming protocol item is showed only the current phone type is CDMA
        //so here we align this design and use protocol even if it is roaming
        String protocol = mApn. protocol ;
        phone. mCM .setupDataCall(
                Integer. toString(getRilRadioTechnology(RILConstants. SETUP_DATA_TECH_GSM)),
                Integer. toString(mProfileId),
                mApn. apn , mApn. user, mApn .password,
                Integer. toString(authType),
                protocol, String. valueOf(mId + 1), msg);         
    }

(19).再看下CdmaDataConnection中的onConnect()方法。從做的處理來看與Gsm的幾乎沒什麼差別,但在調用setupDataCall方法時,參數有所不同。setupDataCall(String radioTechnology, String profile,String apn, String user, String password, String authType, String protocol, Message result) ,cdma並未進行 apn,user ,password的三個參數的傳遞。它調用的 setupDataCall其實與GsmDataConnection 中的setupDataCall也不相同,不過呢最終 CdmaDataConnection的setupDataCall 方法最終調用的地方與 GsmDataConntion相同。

@Override
    protected void onConnect(ConnectionParams cp) {
        if (DBG ) log("CdmaDataConnection Connecting...");     
        mApn = cp.apn ;
        createTime = -1;
        lastFailTime = -1;
        lastFailCause = FailCause.NONE;
        int dataProfile;
        if ((cp.apn != null ) && (cp. apn .types .length > 0) && (cp. apn .types [0] != null ) &&
                (cp. apn. types [0].equals(PhoneConstants. APN_TYPE_DUN))) {
            if (DBG ) log("CdmaDataConnection using DUN");
            dataProfile = RILConstants. DATA_PROFILE_TETHERED;
        } else {
            dataProfile = RILConstants. DATA_PROFILE_DEFAULT;
        }
        // msg.obj will be returned in AsyncResult.userObj;
        Message msg = obtainMessage( EVENT_SETUP_DATA_CONNECTION_DONE , cp);
        msg. obj = cp;
        phone. mCM .setupDataCall(
                Integer. toString(getRilRadioTechnology(RILConstants. SETUP_DATA_TECH_CDMA)),
                Integer. toString(dataProfile),
                null, null , null ,
                Integer. toString(RILConstants.SETUP_DATA_AUTH_PAP_CHAP),
                RILConstants. SETUP_DATA_PROTOCOL_IP, msg);
    }

(20).setupDataCall方法的實現在 RIL.jvaa中
(frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java)
CdmaDataConnection 中調用的是下面這個方法:

public void setupDataCall(String radioTechnology, String profile, String apn,
            String user, String password, String authType, String protocol,
            Message result) {
        /* [Note by mtk01411] In original Android2.1 release: MAX PDP Connection is 1
        * request_cid is only allowed to set as "1" manually
        */                    
                String request_cid = "1";
        setupDataCall(radioTechnology, profile, apn, user, password, authType, protocol, request_cid, result);
    }

實際上只是爲了增加一個 request_cid 而已,之後調用的其實和 GsmDataConnetion是一樣的

 public void setupDataCall(String radioTechnology, String profile, String apn,
            String user, String password, String authType, String protocol, String requestCid,
            Message result) {
        RILRequest rr
                = RILRequest. obtain(RIL_REQUEST_SETUP_DATA_CALL, result);
        /* [Note by mtk01411] Currently, MAX PDP Connection is 1: request_cid is only allowed to set as "1" manually
        * But for Multiple PDP Connecitons Feature:
        * PdpConnection.java connect() shoud invoke this 8-parms version's setupDataCall() directly
        * request_cid is obtained from the parameter paased into the setupDataCall()
        */

        /* [Note by mtk01411] Change from 6 to 7: Due to add one cid field as the last one parameter */
        rr. mp.writeInt(8);
        rr. mp.writeString(radioTechnology);
        rr. mp.writeString(profile);
        rr. mp.writeString(apn);
        rr. mp.writeString(user);
        rr. mp.writeString(password);
        rr. mp.writeString(authType);
        /* [Add by mtk01411]
        * MAX PDP Connection =1:Only cid string "1" is allowed for RILD
        * MAX PDP Connection =2:Only cid string "1" or "2" is allowed for RILD
        * MAX PDP Connection =3:Only cid string "1" or "2" or "3" is allowed for our RILD
        */
        rr. mp.writeString(protocol);
        rr. mp.writeString(requestCid);
        if (RILJ_LOGD ) riljLog(rr.serialString() + "> "
                + requestToString(rr.mRequest) + " radioTech=" + radioTechnology + " profile="
                + profile + " apn=" + apn + " user=" + user + " password="
                + password + " authType=" + authType + " protocol=" + protocol + " requestCid=" + requestCid);

        send(rr);// 發送請求,由 RIL的構造函數中創建的RILSender線程完成向 socket寫入數據
    }

(21).之後send 就是發消息給內部類 RILSender,再調用 socket發送數據了,當然再往下就是 c部分了,那裏面是不會有對數據網絡本身的開啓關閉進行的處理的。因此從應用到框架的數據開啓過程就是這樣的流程了。

 private void send(RILRequest rr) {
        Message msg; 
        boolean show = (requestToString (rr. mRequest).compareTo("LAST_CALL_FAIL_CAUSE" ) == 0);
        if (mSocket == null ) {   
            rr.onError( RADIO_NOT_AVAILABLE, null );
            rr.release();
            return;
        }
        msg = mSender.obtainMessage( EVENT_SEND, rr);    
        acquireWakeLock();

        msg.sendToTarget();    
    }

之後 RILSender這個內部類調用LocalSocket進行數據發送

 case EVENT_SEND:
                    /**
                     * mRequestMessagePending++ already happened for every
                     * EVENT_SEND, thus we must make sure
                     * mRequestMessagePending -- happens once and only once
                     */
                    boolean alreadySubtracted = false ;
                    try {
                        LocalSocket s;
                        s = mSocket;

                        //MTK-START [mtk04070][111121][ALPS00093395]MTK modified
                        if (s == null || radioTemporarilyUnavailable !=RADIO_TEMPSTATE_AVAILABLE) {
                            rr.onError( RADIO_NOT_AVAILABLE, null );
                            rr.release();// 錯誤則將 RILRequest放回池中
                            mRequestMessagesPending--;
                            alreadySubtracted = true;
                            return;
                        }
                         //RILRequest請求列表,整型序列號serial作爲其 id標識。
                        //當 RILSender發送一個RIL 請求後,則將其添加到該列表中(若發送時出現異常則需再清除);
                        //當請求完成並得到回送的 response消息後,則將其移除
                        synchronized (mRequestsList ) {
                            mRequestsList.add(rr);
                        }

                        mRequestMessagesPending--;
                        alreadySubtracted = true;
                        //MTK-END [mtk04070][111121][ALPS00093395]MTK modified

                        byte[] data;
                         //mp 即爲rr中的 parcel,它當中是戴寫入數據的緩衝區,經過 marshall優化後在賦值給data
                        data = rr. mp.marshall();
                        rr. mp.recycle();
                        rr. mp = null ;

                        if (data.length > RIL_MAX_COMMAND_BYTES ) {
                            throw new RuntimeException(
                                    "Parcel larger than max bytes allowed! "
                                                          + data.length);
                        }

                        // parcel length in big endian
                        dataLength[0] = dataLength [1] = 0;
                        dataLength[2] = ( byte)((data.length >> 8) & 0xff);
                        dataLength[3] = ( byte)((data.length ) & 0xff);

                        //Log.v(LOG_TAG, "writing packet: " + data.length + " bytes");

                        s.getOutputStream().write( dataLength);// 寫入scoket數據長度,不能超過 RIL_MAX_COMMAND_BYTES
                        s.getOutputStream().write(data);// 寫數據給 rild
                    } catch (IOException ex) {
                        Log. e(LOG_TAG, "IOException", ex);
                         // 如果發送過程中出現異常,通過 findAndRemoveRequestFromList移除該次RILRequest
                        req = findAndRemoveRequestFromList(rr. mSerial);
                        // make sure this request has not already been handled,
                        // eg , if RILReceiver cleared the list.
                        if (req != null || !alreadySubtracted) {
                            rr.onError( RADIO_NOT_AVAILABLE, null );
                            rr.release();
                        }
                    } catch (RuntimeException exc) {
                        Log. e(LOG_TAG, "Uncaught exception ", exc);
                          //如果發送過程中出現異常,通過 findAndRemoveRequestFromList移除該次RILRequest
                        req = findAndRemoveRequestFromList(rr. mSerial);
                        // make sure this request has not already been handled,
                        // eg , if RILReceiver cleared the list.
                        if (req != null || !alreadySubtracted) {
                            rr.onError( GENERIC_FAILURE, null );
                            rr.release();
                        }
                    } finally {
                        // Note: We are "Done" only if there are no outstanding
                        // requests or replies. Thus this code path will only release
                        // the wake lock on errors.
                        releaseWakeLockIfDone();
                    }

                    //MTK-START [mtk04070][111121][ALPS00093395]MTK modified
                    if (!alreadySubtracted) {
                        mRequestMessagesPending--;
                    }
                    //MTK-END [mtk04070][111121][ALPS00093395]MTK modified
                    break;

由此可見一個 RIL請求(RILRequest) 執行時一個異步過程,調用者調用 RIL類的api 函數只是往 RILSender線程添加了一個消息就返回,然後線程在執行無限循環時將其寫入到 socket中,並將RILRequest 添加到一個列表中,再將數據優化後通過 socket寫給rild 。

發佈了31 篇原創文章 · 獲贊 40 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章