天天看點

網絡連接配接評分機制之NetworkMonitor(原)一、NetworkMonitor來源二、NetworkMonitor作用與初始化流程三、NetworkMonitor各個狀态機作用四、NetworkMonitor對網絡評分的影響

        本節介紹一個比較特殊的流程,就是網絡可用性對評分的影響。

        該影響主要展現在,當一個網絡連接配接建立時,系統将用該連接配接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連接配接時,資料也在連接配接的異常。

        這就是網絡可用性對評分的影響。