ConnectivityService框架初識

Android中提供的資料業務方式有幾種:移動資料網路,WIFI,熱點,網線等。這些資料業務本身可以獨立使用,但是同一時刻,只能使用其中的一種資料業務方式。管理這些資料業務方式的使用由ConnectivityService,NetworkFactory,NetworkAgent,NetworkMonitor等來完成,ConnectivityService處於核心排程位置。

ConnectivityService框架主要有四個方面組成:

一 . 網路有效性檢測(NetworkMonitor)
二 . 網路評分機制(NetworkFactory)
三 . 路由配置資訊的獲取(NetworkAgent)
四 . 網路物理埠的設定(Netd)

其大體框架圖如下:
這裡寫圖片描述

這篇部落格就針對前兩個方面進行逐一分析,Netd這一塊暫時還沒研究到,待日後有空再整理下。

ConnectivityService的工作總結起來就是:通過wifi,mobile data,Tethering,VPN 等方式來獲取路由配置資訊。無論通過哪種方式,獲取到路由配置資訊後,需要交給ConnectivityService來處理,ConnectivityService通過ping網路來檢查網路的有效性,進而影響到各個資料業務方式的評分值,ConnectivityService通過這些評分值來決定以哪個資料業務方式連線網路。決定好資料業務方式後,把這些路由配置資訊設定到網路物理裝置中。這樣我們的手機就可以正常上網了。


初始化:

ConnectivityService屬於系統服務,在SystemServer中被啟動。

SystemServer啟動的服務:

NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
NetworkScoreService networkScore = null;

一 . ConnectivityService初始化

1.獲取其他服務的介面

private INetworkManagementService mNetd;
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyManager;
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mStatsService = checkNotNull(statsService, "missing INetworkStatsService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);

2.註冊其他必要的監聽和廣播,以便接收變化資訊和通知變化資訊。

mNetd.registerObserver(mTethering);
mNetd.registerObserver(mDataActivityObserver);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_STARTED);
intentFilter.addAction(Intent.ACTION_USER_STOPPED);
intentFilter.addAction(Intent.ACTION_USER_ADDED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);

二 . NetworkFactory的初始化

NetworkFactory負責了網路評分機制的功能,為了在手機開機後可以及時依靠網路評分機制來選擇網路。ConnectivityService服務起來後,在各個模組的初始化過程中,NetworkFactory必須要啟動起來。以下的時序圖只畫了mobile data和wifi模組的NetworkFactory啟動流程:
這裡寫圖片描述

NetworkFactory在register()之後通過AsyncChannel與ConnectivityService建立起了連線,這一塊的邏輯流程,如果看不太懂,那麼需要去看看:AsyncChannel的工作機制

public void register() {
if (DBG) log("Registering NetworkFactory");
if (mMessenger == null) {
mMessenger = new Messenger(this);
ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
}
}
public void registerNetworkFactory(Messenger messenger, String name) {
enforceConnectivityInternalPermission();
NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
}
private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
if (DBG) log("Got NetworkFactory Messenger for "   nfi.name);
mNetworkFactoryInfos.put(nfi.messenger, nfi);
nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}

三 . NetworkAgent的初始化

NetworkAgent是一個網路代理,它裡面儲存了一些路由的配置資訊,比如NetworkInfo,LinkProperties,NetworkCapabilities等。NetworkAgent的初始化都是在路由配置資訊獲取成功之後。比如開啟資料開關,開啟wifi開關等操作之後。

