本節介紹一個比較特殊的流程,就是網絡可用性對評分的影響。
該影響主要展現在,當一個網絡連接配接建立時,系統将用該連接配接Ping一個Google的網站來判斷該連接配接是否真的可以上網,如果不可以,那麼就會扣掉該網絡40分,進而可能導緻該網絡的評分低于其他網絡評分,下面來看詳細過程。
一、NetworkMonitor來源
在前面《 網絡連接配接評分機制之NetworkAgent》我們分析過,當某個NetworkFactory連接配接上網絡時,就會建立NetworkAgent對象,然後注冊到ConnectivityService,而在注冊過程中,ConnectivityService将會利用NetworkAgent傳遞過來的NetworkInfo、Messenger、分值等資訊建立NetworkAgentInfo對象。而在該對象的建立過程中,将會建立一個網絡監聽器NetworkMonitor,下面來看這個過程:
@ConnectivityService.java
public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkMisc networkMisc) {
//注冊NetworkAgent時需要建立NetworkAgentInfo
NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
new NetworkMisc(networkMisc));
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
}
這就是我們所說的注冊NetworkAgent時所建立的NetworkAgentInfo對象,然後來看該對象的屬性:
public class NetworkAgentInfo {}
再來看他提供的方法:
public void addRequest(NetworkRequest networkRequest) {}
public int getCurrentScore() {}
public void setCurrentScore(int newScore) {}
public String toString() {}
public String name() {}
然後來看NetworkAgentInfo建立過程:
@NetworkAgentInfo.java
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc) {
//各種指派
this.messenger = messenger;
asyncChannel = ac;
network = null;
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
currentScore = score;
//建立NetworkMonitor
networkMonitor = new NetworkMonitor(context, handler, this);
networkMisc = misc;
created = false;
validated = false;
}
從這些資訊我們看到,NetworkAgentInfo沒有繼承其他類,同時也隻是提供了一些設定或者查詢目前對象屬性的一些方法,該對象的 主要作用也就是儲存各個向ConnectivityService注冊的NetworkAgent,以便于查詢或修改某個NetworkAgent對象的相關資訊。
但是從NetworkAgentInfo的構造方法中我們看到他建立了一個NetworkMonitor對象,那麼該對象的作用是什麼呢?
二、NetworkMonitor作用與初始化流程
NetworkMonitor是ConnectivityService用于管理網絡連接配接狀态而建立的狀态機,主要作用就是檢測目前網絡的有效性。
下面我們來看NetworkMonitor的屬性與初始化流程:
@NetworkMonitor.java
public class NetworkMonitor extends StateMachine {}
public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
super(TAG + networkAgentInfo.name());
//初始化各種成員變量
mContext = context;
mConnectivityServiceHandler = handler;
mNetworkAgentInfo = networkAgentInfo;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
//初始化狀态機
addState(mDefaultState);
addState(mOfflineState, mDefaultState);
addState(mValidatedState, mDefaultState);
addState(mEvaluatingState, mDefaultState);
addState(mUserPromptedState, mDefaultState);
addState(mCaptivePortalState, mDefaultState);
addState(mLingeringState, mDefaultState);
//初始狀态機為DefaultState
setInitialState(mDefaultState);
mServer = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_SERVER);
if (mServer == null) mServer = DEFAULT_SERVER;
mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, DEFAULT_REEVALUATE_DELAY_MS);
mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
//開始狀态機
start();
}
從這個狀态機的初始化流程中我們可以看到兩個資訊:1、該類内部有七個狀态機;2、初始化的狀态機是DefaultState。接下來分别介紹各個狀态機的作用和狀态機運作流程。
三、NetworkMonitor各個狀态機作用
NetworkMonitor共有七個狀态機,分别是:DefaultState、OfflineState、ValidatedState、EvaluatingState、UserPromptedState、CaptivePortalState、LingeringState。他們的作用是:
DefaultState
----這是預設的狀态機,也是其他所有狀态機的父狀态,主要處理網絡連接配接的主要狀态變化,包括連接配接、斷開、測試、延時等模式。
EvaluatingState
----驗證狀态,網絡連上時,将會進入該狀态,然後會Ping網絡,判斷目前網絡有效性,并決定下一步是進入ValidatedState還是OfflineState或者UserPromptedState。
OfflineState
----脫機狀态,當Ping網絡時,始終沒有任何回應時,就會進入該狀态。
ValidatedState
----驗證通過的狀态,當Ping通網絡時,說明目前的網絡是通暢的,将會進入該狀态。
UserPromptedState
----驗證失敗狀态,當Ping網絡時,網絡給出了重定向異常,比如接入中國移動時會跳入移動的帳号認證頁面,需要使用者進行網絡登入後才可以繼續上網。此時一般需要在界面上提示使用者。
CaptivePortalState
----當網絡被測試失敗時進入UserPromptedState後,使用者可以通過發送ACTION_SIGN_IN_REQUESTED的消息來進入CaptivePortalState狀态,該狀态中将會監聽ACTION_CAPTIVE_PORTAL_LOGGED_IN消息,并可直接由該消息指定進入ValidatedState或者OfflineState模式。
四、NetworkMonitor對網絡評分的影響
下面我們從一次正常網絡接入過程來看NetworkMonitor對評分的影響。
從NetworkMonitor初始化過程我們知道,該狀态機的預設狀态是DefaultState,并且從NetworkAgent向ConnectivityService注冊過程可以看到,在NetworkAgent注冊過程中最後将會通過updateNetworkInfo方法更新目前網絡狀态為CONNECTED:
@ConnectivityService.java
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
if (state == NetworkInfo.State.CONNECTED && !networkAgent.created) {
networkAgent.created = true;
updateLinkProperties(networkAgent, null);
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
// Consider network even though it is not yet validated.
rematchNetworkAndRequests(networkAgent, false);
} else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) {
}
}
這裡我們發現,更新狀态的同時,向NetworkMonitor發送了CMD_NETWORK_CONNECTED的消息,根據NetworkMonitor的狀态機原理,此消息将在NetworkMonitor中的DefaultState狀态機中來處理:
@NetworkMonitor.java
private class DefaultState extends State {
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_NETWORK_CONNECTED:
transitionTo(mEvaluatingState);
return HANDLED;
}
}
}
從這裡我們看到,此時的NetworkMonitor将會進入EvaluatingState的狀态中,然後我們來看該狀态的初始化過程:
private class EvaluatingState extends State {
private int mRetries;
public void enter() {
mRetries = 0;
sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
if (mUidResponsibleForReeval != INVALID_UID) {
TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
mUidResponsibleForReeval = INVALID_UID;
}
}
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_REEVALUATE:
if (message.arg1 != mReevaluateToken)
return HANDLED;
if (mNetworkAgentInfo.isVPN()) {
transitionTo(mValidatedState);
return HANDLED;
}
//Ping網絡
int httpResponseCode = isCaptivePortal();
//根據網絡回應來進入不同狀态
if (httpResponseCode == 204) {
transitionTo(mValidatedState);
} else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
transitionTo(mUserPromptedState);
} else if (++mRetries > MAX_RETRIES) {
transitionTo(mOfflineState);
} else if (mReevaluateDelayMs >= 0) {
Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
sendMessageDelayed(msg, mReevaluateDelayMs);
}
return HANDLED;
case CMD_FORCE_REEVALUATION:
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
從這個狀态機的初始化過程我們發現,進入該狀态時,将會發送一個CMD_REEVALUATE的消息,然後在該狀态機内部收到該消息時,就會通過isCaptivePortal方法來Ping網絡:
private int isCaptivePortal() {
if (!mIsCaptivePortalCheckEnabled) return 204;
HttpURLConnection urlConnection = null;
int httpResponseCode = 599;
try {
//準備連接配接的uri
URL url = new URL("http", mServer, "/generate_204");
urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
//發起連接配接
long requestTimestamp = SystemClock.elapsedRealtime();
urlConnection.getInputStream();
long responseTimestamp = SystemClock.elapsedRealtime();
//擷取伺服器回應
httpResponseCode = urlConnection.getResponseCode();
//拿到回應
if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
httpResponseCode = 204;
}
sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204, requestTimestamp, responseTimestamp);
} catch (IOException e) {
if (httpResponseCode == 599) {
}
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return httpResponseCode;
}
從這裡我們看到,其向一個url網址進行Ping操作,而這個url的具體内容為:
URL url = new URL("http", mServer, "/generate_204");
而這裡的mServer的預設值是從NetworkMonitor初始化時擷取的:
public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
mServer = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_SERVER);
if (mServer == null) mServer = DEFAULT_SERVER;
}
而DEFAULT_SERVER内容為:
private static final String DEFAULT_SERVER = "clients3.google.com";
這就說明,這個url的具體位址為: “http://clients3.google.com/generate_204”,然後當isCaptivePortal結束之後,EvaluatingState就會對結果進行分類:
如果httpResponseCode=204,就說明目前網絡是可用的,将會進入ValidatedState狀态。
如果httpResponseCode >= 200同時httpResponseCode <= 399,說明目前網絡需要進行重定向,當認證完成後才可以有效通路網絡。
如果沒有任何回應,将會通過sendMessageDelayed方式延時mReevaluateDelayMs(預設為5秒)之後再次發送CMD_REEVALUATE消息,觸發循環Ping過程,直到嘗試次數超過MAX_RETRIES(10次)之後,進入OfflineState狀态,表示目前網絡無法使用。
那麼當目前網絡可用或者不可用時,對NetworkAgent評分有什麼影響呢?
我們來看當該Ping通過後,進入ValidatedState狀态時将會觸發什麼動作:
private class ValidatedState extends State {
@Override
public void enter() {
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo));
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_NETWORK_CONNECTED:
transitionTo(mValidatedState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
這個狀态機比較簡單,在進入該狀态機時,隻是向ConnectivityService發送了一條EVENT_NETWORK_TESTED的消息,并攜帶了目前網絡的NetworkAgentInfo對象和測試通過(NETWORK_TEST_RESULT_VALID)的參數。
然後我們來看,當Ping不通過時,發生重定向時進入的UserPromptedState狀态機會是怎樣的處理:
private class UserPromptedState extends State {
private static final String ACTION_SIGN_IN_REQUESTED = "android.net.netmon.sign_in_requested";
private CustomIntentReceiver mUserRespondedBroadcastReceiver;
@Override
public void enter() {
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
mUserRespondedBroadcastReceiver = new CustomIntentReceiver(ACTION_SIGN_IN_REQUESTED, ++mUserPromptedToken, CMD_USER_WANTS_SIGN_IN);
Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
mNetworkAgentInfo.network.netId,
mUserRespondedBroadcastReceiver.getPendingIntent());
mConnectivityServiceHandler.sendMessage(message);
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_USER_WANTS_SIGN_IN:
if (message.arg1 != mUserPromptedToken)
return HANDLED;
transitionTo(mCaptivePortalState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
這個狀态機與ValidatedState類似,也是在進入時向ConnectivityService發送了同樣的EVENT_NETWORK_TESTED消息,并攜帶目前的NetworkAgentInfo對象,不同的是,該消息中攜帶了一個測試不通過的參數(NETWORK_TEST_RESULT_INVALID)。
下面我們來看ConnectivityService接收到該消息後的處理:
@ConnectivityService.java
private class NetworkStateTrackerHandler extends Handler {
public void handleMessage(Message msg) {
NetworkInfo info;
switch (msg.what) {
case NetworkMonitor.EVENT_NETWORK_TESTED: {
NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
if (isLiveNetworkAgent(nai, "EVENT_NETWORK_VALIDATED")) {
boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
if (valid) {
//驗證通過
final boolean previouslyValidated = nai.validated;
final int previousScore = nai.getCurrentScore();
//标記NetworkAgentInfo的validated狀态為true
nai.validated = true;
rematchNetworkAndRequests(nai, !previouslyValidated);
if (nai.getCurrentScore() != previousScore) {
//将最新分數更新到其他NetworkFactory
sendUpdatedScoreToFactories(nai);
}
}
updateInetCondition(nai, valid);
//通知NetworkAgent
nai.asyncChannel.sendMessage( android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS, (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), 0, null);
}
break;
}
}
}
}
在這裡我們看到, 通過與不通過的差距就在于是否将NetworkAgentInfo的validated标志位設定為true。
那麼為什麼這個标志位會對評分産生影響呢?我們來看NetworkAgentInfo中關于該标志位的使用:
當NetworkAgent注冊到ConnectivityService時,将會建立該NetworkAgent的NetworkAgentInfo,此時該标志位的預設狀态是false:
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc) {
this.messenger = messenger;
asyncChannel = ac;
network = null;
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
currentScore = score;
networkMonitor = new NetworkMonitor(context, handler, this);
networkMisc = misc;
created = false;
//預設值為false
validated = false;
}
這也很容易了解,因為此時說明網絡連接配接建立完成,但是對于ConnectivityService來說,并不能确定該網絡完全可用,是以預設狀态下網絡都是未經檢驗的。
而當Ping通過之後,說明該網絡通過了可用性檢測,那麼此時在對該标志位指派也是合理的。
而該标志位直接影響到NetworkAgentInfo中該網絡的分值計算:
public int getCurrentScore() {
//預設是NetworkAgent帶過來的
int score = currentScore;
//如果沒有通過Ping的檢測,那麼該網絡就要被扣掉UNVALIDATED_SCORE_PENALTY=40分
if (!validated) score -= UNVALIDATED_SCORE_PENALTY;
//有效性保護
if (score < 0) score = 0;
//如果該網絡是使用者特意指定的,分值就是EXPLICITLY_SELECTED_NETWORK_SCORE=100
if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
return score;
}
從這裡我們終于找到了Ping網絡對評分的限制:無論是通過rematchNetworkAndRequests重新比對最佳NetworkFactory,還是通過sendUpdatedScoreToFactories廣播最新網絡的分值,都是需要從NetworkAgentInfo中通過getCurrentScore擷取最新分值的, 而如果目前網絡沒有經過有效性檢測,那麼對其他所有的NetworkFactory來說,目前的網絡分值都是被扣掉40分之後的分值。
這就完全可能讓原本分值高的網絡由于沒有通過有效性檢測,而低于其他網絡優先級。
比如WIFI預設60分,而資料連接配接預設50分,但是如果WIFI沒有通過有效性檢測,那麼對資料連接配接來說,WIFI的分數隻有60-40=20分,由此就會引發WIFI連接配接時,資料也在連接配接的異常。
這就是網絡可用性對評分的影響。