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控制元件值。
詳細可以看:
接下來看看當前 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 。