注:
NetworkInfo 描述一個給定型別的網路介面的狀態方面的資訊,包括網路連線狀態、網路型別、網路可連線性、是否漫遊等資訊
LinkProperties 描述一個網路連線屬性資訊(包含網路地址、閘道器、DNS、HTTP代理等屬性資訊
NetworkCapabilities 描述一個網路連線能力方面的資訊,包括頻寬、延遲等

四 . NetworkMonitor的初始化

NetworkMonitor主要是檢測網路有效性的,通過Http封裝類去ping一個網站,根據ping網站的結果來影響評分值。因此,它的初始化是在NetworkAgent初始化之後,必須要獲取到路由配置資訊NetworkAgent後才會去初始化。


一 . 網路有效性檢測(NetworkMonitor)

NetworkMonitor是一個狀態機。負責檢測網路有效性,也就是ping網路的過程。ping網路過程中產生的幾種狀態如下:

DefaultState 預設狀態
EvaluatingState 驗證狀態
ValidatedState 驗證通過狀態
LingeringState 休閒狀態,表示網路的驗證位是真實的,並且曾經是滿足特定NetworkRequest的最高得分網路,但是此時另一個網路滿足了NetworkRequest的更高分數,在斷開連線前的一段時間前,該網路被“固定”為休閒狀態。
CaptivePortalState 強制門戶狀態
MaybeNotifyState 可能通知狀態,表示使用者可能已被通知需要登入。 在退出該狀態時,應該小心清除通知。

NetworkMonitor中各個狀態之間的關係:

這裡寫圖片描述

以正常的ping網站過程為例,DefaultState為預設狀態,NetworkMonitor接收到CMD_NETWORK_CONNECTED事件訊息後,先由DefaultState狀態處理,然後由EvaluatingState處理,最後交給ValidatedState處理。這一塊的邏輯流程,如果看不太懂,那麼需要去看看StateMachine狀態機的使用:StateMachine狀態機初識

從NetworkMonitor的初始化,到ping網站的過程,到ping網站的結果影響評分值。這個過程的時序圖如下:
這裡寫圖片描述

接下來將按照時序圖中的三大步驟去結合程式碼分析。

1 . NetworkMonitor的初始化(以mobile data為例)

DataConnection從modem中獲取到了代理資訊,並把此代理資訊儲存到了NetworkAgent中:

mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
"DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
50, misc);

在NetworkAgent的建構函式中,把它自己註冊到了ConnectivityService中,

ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);

接下來的流程就如時序圖所示了,意味著每產生一個代理資訊NetworkAgent的物件,就會有自己相應的NetworkMonitor狀態機來處理ping網站的過程。

2 . ping網站的過程

NetworkMonitor狀態機執行起來後,接收到sendMessage的訊息就可以做相應的處理。這裡比較重要的就是CMD_NETWORK_LINGER和CMD_NETWORK_CONNECTED訊息,分別由ConnectivityService的linger()和unlinger()方法封裝傳送的操作。

linger():

封裝了CMD_NETWORK_LINGER訊息的傳送操作,讓NetworkMonitor進入到休閒狀態:
CMD_NETWORK_LINGER訊息首先進入到DefaultState.processMessage()處理:

case CMD_NETWORK_LINGER:
transitionTo(mLingeringState);

CMD_NETWORK_LINGER訊息切換到LingeringState處理,enter()做一個CMD_LINGER_EXPIRED訊息延遲傳送:

public void enter() {
mEvaluationTimer.reset();
final String cmdName = ACTION_LINGER_EXPIRED   "."   mNetId;
mWakeupMessage = makeWakeupMessage(mContext, getHandler(), cmdName, CMD_LINGER_EXPIRED);
long wakeupTime = SystemClock.elapsedRealtime()   mLingerDelayMs;
mWakeupMessage.schedule(wakeupTime);
}

LingeringState.processMessage()中對延遲訊息CMD_LINGER_EXPIRED做處理:

case CMD_LINGER_EXPIRED:
mConnectivityServiceHandler.sendMessage(
obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
return HANDLED;

ConnectivityService中處理EVENT_NETWORK_LINGER_COMPLETE訊息:

case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
if (isLiveNetworkAgent(nai, msg.what)) {
handleLingerComplete(nai);
}
break;
}
private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
teardownUnneededNetwork(oldNetwork);
}
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
for (int i = 0; i < nai.networkRequests.size(); i  ) {
NetworkRequest nr = nai.networkRequests.valueAt(i);
if (!isRequest(nr)) continue;
break;
}
nai.asyncChannel.disconnect();
}

綜上,CMD_NETWORK_LINGER訊息的處理就是讓NetworkMonitor進入空閒狀態,NetworkMonitor處於空閒狀態說明此網路不再需要,可以釋放掉ConnectivityService和NetworkAgent的連線。

unlinger():

封裝了CMD_NETWORK_CONNECTED訊息的傳送操作,讓NetworkMonitor進入到非休閒狀態:
CMD_NETWORK_CONNECTED訊息首先進入到DefaultState.processMessage()處理:

case CMD_NETWORK_CONNECTED:
transitionTo(mEvaluatingState);
return HANDLED;

CMD_NETWORK_LINGER訊息切換到EvaluatingState處理,enter()方法傳送CMD_REEVALUATE訊息:

public void enter() {
sendMessage(CMD_REEVALUATE,   mReevaluateToken, 0);
}

CMD_REEVALUATE訊息由EvaluatingState.processMessage()處理:
Ping網路的關鍵地方:

CaptivePortalProbeResult probeResult = isCaptivePortal();

