天天看点

网络连接评分机制之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连接时,数据也在连接的异常。

        这就是网络可用性对评分的影响。