編輯:關於Android編程
前面我們已經分析了android在進行數據業務撥號前,進行相關准備工作的流程,現在我們可以分析一下整個數據業務長連接撥號在框架部分的流程。
長連接的“長”,是相對於終端進行彩信發送等操作時,建立的臨時數據連接而言的(這種臨時數據連接在業務執行完畢後,會主動斷開),是能夠長時間存在的數據連接。
1 CellDataPreference
我們從點擊UI界面的數據撥號開關開始分析整個流程。
在原生的Android代碼中,數據開關作為設置的一部分,相關的操作定義於CellDataPreference.java中,定義於packages/apps/settings/src/com/android/settings/datausage目錄下。
我們看看處理點擊操作的performClick函數:
@Override
protected void performClick(View view) {
.............
//開關處於開啟狀態
if (mChecked) {
//當前subId對應的卡信息(卡需要處於激活狀態,即相關信息已經加載)
final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
//默認數據卡對應的卡狀態
final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo();
//showSimCardTile判斷手機是否支持多卡,支持的話返回true
//整個If的含義就是:僅支持單卡,或者默認數據卡與當前的卡信息一致
if (!Utils.showSimCardTile(getContext()) || (nextSir != null && currentSir != null &&
currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) {
//關閉數據業務(開關處於開啟態,再點擊一次,變成關閉態)
setMobileDataEnabled(false);
if (nextSir != null && currentSir != null &&
currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
//雙卡的情況下,還要關閉另一張卡的數據業務(當前卡為默認數據卡,這裡是以防萬一)
disableDataForOtherSubscriptions(mSubId);
}
return;
}
............
super.performClick(view);
} else {
//這裡是從關到開的過程,多卡的情況
if (Utils.showSimCardTile(getContext())) {
//將標志位置為true
mMultiSimDialog = true;
//調用父類方法;在父類方法中最終將調用子類實現的onClick方法
super.performClick(view);
} else {
//單卡時直接開始撥號
setMobileDataEnabled(true);
}
}
}
從上面的代碼我們可以看出,在多卡的情況下,將開關從關閉置為打開,將由CellDataPreference的onClick函數進行處理:
@Override
protected void onClick(DialogInterface dialog, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
return;
}
//在前面的代碼中,mMultiSimDialog已經置為true,表示手機支持多卡
if (mMultiSimDialog) {
//將當前CellDataPreference對應卡設為默認數據卡
mSubscriptionManager.setDefaultDataSubId(mSubId);
//開始數據撥號
setMobileDataEnabled(true);
//關閉另一張卡的數據業務
disableDataForOtherSubscriptions(mSubId);
} else {
// TODO: extend to modify policy enabled flag.
setMobileDataEnabled(false);
}
}
private void setMobileDataEnabled(boolean enabled) {
if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled(" + enabled + "," + mSubId + ")");
//調用TelephonyManager的接口
mTelephonyManager.setDataEnabled(mSubId, enabled);
//更改界面
setChecked(enabled);
}
2 TelephonyManager
根據上文的代碼,我們知道設置界面最終通過調用TelephonyManager開啟撥號流程。
//傳入參數subId為數據卡對應的subId
//enable為true表示開啟數據業務;false表示關閉數據業務
@SystemApi
public void setDataEnabled(int subId, boolean enable) {
try {
Log.d(TAG, "setDataEnabled: enabled=" + enable);
//獲取binder代理對象
ITelephony telephony = getITelephony();
if (telephony != null)
//通過binder通信調用接口
telephony.setDataEnabled(subId, enable);
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
}
}
private ITelephony getITelephony() {
//Context.TELEPHONY_SERVICE對應字符串"phone"
return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}
上面的代碼較為簡單,唯一值得關注的是找到binder通信對應的服務提供者。
實際上我們在之前的blog中已經提到過了,在PhoneApp啟動時會創建PhoneGlobals,而PhoneGlobals會創建PhoneInterfaceManager:
3 PhoneInterfaceManager
....... phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone()); ......
我們來看看PhoneInterfaceManager的定義:
//可以看到PhoneInterfaceManager繼承ITelephony.Stub,與前面呼應起來了
public class PhoneInterfaceManager extends ITelephony.Stub {
.............
static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
//創建PhoneInterfaceManager
sInstance = new PhoneInterfaceManager(app, phone);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
...........
publish();
}
private void publish() {
if (DBG) log("publish: " + this);
//publish服務名為"phone",與前面對應起來了
ServiceManager.addService("phone", this);
}
...........
}
至此,我們知道了TelephonyManager通過Binder通信調用的實際上是PhoneInterfaceManager中的接口。
@Override
public void setDataEnabled(int subId, boolean enable) {
//檢查權限
enforceModifyPermission();
//利用subId映射得到phoneId
int phoneId = mSubscriptionController.getPhoneId(subId);
if (DBG) log("getDataEnabled: subId=" + subId + " phoneId=" + phoneId);
//根據phoneId得到PhoneFactory中記錄的phone對象
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone != null) {
if (DBG) log("setDataEnabled: subId=" + subId + " enable=" + enable);
//調用phone對象的setDataEnabled方法
phone.setDataEnabled(enable);
} else {
loge("setDataEnabled: no phone for subId=" + subId);
}
}
4 GsmCdmaPhone
與之前的版本不一樣,Android 7.0中新增了GsmCdmaPhone對象,並且將Phone變成了一個抽象類,新增了PhoneInternaInterface接口:
public abstract class Phone extends Handler implements PhoneInternalInterface {
...........
}
public class GsmCdmaPhone extends Phone {
...........
}
這種繼承結構的變化帶來的影響,自己目前也還沒有深入研究,今後有流程涉及時,再做進一步的分析。
我們目前還是關注數據撥號流程:
@Override
public void setDataEnabled(boolean enable) {
mDcTracker.setDataEnabled(enable);
}
可以看到在撥號流程中,GsmCdmaPhone的工作很簡單,直接調用DcTracker的接口即可。
5 DcTracker
在Android 7.0中,去掉了DcTrackerBase對象,DcTracker直接繼承Handler。
public class DcTracker extends Handler {
.......
public void setDataEnabled(boolean enable) {
Message msg = obtainMessage(DctConstants.CMD_SET_USER_DATA_ENABLE);
msg.arg1 = enable ? 1 : 0;
if (DBG) log("setDataEnabled: sendMessage: enable=" + enable);
//發送消息給自己,將調用onSetUserDataEnabled進行處理
sendMessage(msg);
}
........
private void onSetUserDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
//新設定的狀態,與舊狀態不一樣時,才需要繼續處理
if (mUserDataEnabled != enabled) {
mUserDataEnabled = enabled;
// 更新數據庫,注意到單、雙卡更新字段的區別
if (TelephonyManager.getDefault().getSimCount() == 1) {
Settings.Global.putInt(mResolver, Settings.Global.MOBILE_DATA, enabled ? 1 : 0);
} else {
int phoneSubId = mPhone.getSubId();
Settings.Global.putInt(mResolver, Settings.Global.MOBILE_DATA + phoneSubId,
enabled ? 1 : 0);
}
//根據系統屬性判斷終端是否允許在漫游狀態使用數據業務
if (getDataOnRoamingEnabled() == false &&
mPhone.getServiceState().getDataRoaming() == true) {
if (enabled) {
//僅為不可用的APN發送通知
notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
} else {
notifyOffApnsOfAvailability(Phone.REASON_DATA_DISABLED);
}
}
if (enabled) {
//開啟數據業務時,調用該函數
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
//關閉數據業務時,調用該函數
onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
}
}
}
}
..............
}
onTrySetupData的內容較為簡單,直接調用setupDataOnConnectableApns:
private boolean onTrySetupData(String reason) {
if (DBG) log("onTrySetupData: reason=" + reason);
//顧名思義,將利用可連接的APN進行撥號
setupDataOnConnectableApns(reason);
return true;
}
private void setupDataOnConnectableApns(String reason) {
//這裡RetryFailures.ALWAYS表示連網失敗話,會一直重試
setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);
}
private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
...............
//介紹Phone撥號前的准備工作時,我們已經已經mPrioritySortedApnContexts是通過解析xml文件形成的
for (ApnContext apnContext : mPrioritySortedApnContexts) {
//如果apnContext之前用過,不處於Idle態(apnContext初始時處於Idle態),那麼按需釋放對應的數據連接,這一部分我們目前不用太關注
......................
//注意到apnContext的isConnectable返回true時,撥號流程才能繼續下去
if (apnContext.isConnectable()) {
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
//首次使用的ApnContext, waitingApns為null
trySetupData(apnContext, waitingApns);
}
}
}
我們看看ApnContext中的isConnectable等函數:
public boolean isConnectable() {
return isReady() && ((mState == DctConstants.State.IDLE)
|| (mState == DctConstants.State.SCANNING)
|| (mState == DctConstants.State.RETRYING)
|| (mState == DctConstants.State.FAILED));
}
public boolean isReady() {
//ApnContext被激活時,mDataEnabled才會變為true;mDependencyMet從配置中讀出來,衡為true
return mDataEnabled.get() && mDependencyMet.get();
}
根據前面的數據業務撥號准備工作的流程,我們知道ConnectivityService中default NetworkRequest對應類型的ApnContext被激活了,也就是Default Type的APN被激活了。
因此,我們至少有一個connectable的APN可以使用, 可以繼續撥號流程。
trySetupData函數中主要根據當前終端的運行狀態,判斷框架是否應該繼續撥號。
private boolean trySetupData(ApnContext apnContext, ArrayList waitingApns) {
//判斷撥號條件
..........
//整個判斷能否撥號的過程比較復雜,涉及了很多細節
//本來打算詳細寫一下,但寫了一部分後,發現太亂了
//這裡就大概描述一下:其實這裡就是檢查之前的准備工作是否完成,主要是結合APN類型、數據能力是否激活及數據開關是否打開等,判斷處能否繼續撥號
if (......../*滿足撥號條件*/) {
............
if (apnContext.getState() == DctConstants.State.IDLE) {
if (waitingApns == null) {
//結合激活的apnContext的type,例如default類型,以及底層使用的無線技術(從servicestate獲取),從Dctracker加載卡對應的所有apn中,得到可以使用的Apn
waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
}
if (waitingApns.isEmpty()) {
//沒獲取到可用apn
........
return false;
} else {
//將所有可用的apn存入apnContext
apnContext.setWaitingApns(waitingApns);
.............
}
}
//繼續撥號
boolean retValue = setupData(apnContext, radioTech);
...........
return retValue;
} else {
//打印不允許撥號的原因
.................
}
}
之前寫blog總想盡可能的詳細,以便以後查閱,但這次真是被trySetupData打敗了。回過頭來想想,自己之前的想法可能確實是有問題的,對於整個Framework而言,重要的是架構和主要流程的脈絡,自己過於注重細節,反而影響閱讀和記錄的效率。可能自己需要分析Framework的bug,因此很多時候不得不關注細節,於是產生了現在的毛病,以後行文風格要力求簡潔。
現在,我們回過頭來看看setupData:
private boolean setupData(ApnContext apnContext, int radioTech) {
..........
//用於連接DcTracker和DataConnection(後文描述)
DcAsyncChannel dcac = null;
//從apncontext取出可用的apn
apnSetting = apnContext.getNextApnSetting();
.............
//得到profileId,這個需要傳遞給modem
int profileId = apnSetting.profileId;
if (profileId == 0) {
profileId = getApnProfileID(apnContext.getApnType());
}
//不同類型Apn的profileId定義於RILConstants中
//例如:
//public static final int DATA_PROFILE_DEFAULT = 0;
//public static final int DATA_PROFILE_TETHERED = 1;
//public static final int DATA_PROFILE_IMS = 2;
if (dcac == null) {
//根據無線技術,判斷是否只允許建立一個DataConnection
//無線技術能否支持多個數據連接,由frameworks/base/core/res/res/values/config.xml決定
if (isOnlySingleDcAllowed(radioTech)) {
//當無線技術僅支持單連接時,若有高優先級的APN被激活,那麼此次撥號無法繼續
//舉例來說,就是某些無線技術下,彩信發送時,default數據不能撥號
//目前原生中,僅支持單連接的無線技術為IS95A,IS95B,1xRTT,EVDO_0,EVDO_A,EVDO_B
if (isHigherPriorityApnContextActive(apnContext)) {
return false;
}
//在僅支持單連接的情況下,撥號前需要清楚所有已建立的連接
//代碼走到這裡,說明當前APN的優先級是最高的,需要清除低優先級的連接
//舉例來說,在上述無線技術下,建立彩信時,會斷開已連接的default數據業務;當然彩信發送完畢後,會自動重新建立default的數據連接
if (cleanUpAllConnections(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
return false;
}
}
//判斷能否復用dataConnection
dcac = findFreeDataConnection();
if (dcac == null) {
//不能復用則創建新的dataConnection和dcAsyncChannel
dcac = createDataConnection();
}
..........
}
//下面均是更新ApnContext的狀態
final int generation = apnContext.incAndGetConnectionGeneration();
.............
apnContext.setDataConnectionAc(dcac);
apnContext.setApnSetting(apnSetting);
apnContext.setState(DctConstants.State.CONNECTING);
Message msg = obtainMessage();
//注意此msg類型;當dataConnection撥號成功後,將會返回此消息給dcTracker
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = new Pair(apnContext, generation);
//調用dcAsyncChannel的bringUp
dcac.bringUp(apnContext, profileId, radioTech, msg, generation);
if (DBG) log("setupData: initing!");
return true;
}
根據上面的代碼,我們知道了當DcTracker判斷出撥號的准備工作OK時,將創建出DataConnection對象,然後調用DcAsyncChannel的bringUp函數。
6 DataConnection
6.1 創建過程
我們先看看DcTracker中創建DataConnection的過程:
private DcAsyncChannel createDataConnection() {
..........
//每個DataConnection有唯一的id號
int id = mUniqueIdGenerator.getAndIncrement();
//創建dataconnection,注意this為dctracker
DataConnection conn = DataConnection.makeDataConnection(mPhone, id, this, mDcTesterFailBringUpAll, mDcc);
//dctracker保存dataconnection
mDataConnections.put(id, conn);
//創建DcAsyncChannel
DcAsyncChannel dcac = new DcAsyncChannel(conn, LOG_TAG);
//其中調用AsyncChannel中的方法,完成dctracker與dataconnection之間handler的綁定;如同connectivityService與NetworkFactory的綁定一樣
int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
if (status == AsyncChannel.STATUS_SUCCESSFUL) {
//dctracker保存DcAsyncChannel,鍵值為對應dataConnection的id
mDataConnectionAcHashMap.put(dcac.getDataConnectionIdSync(), dcac);
} else {
.........
}
return dcac;
}
現在我們看看makeDataConnection方法:
public static DataConnection makeDataConnection(.....) {
DataConnection dc = new DataConnection(phone,
"DC-" + mInstanceNumber.incrementAndGet(), id, dct, failBringUpAll, dcc);
//DataConnection是個狀態機,因此需要start
dc.start();
return dc;
}
最後,看看DataConnection的構造函數:
//繼承狀態機
public class DataConnection extends StateMachine {
......
private DataConnection(......) {
...........
//撥號成功後,將利用NetworkInfo構造NetworkAgent,注冊到ConnectivityService
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
...........
//調用狀態機中的addState方法
addState(mDefaultState);
//後面的狀態是前面的父狀態
addState(mInactiveState, mDefaultState);
addState(mActivatingState, mDefaultState);
addState(mActiveState, mDefaultState);
addState(mDisconnectingState, mDefaultState);
addState(mDisconnectingErrorCreatingConnection, mDefaultState);
//初始態為InactiveState
setInitialState(mInactiveState);
...........
}
}
DataConnection是一個狀態機,android中狀態機的實現原理,以後再單獨分析。這裡我們只需要知道,狀態機內部有自己的Handler,收到消息時由當前狀態進行處理;若當前狀態無法處理,則遞交給父狀態進行處理。當從一個狀態離開時,將調用該狀態的exit函數(可以是空實現);當進入到一個狀態時,將調用該狀態的enter函數(可以是空實現)。
6.2 DataConnection撥號
前文已經描述,在DcTracker創建完DataConnection和DcAysncChannel後,調用了DcAsyncChannel的bringUp函數:
public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
Message onCompletedMsg, int connectionGeneration) {
//調用AsyncChannel的sendMessage方法,將消息發送給dstMessenger,也就是dataConnection
sendMessage(DataConnection.EVENT_CONNECT,
new ConnectionParams(apnContext, profileId, rilRadioTechnology,
onCompletedMsg, connectionGeneration));
}
6.2.1 DcInactiveState
根據DataConnection的構造函數,我們知道DataConnection初始時處於DcInactiveState,於是應該由DcInactiveState處理EVENT_CONNECT事件:
private class DcInactiveState extends State {
.........
@Override
public boolean processMessage(Message msg) {
.........
switch (msg.what) {
...........
case EVENT_CONNECT:
ConnectionParams cp = (ConnectionParams) msg.obj;
//判斷參數的有效性
if (initConnection(cp)) {
//進行實際的撥號操作
onConnect(mConnectionParams);
//dataConnection遷移到Activating狀態
transitionTo(mActivatingState);
} else {
//通知DcTracker撥號失敗
notifyConnectCompleted(cp, DcFailCause.UNACCEPTABLE_NETWORK_PARAMETER, false);
}
break;
}
}
}
我們看看onConnect函數:
private void onConnect(ConnectionParams cp) {
...........
//撥號返回時的消息為EVENT_SETUP_DATA_CONNECTION_DONE
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
...........
//通過RIL將消息發送給modem
mPhone.mCi.setupDataCall(
cp.mRilRat,
cp.mProfileId,
mApnSetting.apn, mApnSetting.user, mApnSetting.password,
authType,
protocol, msg);
}
6.2.2 DcActivatingState
根據前面的代碼,我們知道DataConnection在DcInactiveState狀態,利用RIL向modem發送撥號請求後,進入到了DcActivatingState;同時,RIL收到modem撥號返回的消息後,將向DataConnection發送EVENT_SETUP_DATA_CONNECTION_DONE的消息。
根據狀態機的原理,我們看看DcActivatingState處理消息的代碼:
private class DcActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
.........
switch (msg.what) {
...........
case EVENT_SETUP_DATA_CONNECTION_DONE:
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
//從RIL返回的msg中取出撥號的結果;當結果正常時,內部還調用了updateLinkProperty獲取了鏈路信息
DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
........
switch (result) {
case SUCCESS:
mDcFailCause = DcFailCause.NONE;
transitionTo(mActiveState);
break;
//撥號出現異常時,將根據結果判斷是否需要重新撥號,還是打印log,停止撥號
..............
}
retVal = HANDLED;
break;
................
}
}
}
從上面的代碼可以看出,當底層返回撥號成功的消息後,DataConnection將進入到DcActiveState。
6.2.3 DcActiveState
DcActiveState實現了自己的enter函數,因此從DcActivatingState遷入時,首先將調用該enter函數:
private class DcActiveState extends State {
@Override public void enter() {
boolean createNetworkAgent = true;
//如果隊列中有斷開連接的消息待處理,則不創建NetworkAgent
if (hasMessages(EVENT_DISCONNECT) ||
hasMessages(EVENT_DISCONNECT_ALL) ||
hasDeferredMessages(EVENT_DISCONNECT) ||
hasDeferredMessages(EVENT_DISCONNECT_ALL)) {
log("DcActiveState: skipping notifyAllOfConnected()");
createNetworkAgent = false;
} else {
//通知DcTracker撥號完成,消息為EVENT_DATA_SETUP_COMPLETE
notifyAllOfConnected(Phone.REASON_CONNECTED);
}
//注冊監聽通話的開始和結束;由於通信制式的約束,同一個phone通話時必須斷開數據業務,通話結束後,再重新連接
mPhone.getCallTracker().registerForVoiceCallStarted(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_STARTED, null);
mPhone.getCallTracker().registerForVoiceCallEnded(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_ENDED, null);
//更新NetworkInfo等
............
if (createNetworkAgent) {
//創建NetworkAgent,將注冊到ConnectivityService
mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
"DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
50, misc);
}
}
.........
}
至此,與modem交互的撥號主要流程已經結束。然而,還有兩件重要的事情沒有做:1、通知其它APK,例如SystemUI,撥號成功;2、配置路由等,讓終端可以真正的訪問網絡。
其中,第一件事是通過上述代碼中的notifyAllOfConnected完成的,第二件事是通過創建DcNetworkAgent完成,接下來我們分別介紹完成這兩件事的過程。
7 通知數據撥號成功
在DataConnection的DcActiveState中,我們已經知道撥號成功後,將調用notifyAllOfConnected函數:
private void notifyAllOfConnected(String reason) {
notifyAllWithEvent(null, DctConstants.EVENT_DATA_SETUP_COMPLETE, reason);
}
private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
............
for (ConnectionParams cp : mApnContexts.values()) {
..........
//消息發送給了DcTracker
Message msg = mDct.obtainMessage(event, pair);
AsyncResult.forMessage(msg);
msg.sendToTarget();
}
}
DcTracker收到EVENT_DATA_SETUP_COMPLETE消息後,將調用onDataSetupComplete進行處理。
private void onDataSetupComplete(AsyncResult ar) {
.....
if (ar.exception == null) {
............
if (dcac == null) {
//正常情況下,撥號前創建過DcAsyncChannel,不會進入該分支
.........
} else {
ApnSetting apn = apnContext.getApnSetting();
//有些APN,利用mms用的,配置了Proxy等屬性
if (apn != null && apn.proxy != null && apn.proxy.length() != 0) {
try {
String port = apn.port;
if (TextUtils.isEmpty(port)) port = "8080";
ProxyInfo proxy = new ProxyInfo(apn.proxy,
Integer.parseInt(port), null);
//通過DcAsyncChannel設入DataConnection的LinkProperties屬性中
dcac.setLinkPropertiesHttpProxySync(proxy);
} catch (NumberFormatException e) {
......
}
.........
//更新ApnContext的狀態
apnContext.setState(DctConstants.State.CONNECTED);
//判斷APN是否為網絡端配置的,國內見的比較少
boolean isProvApn = apnContext.isProvisioningApn();
..........
//如果不是網絡端配置的APN
if ((!isProvApn) || mIsProvisioning) {
.........
completeConnection(apnContext);
} else {
//網絡配置的APN,進行通知後,關閉radio
//這裡為什麼要這麼做,自己還不是太清楚
.........
setRadio(false);
}
}
}
} else {
//撥號失敗,也會發送通知
.........
}
......
}
通過上面的代碼,我們知道DcTracker判斷撥號結果符合要求後,將調用completeConnection函數:
private void completeConnection(ApnContext apnContext) {
........
//進行通知工作
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
//周期性讀取底層接口文件,判斷終端是否發送和接受數據,從而更新UI界面的上下行圖標,以後單獨介紹
startNetStatPoll();
//周期性地檢測終端是否出現問題:同樣是讀取底層文件,當連續發送10個包,但沒有收到回復時,認為終端出現問題,需要進行恢復,以後單獨介紹
startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
}
這裡我們主要關注Phone對象的notifyDataConnection函數,這個函數現在由抽象類Phone來實現:
public void notifyDataConnection(String reason, String apnType) {
mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType));
}
其中,mNotifier的類型為DefaultPhoneNotifier,是PhoneFactory調用makeDefaultPhone時創建的,傳入Phone對象中。
我們看看DefaultPhoneNotifier中的notifyDataConnection函數:
@Override
public void notifyDataConnection(Phone sender, String reason, String apnType,
PhoneConstants.DataState state) {
doNotifyDataConnection(sender, reason, apnType, state);
}
private void doNotifyDataConnection(Phone sender, String reason, String apnType,
PhoneConstants.DataState state) {
//獲取需要通知的參數
........
try {
//mRegistry為TelephonyRegistry的Binder代理端
if (mRegistry != null) {
mRegistry.notifyDataConnectionForSubscriber(....);
}
}catch (RemoteException ex) {
// system process is dead
}
}
通過上面的代碼,我們知道最終DefaultPhoneNotifier將通過Binder通信,調用TelephonyRegistry的接口。
我們看看TelephonyRegistry中的notifyDataConnectionForSubscriber:
public void notifyDataConnectionForSubscriber(.....) {
........
//mRecords記錄了注冊在TelephonyRegistry中的觀察者
synchronized (mRecords) {
.......
//如果狀態發生改變,例如從未連接變為連接
if (modified) {
//輪詢所有的觀察者
for (Record r : mRecords) {
//如果觀察者關注data狀態的變化,並且監聽的phone對應於建立連接的phone
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) &&
idMatch(r.subId, subId, phoneId)) {
try {
//通過回調函數進行通知
r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
} catch (RemoteException ex) {
//如果觀察者已經死亡,加入移除鏈表
mRemoveList.add(r.binder);
}
}
}
//移除異常觀察者對應的注冊信息
handleRemoveListLocked();
}
//輪詢所有的觀察者,對於監聽PRECISE_DATA_CONNECTION_STATE的觀察者進行通知
//與上面的相比,監聽這個消息可以獲得更多的dataConnection信息,但通知更為頻繁(沒有狀態發生改變才通知的限制),同時要求更高的權限
...........
}
//發送廣播進行通知
broadcastDataConnectionStateChanged(.......);
broadcastPreciseDataConnectionStateChanged(.........);
}
至此,我們已經分析框架是如何通知其它應用數據連接的狀態了。
對於APK的開發者而言,既可以監聽廣播來獲取數據連接的狀態,也可以通過調用TelephonyManager的接口:
public void listen(PhoneStateListener listener, int events)
只需要自己創建PhoneStateListener,指定subId(決定監聽哪個phone的數據連接),同時定義回調函數,並指定關注的事件(events指定,具體的值定義於PhoneStateListener.java中)。
8 ConnectivityService管理網絡
根據前面的代碼,我們知道DataConnection在DcActiveState中,創建出了DcNetworkAgent。DcNetworkAgent是DataConnection的內部類,繼承NetworkAgent。
我們看看NetworkAgent的構造函數:
public NetworkAgent(.....) {
.........
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
//通過ConnectivityManager將自己注冊到ConnectivityService
//注意到此處的Messenger中包裹了NetworkAgent自身,NetworkAgent繼承自Handler
//ConnectivityService將通過AsyncChannel與NetworkAgent通信
netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
}
ConnectivityManager通過Binder通信調用ConnectivityService中的接口:
public int registerNetworkAgent(....) {
//權限檢查
enforceConnectivityInternalPermission();
//利用輸入參數構建NetworkAgentInfo,用來存儲整個網絡有關的信息
final NetworkAgentInfo nai = new NetworkAgentInfo(......);
.......
//發送消息給自己的Handler處理
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
return nai.network.netId;
}
接下來,ConnectivityService的handle收到消息後,調用handleRegisterNetworkAgent進行處理:
private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
//與注冊NetworkFactory一樣,注冊的NetworkAgent信息也會被存儲到ConnectivityService
mNetworkAgentInfos.put(na.messenger, na);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(na.network.netId, na);
}
//同樣,mTrackerHandler與NetworkAgent的handler連接在一起了
na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
//新創建的NetworkAgentInfo,為了復用更新NetworkAgentInfo的接口,才進行了下述操作
NetworkInfo networkInfo = na.networkInfo;
na.networkInfo = null;
//更新NetworkAgentInfo中的NetworkInfo
updateNetworkInfo(na, networkInfo);
}
繼續跟進updateNetworkInfo:
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
..........
//新創建的NetworkAgentInfo的created字段為false
//DataConnection在DcActiveState將NetworkInfo的狀態置為了CONNECTED
if (!networkAgent.created
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
try {
//如果建立的是VPN網絡
if (networkAgent.isVPN()) {
//mNetd是NetworkManagementService的binder代理端
mNetd.createVirtualNetwork(.....);
} else {
//對於實際的網絡將進入這個分支,NetworkManagementService的操作,我們在後文再講述
mNetd.createPhysicalNetwork(.....);
}
}catch (Exception e) {
...........
}
}
//新創建的NetworkAgentInfo進入該分支
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
//更新NetworkAgentInfo中的鏈路信息,例如mtu,dns, 路由等
updateLinkProperties(networkAgent, null);
//發送消息給NetworkMonitor;NetworkMonitor也是個狀態機,收到消息後,負責通進行HTTP訪問,並根據返回結果,判斷網絡是否可用,是否需要認證
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
..............
//判斷新注冊的NetworkAgent能否保留
rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP);
..............
//斷開連接時,注銷NetworkAgentInfo將進入這個分支
} else if (state == NetworkInfo.State.DISCONNECTED) {
..............
//NetworkAgent處於掛起態時,進入該分支
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED)
|| state == NetworkInfo.State.SUSPENDED){
.............
}
}
總結一下上面的代碼,目前我們主要需要關注的是:通過NetworkManagementService創建實際的物理網絡,更新網絡的鏈路信息,判斷NetworkAgent能否被保留。
ConnectivityService判斷NetworkAgent能否被保留的原因,之前的blog中其實提過:當兩個網絡同時滿足一個需求時,僅保留分數較高的。
因此當一個新的NetworkAgent注冊到ConnectivityService時,需要判斷這個NetworkAgent是否與已經注冊過的NetworkAgent產生沖突。
我們看看rematchNetworkAndRequests函數:
private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,
ReapUnvalidatedNetworks reapUnvalidatedNetworks) {
...........
//keep最後決定新加入的NetworkAgent是否保留
boolean keep = newNetwork.isVPN();
//匹配ConnectivityService初始化時創建默認NetworkRequest的NetworkAgent,將成為終端的默認網絡
boolean isNewDefault = false;
//存儲受到影響的NetworkAgentInfo
//新加入的NetworkAgentInfo可能同時是多個networkRequest的最優匹配對象
//於是這些NetworkRequest原來的匹配對象就是受到影響的NetworkAgentInfo
ArrayList affectedNetworks = new ArrayList();
//記錄需要進行通知的對象
//APK可以通過ConnectivityManager的接口,注冊監聽網絡變化
ArrayList addedRequests = new ArrayList();
//每一個NetworkRequest都需要進行重新匹配
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
//取出NetworkRequest當前的最優NetworkAgent
final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
//判斷新注冊的NetworkAgent是否匹配這個NetworkRequest,即NetworkCapabilities是否能夠滿足
final boolean satisfies = newNetwork.satisfies(nri.request);
//同樣的NetworkAgent,匹配情況不變
if (newNetwork == currentNetwork && satisfies) {
keep = true;
continue;
}
//新增加的NetworkAgent匹配NetworkRequest
if (satisfies) {
//如果這個NetworkRequest僅用於監聽
if (!nri.isRequest()) {
//存入相應的對象中,通知時將使用
if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
continue;
}
//如果這個匹配的NetworkRequest沒有對應的NetworkAgent
//或者對應NetworkAgent的分數小於新增NetworkAgent
if (currentNetwork == null ||
currentNetwork.getCurrentScore() < newNetwork.getCurrentScore()) {
//舊有的NetworkAgent被取代
if (currentNetwork != null) {
currentNetwork.networkRequests.remove(nri.request.requestId);
currentNetwork.networkLingered.add(nri.request);
//被取代後,加入到affectedNetworks中
affectedNetworks.add(currentNetwork);
} else {
//log
.......
}
//新增加的NetworkAgent不會被移除
unlinger(newNetwork);
mNetworkForRequestId.put(nri.request.requestId, newNetwork);
if (!newNetwork.addRequest(nri.request)) {
..........
}
addedRequests.add(nri);
keep = true;
//由於NetworkRequest匹配到了新的NetworkAgent,因此更新一下分數,以免NetworkFactory進行不必要的建立連接的操作
sendUpdatedScoreToFactories(nri.request, newNetwork.getCurrentScore());
//如果匹配的是ConnectivityService中默認的Request,那麼新的NetworkAgent將成為默認使用的網絡
if (mDefaultRequest.requestId == nri.request.requestId) {
isNewDefault = true;
oldDefaultNetwork = currentNetwork;
}
}//下面這個分支的意思是:NetworkAgent中包含NetworkRequest但不匹配;說明NetworkAgent之前匹配,屬性發生變化導致不匹配了
} else if (newNetwork.networkRequests.get(nri.request.requestId) != null){
newNetwork.networkRequests.remove(nri.request.requestId);
//如果這個不再匹配的networkAgent曾經是最匹配的,那麼需要更新分數,讓合適的NetworkFactory建立連接
if (currentNetwork == newNetwork) {
mNetworkForRequestId.remove(nri.request.requestId);
sendUpdatedScoreToFactories(nri.request, 0);
} else {
if (nri.isRequest()) {
//僅打印log,不應該進入到這個分支
................
}
}
//通過回調接口通知觀察者,網絡斷開
callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST);
}
}
//處理受影響的NetworkAgent
for (NetworkAgentInfo nai : affectedNetworks) {
//NetworkAgent處於等待移除的狀態,不用管
if (nai.lingering) {
} else if (unneeded(nai)) { //unneeded函數判斷該NetworkAgent是否為其它NetworkRequest的最優匹配對象,如果不是就可以移除
//NetworkMonitor發送消息進入linger狀態,30s後移除無用NetworkAgent
linger(nai);
} else {
//保留NetworkAgent
unlinger(nai);
}
}
//如果是新的默認網絡
if (isNewDefault) {
//通過NetworkManagementService將該Network設置為默認網絡
makeDefault(newNetwork);
...............
}
...............
//如果輸入參數為ReapUnvalidatedNetworks.REAP,則不經過linger狀態,直接關閉無效NetworkAgent
if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
if (unneeded(nai)) {
if (DBG) log("Reaping " + nai.name());
teardownUnneededNetwork(nai);
}
}
}
}
以上就是數據長連接撥號中,ConnectivityService參與的主要流程。其中,就是rematchNetworkAndRequests函數過長,導致看起來比較繁瑣。
但整個過程相對而言還是比較簡單的,其實就是讓ConnectivityService來管理數據撥號產生的NetworkAgent,包括判斷該NetworkAgent能否保留,是否需要更新其它現有的NetworkAgent。
9 NetworkManagementService創建和配置網絡
我們知道Android是運行在Linux之上的,前面的撥號實際上僅在框架層形成了網絡的抽象對象,還需要在Native層中形成網絡抽象,這就需要依賴於NetworkManagementService了。
在前面的代碼中,我們已經提到過ConnectivityService會通過NetworkManagementService創建網絡,配置路由等網絡屬性。現在我們看看NetworkManagementService到底是如何做到的。
9.1 創建網絡
//ConnectivityService中調用以下代碼創建網絡
//mNetd是NetworkManagementService對應的binder代理端
mNetd.createPhysicalNetwork(networkAgent.network.netId,
networkAgent.networkCapabilities.hasCapability(
NET_CAPABILITY_NOT_RESTRICTED) ?
null : NetworkManagementService.PERMISSION_SYSTEM);
看看NetworkManagementService中對應的createPhysicalNetwork:
public void createPhysicalNetwork(int netId, String permission) {
//權限檢查
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
if (permission != null) {
mConnector.execute("network", "create", netId, permission);
} else {
//默認的數據業務,是沒有permission要求的
mConnector.execute("network", "create", netId);
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
代碼中,mConnector是NativeDaemonConnector,是用於連接NetworkManagementService與netd的。netd是Android中管理網絡的守護進程。在之前的版本中,netd的啟動定義與init.rc中,由init進程啟動;在android 7.0中,定義於/system/netd/server/netd.rc中:
service netd /system/bin/netd
class main
socket netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
socket mdns stream 0660 root system
socket fwmarkd stream 0660 root inet
目前自己沒發現netd.rc是如何集成到整個Android啟動過程中的,如果有朋友知道的話,請指點一下。
接下來,我們分析一下NetworkManagementService中的代碼:
//創建NetworkManagementService
public static NetworkManagementService create(Context context) throws InterruptedException {
//NETD_SOCKET_NAME為"netd",是netd進程啟動時創建的socket
return create(context, NETD_SOCKET_NAME);
}
static NetworkManagementService create(Context context, String socket) throws InterruptedException {
//創建NetworkManagementService,其中創建NativeDataConnector
final NetworkManagementService service = new NetworkManagementService(context, socket);
//service.mConnectedSignal的值為CountDownLatch(1),只用遞減1次
final CountDownLatch connectedSignal = service.mConnectedSignal;
//NativeDataConnector連接netd
service.mThread.start();
//等待連接成功
connectedSignal.await();
return service;
}
private NetworkManagementService(Context context, String socket) {
.............
//創建NativeDaemonConnector,繼承runnable
//NetdCallbackReceiver為回調函數
mConnector = new NativeDaemonConnector(new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160, wl, FgThread.get().getLooper());
mThread = new Thread(mConnector, NETD_TAG);
.............
}
從上面的代碼可以看出,NetworkManagementService調用service.mThread.start()後,將調用NativeDaemonConnector的run方法:
public void run() {
mCallbackHandler = new Handler(mLooper, this);
while (true) {
try {
listenToSocket();
} catch (Exception e) {
loge("Error in NativeDaemonConnector: " + e);
SystemClock.sleep(5000);
}
}
}
private void listenToSocket() throws IOException {
LocalSocket socket = null;
try {
socket = new LocalSocket();
//返回"netd"的地址
LocalSocketAddress address = determineSocketAddress();
//本地socket連接netd socket
socket.connect(address);
InputStream inputStream = socket.getInputStream();
synchronized (mDaemonLock) {
mOutputStream = socket.getOutputStream();
}
//連接成功後,調用NetworkManagementService中定義的回調函數
mCallbacks.onDaemonConnected();
//後面的部分暫時不用管,其實就是接受netd socket發過來的數據
............
}
看看NetworkManagementService中定義的回調接口:
private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
public void onDaemonConnected() {
if (mConnectedSignal != null) {
//將countDownLatch減1,觸發NetworkManagementService的構造函數返回
mConnectedSignal.countDown();
mConnectedSignal = null;
} else {
mFgHandler.post(new Runnable() {
@Override
public void run() {
prepareNativeDaemon();
}
});
}
}
........
}
根據上面的代碼,我們知道了NativeDaemonConnector創建的過程,並且知道了NativeDaemonConnector通過socket與netd進程中名為”netd”的socket相連。於是,我們就可以得出結論:NativeDaemonConnector是NetworkManagementService與netd進程通信的橋梁。
現在我們回到NetworkManagementService創建physical network的流程:
.............
//調用NativeDaemonConnector的execute方法
mConnector.execute("network", "create", netId);
.............
進入NativeDaemonConnector:
public NativeDaemonEvent execute(String cmd, Object... args)
throws NativeDaemonConnectorException {
//DEFAULT_TIMEOUT的時間為1min;如果一個cmd執行時間超過1min,watchdog將會殺死進程
return execute(DEFAULT_TIMEOUT, cmd, args);
}
public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
//調用executeForList執行
final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
.............
}
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
................
//記錄命令發起時間
final long startTime = SystemClock.elapsedRealtime();
..............
//每個cmd的有唯一編號
final int sequenceNumber = mSequenceNumber.incrementAndGet();
//利用參數構造netd規定的cmd格式
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
final String rawCmd = rawBuilder.toString();
synchronized (mDaemonLock) {
if (mOutputStream == null) {
............
} else {
try {
//將消息發送給netd
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
} catch() {
...........
}
}
}
NativeDaemonEvent event = null;
do {
//從mResponseQueue取出返回結果;mResponseQueue的類型為BlockingQueue,此處最多等待timeoutMs
//前文中,NativeDaemonConnector的run方法中,創建socket並連接netd後,接收的消息進行解析後會放入mResponseQueue中
event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
}while (event.isClassContinue());
//記錄收到返回結果的時間
final long endTime = SystemClock.elapsedRealtime();
//根據返回結果判斷命令是否執行異常
...............
}
netd守護進程以後單獨分析一下,這裡我們只需要知道netd中定義了CommandListener,用於處理不同的命令。
CommandListener定義於文件system/netd/server/CommandListener.cpp中:
CommandListener::CommandListener() :
FrameworkListener("netd", true) {
registerLockingCmd(new InterfaceCmd());
registerLockingCmd(new IpFwdCmd());
registerLockingCmd(new TetherCmd());
registerLockingCmd(new NatCmd());
registerLockingCmd(new ListTtysCmd());
registerLockingCmd(new PppdCmd());
registerLockingCmd(new SoftapCmd());
registerLockingCmd(new BandwidthControlCmd(), gCtls->bandwidthCtrl.lock);
registerLockingCmd(new IdletimerControlCmd());
registerLockingCmd(new ResolverCmd());
registerLockingCmd(new FirewallCmd(), gCtls->firewallCtrl.lock);
registerLockingCmd(new ClatdCmd());
registerLockingCmd(new NetworkCommand());
registerLockingCmd(new StrictCmd());
...................
}
每種Cmd的名稱基本上能概括它們的功能。
這裡我們看一下NetowrkCommand:
//前面我們提過,NetworkManagementService創建網絡時,第一個參數就是“network”
//因此在NativeDaemonConnector創建cmd時,指定的參數也是“network”
//netd進程收到消息後,就用對應的NetworkCommand表示
CommandListener::NetworkCommand::NetworkCommand() : NetdCommand("network") {
}
//對應的處理函數
int CommandListener::NetworkCommand::runCommand(SocketClient* client, int argc, char** argv) {
..........
//根據傳入參數,做相應的處理
if (!strcmp(argv[1], "route")) {
.............
}
if (!strcmp(argv[1], "interface")) {
.............
}
if (!strcmp(argv[1], "create")) {
//判斷參數有效性
.........
//解析出framework分配的netId
unsigned netId = stringToNetId(argv[2]);
if (argc == 6 && !strcmp(argv[3], "vpn")) {
//創建VPN
............
} else if (argc > 4) {
return syntaxError(client, "Unknown trailing argument(s)");
} else {
//默認的數據網絡,是沒有permission限制的
Permission permission = PERMISSION_NONE;
if (argc == 4) {
permission = stringToPermission(argv[3]);
if (permission == PERMISSION_NONE) {
return syntaxError(client, "Unknown permission");
}
}
//調用NetworkController.cpp的createPhysicalNetwork
if (int ret = gCtls->netCtrl.createPhysicalNetwork(netId, permission)) {
return operationError(client, "createPhysicalNetwork() failed", ret);
}
}
}
..............
}
NetworkController.cpp位於system/netd/server目錄下:
int NetworkController::createPhysicalNetwork(unsigned netId, Permission permission) {
//檢查netId的有效性
..............
//創建網絡對象
PhysicalNetwork* physicalNetwork = new PhysicalNetwork(netId, mDelegateImpl);
//如果有權限,還要設置權限
if (int ret = physicalNetwork->setPermission(permission)) {
ALOGE("inconceivable! setPermission cannot fail on an empty network");
delete physicalNetwork;
return ret;
}
android::RWLock::AutoWLock lock(mRWLock);
//保存新建的網絡
mNetworks[netId] = physicalNetwork;
return 0;
}
至此,Android的框架完成了在Native層創建網絡對象的工作。
9.2 配置網絡
ConnectivityService在創建完網絡後,調用了updateLinkProperties函數:
private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
...............
//這些函數均通過NetworkManagementService,利用NativeDaemonConnector發送命令給Netd進程
updateInterfaces(newLp, oldLp, netId);
updateMtu(newLp, oldLp);
..............
updateRoutes(newLp, oldLp, netId);
updateDnses(newLp, oldLp, netId);
...............
}
當新建的網絡成為default網絡後,ConnectivityService會調用makeDefault函數:
private void makeDefault(NetworkAgentInfo newNetwork) {
try {
//同樣利用NetworkManagementService發送命令給netd
mNetd.setDefaultNetId(newNetwork.network.netId);
} catch (Exception e) {
loge("Exception setting default network :" + e);
}
...................
//TCP buffer大小是通過修改系統屬性得到的
updateTcpBufferSizes(newNetwork);
}
以上函數調用,最終均會在CommandListener中按照各種類型的Command定義的方式進行處理,用於配置網絡的各種屬性。其中的調用方式,與創建網絡基本類似,不再做深入分析。
經過上面的分析,我們來總結一下,對於Android而言,什麼叫做一個可用的網絡?
其實可以認為網絡就是一個可用的網卡接口(Interface),加上針對該接口的屬性,例如IP地址、dns、mtu以及路由等。
前面的流程中我們知道框架撥號成功後,利用撥號返回結果中攜帶的信息,創建並配置了網絡。這些信息利用IP地址等,是modem與網絡側協商得到的,但接口使如何分配的呢?此外,我們知道Android是運行在Linux上的,那麼撥號成功後,Linux又是如何開啟一個實際接口的呢?
其實這部分內容被封裝在了RIL以下,由不同的廠商來實現。例如,Qualcomm中定義了NETGMRD進程,當撥號成功後,NETMGRD利用撥號得到的信息,配置Linux的數據協議棧。這部分內容是廠家的機密,就不方便寫在Blog中了。
結束語
數據業務長連接撥號對應的流程比較繁瑣,即使不包含RIL層以下,也很難看一遍就完全掌握。我們略去了很多細節,僅梳理了大致的脈絡。
最後我們還是整理一下,整個流程涉及的類圖和流程圖:


Android應用開發編譯框架流程與IDE及Gradle概要
1 背景建議閱讀本文之前先閱讀《Android Studio入門到精通》和《Groovy腳本基礎全攻略》及《Gradle腳本基礎全攻略》三篇博客作為背景知識,這樣才能更好
Android ListView分頁功能實現方法
通過本次小Demo我學到了:1、ListView的小小的一個分頁功能2、加深了對自定義控件的理解3、對ListView的優化4、對BaseAdapter的使用5、自定義A
對android M中Call的概要總結
主要內容1. Call涉及的目錄結構及框架結構2. InCallUI層的基本架構(所涉及的Presenter、Fragment及Activity)3. Call的幾種狀態
android開發游記:meterial design 5.0 新增控件介紹及使用方法
Android 5.0 Lollipop 是迄今為止最重大的一次發布,因為 material design 的存在,android的界面風格發生了新的改變,這是一門新的設