isCaptivePortal()通過HttpURLConnection類去ping一個網站,android原生給的網站在中國由於牆的存在,是ping不通的,因此就會出現wifi和訊號格旁邊有一個感嘆號。晶片廠商一般會對這個網站進行客製化:這裡寫圖片描述

private static String getCaptivePortalServerUrl(Context context, boolean isHttps) {
String server = Settings.Global.getString(context.getContentResolver(),
Settings.Global.CAPTIVE_PORTAL_SERVER);
if (server == null) server = DEFAULT_SERVER_SECONDARY;
return (isHttps ? "https" : "http")   "://"   server   "/generate_204";
}

在log中可以找到當前ping的網站是哪一個:

這裡寫圖片描述

3 . 根據ping網站的結果影響評分值

if (probeResult.isSuccessful()) {
transitionTo(mValidatedState);
} else if (probeResult.isPortal()) {
transitionTo(mCaptivePortalState);
} else {
final Message msg = obtainMessage(CMD_REEVALUATE,   mReevaluateToken, 0);
sendMessageDelayed(msg, mReevaluateDelayMs);
mReevaluateDelayMs *= 2;
if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
}
}
boolean isSuccessful() { return mHttpResponseCode == 204; }
boolean isPortal() {
return !isSuccessful() && mHttpResponseCode >= 200 && mHttpResponseCode <= 399;
}

根據ping網路的結果來執行不同的操作:
一.如果ping網路成功,網路返回204,切換到ValidatedState狀態處理。
二.如果ping網路失敗,網路返回200~399,轉到CaptivePortalState狀態處理。
三.如果ping網路失敗,不是204,也不是200~399,則傳送CMD_REEVALUATE訊息,重新觸發ping網路的動作。第一次失敗,8s後重新ping網路,第二次失敗,16s後重新ping網路,時間依次倍增,最長的時間間隔為10分鐘。

重點關注ping網路成功後,ValidatedState狀態的處理:ValidatedState.enter()

mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));

ConnectivityService中處理EVENT_NETWORK_TESTED訊息,把ping成功的狀態儲存到NetworkAgentInfo中,並通知NetworkFactory評分值已變,需要重新評估。

if (valid != nai.lastValidated) {
final int oldScore = nai.getCurrentScore();
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}

這裡需要說明一下,ping網路的狀態會儲存到NetworkAgentInfo中,而後續所有的評分值都會呼叫NetworkAgentInfo的getCurrentScore()方法來獲取,getCurrentScore()方法會根據當前ping網路的狀態重新計算評分值:

private int getCurrentScore(boolean pretendValidated) {
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
return MAXIMUM_NETWORK_SCORE;
}
int score = currentScore;
if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) {
score -= UNVALIDATED_SCORE_PENALTY;
}
if (score < 0) score = 0;
return score;
}

如果是使用者指定的聯網方式,評分值設定為100,如果ping網路失敗,評分值-40,如果ping網路成功,則評分值不變。

二 . 網路評分機制(NetworkFactory)

NetworkFactory的存在意義就是為了幫助ConnectivityService進行評分的管理。一般在NetworkFactory在初始化時,設定固定的評分值,作為評判的標準。
NetworkAgent作為一個代理資訊的抽象,在其初始化時,也設定了固定的評分值,不過,這個評分值會根據當前的網路情況的不同而變化,其最後的評分值會和NetworkFactory中的固定評分值進行比較,從而篩選出最優網路。

NetworkFactory和NetworkAgent的評分初始化:

TelephonyNetworkFactory:

private final static int TELEPHONY_NETWORK_SCORE = 50;
setCapabilityFilter(makeNetworkFilter(subscriptionController, phoneId));
setScoreFilter(TELEPHONY_NETWORK_SCmNetworkAgent = new 

DataConnection(NetworkAgent子類):

DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
"DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
50, misc);
ORE);

以上只列出了TelephonyNetwork的評分初始化情況。以下是各個網路型別的評分初始化和變化情況:

NetworkFactory初始化NetworkAgent初始化NetworkMonitor中ping網路disconnect
TelephonyNetwork5050成功: 0 失敗:-40 使用者指定: 1000
Wifi6060成功: 0 失敗:-40 使用者指定: 1000
EthernetNetwork6969成功: 0 失敗:-40 使用者指定: 1000
PhoneSwitcher101101成功: 0 失敗:-40 使用者指定: 1000

各種資料業務型別的評分標準,除了其基礎評分值不同之外,其他的評判標準都一樣。其評分值的變化,主要有以下幾種情況:

一.代理資訊獲取結束後,會參與ping網路的過程,如果ping網路成功,那麼NetworkAgent中的評分值不變。如果ping網路失敗,那麼NetworkAgent中的評分值-40。如果使用者指定了某種網路型別作為連線方式,那麼NetworkAgent重的評分值 100。

二.如果NetworkAgent和ConnectivityService的AsyncChannel通道斷開,需要設定其評分值為0,好讓其他的評分高的網路型別連線。

NetworkFactory中的評分標準:

NetworkFactory中維持了基礎的評分分值mScore,mScore只有在 NetworkFactory物件建立的時候才會賦值。因網路環境的變化導致需要重新進行網路評估時,使用基礎評分分值與傳進來的NetworkRequestInfo中的分值進行比較。如果當前的NetworkRequestInfo沒有requested過,且當前的分值score比基礎分值mScore小,說明當前的NetworkRequestInfo為最優網路,呼叫needNetworkFor()連線網路。如果當前的NetworkRequestInfo已經requested過,且當前的分值score比基礎分值mScore大,說明當前的NetworkRequestInfo已經不是最優網路了,有個更優的網路可用連線,此時應該呼叫releaseNetworkFor()釋放掉此類網路連線。

private void evalRequest(NetworkRequestInfo n) {
if (VDBG) log("evalRequest request = "   n.request   " with requested = "   n.requested);
if (n.requested == false && n.score < mScore &&
n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter) && acceptRequest(n.request, n.score)) {
if (VDBG) log("  needNetworkFor");
needNetworkFor(n.request, n.score);
n.requested = true;
} else if (n.requested == true &&
(n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
if (VDBG) log("  releaseNetworkFor");
releaseNetworkFor(n.request);
n.requested = false;
} else {
if (VDBG) log("  done");
}
}

以上是NetworkFactory的評分標準,那麼,應該在哪裡觸發這個評分過程呢?

觸發評分的過程:

1.NetworkFactory與ConnectivityService通過AsyncChannel建立連線的時候,初始化評分,並參與了第一次的評分過程。如果此時還沒有ping網路的話,其傳進來的評分值為基礎評分值,以上程式碼會執行else邏輯。
這裡寫圖片描述

2.呼叫sendUpdatedScoreToFactories()方法觸發了評分過程

在ping網路過程中,會觸發多次評分過程。在NetworkMonitor的多個狀態中,都有向ConnectivityService發起EVENT_NETWORK_TESTED事件訊息更新評分:

                mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));

ConnectivityService接收到EVENT_NETWORK_TESTED事件訊息,更新評分值和觸發評分機制:

private boolean maybeHandleNetworkMonitorMessage(Message msg) {
switch (msg.what) {
default:
return false;
case NetworkMonitor.EVENT_NETWORK_TESTED: {
if (valid != nai.lastValidated) {
final int oldScore = nai.getCurrentScore();
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}
}
}
}               

從log中看評分過程:

例子:開啟wifi開關連線wifi —> 開啟資料開關 —-> 關閉wifi開關

radio.log
Telephony 角度看評分:
這裡寫圖片描述

wifi角度看評分:
這裡寫圖片描述

從wifi的log來看,此次wifi的評分過程沒有滿足條件,因此都沒有執行releaseNetworkFor()或者needNetworkFor()方法。即使wifi執行了needNetworkFor()和releaseNetworkFor()方法,對於wifi也沒有影響,因為wifi並沒有實現對這兩個方法的具體實現。如果存在比wifi優先順序更高的資料業務方式,此處應該是要自己實現的吧??

WifiStateMachine.java

private class WifiNetworkFactory extends NetworkFactory {
public WifiNetworkFactory(Looper l, Context c, String TAG, NetworkCapabilities f) {
super(l, c, TAG, f);
}
@Override
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
mConnectionRequests;
}
@Override
protected void releaseNetworkFor(NetworkRequest networkRequest) {
--mConnectionRequests;
}
}

三 . 路由配置資訊的獲取(NetworkAgent)

路由配置資訊的獲取方式有多種,wifi,mobile data,Tethering,VPN都可,此處主要研究的是mobile data的路由配置資訊的獲取,由於篇幅較長,因此另起一篇部落格。

四 . 網路物理埠的設定(Netd)

此篇章暫時留空白,待日後補充。可參考:http://www.360doc.com/content/13/0817/17/9171956_307859123.shtml

借用一張圖表示Netd層的框架:
這裡寫圖片描述