三、Android 網絡評分機制

在前兩節簡單介紹了連接管理的大致框架,數據鏈接的準備工作,包括APN的初始化與默認APN使能,DcTracker的構造,包括各種事件的註冊等工作。但是數據鏈接的打開不止是隻有用戶主動去打開,Android可以提供數據業務的對象主要有,移動數據網絡、WIFI、藍牙、網線等,這些連接本身都可以獨立使用,但是對於用戶來說,每一時刻又最多隻能使用一種方式接入網絡,那麼當這些功能同時打開時,比如即使用戶打開了移動數據連接,但是又打開了wifi,那麼只要wifi暢通,移動數據鏈接是不會用於上網的,那究竟如何選擇最佳的接入環境呢?這就需要提供一個能夠動態管理他們的打開與斷開的功能,Android專門設計了一套管理方法來實現上面的這種機制,包括ConnectivityManager、ConnectivityService、NetworkAgent等對象之間的關係以及消息流走向,本節在這些知識的基礎上介紹連接管理的核心機制,即連接管理中的評分機制,其中ConnectivityService是管理員身份,沒種網絡都會去向它註冊,網絡的使用權全靠它來分配。

連接管理通過一個評分機制來實現不同接入方式的選擇。具體來說就是,每一種上網方式在初始化時,都向ConnectivityService標明自己網絡的分值(比如數據連接50,WIFI60,藍牙69,網線70),當有更高分數的網絡就緒時,就將當前分值低的連接斷開。而噹噹前網絡被斷開時,就尋找當前就緒的其他網絡連接,選取分值高的進行接入。並且,每一個網絡接入時,都會進行有效性檢測,如果檢測不通過,將會被扣掉一定分數,此時該網絡的優先級也會相應的下降。下面我們利用三個部分來分析評分機制的原理:
1、NetworkFactory
2、NetworkAgent
3、NetworkMonitor
其中NetworkFactory是每一種網絡持有一個,比如WIFI和Telephony會分別註冊一個,但是NetworkAgent和NetworkMonitor是一種數據類型就會有一個,比如數據連接總的APN TYPE有8種,其中任意一種鏈接上之後都會各註冊一個。

1、NetworkFactory

NetworkFactory直譯就是網絡工廠,開機之後每種網絡都必須註冊自己的NetworkFactory,NetworkFactory的作用是用來創建NetworkAgent,同時作爲ConnectivityService與網絡之間的通訊樞紐

private DctController(PhoneProxy[] phones) {
    for (int i = 0; i < mPhoneNum; ++i) {
        // Register for radio state change
        PhoneBase phoneBase = (PhoneBase)mPhones[i].getActivePhone();
        updatePhoneBaseForIndex(i, phoneBase);
    }
}    

private void updatePhoneBaseForIndex(int index, PhoneBase phoneBase) {
    phoneBase.getServiceStateTracker().registerForDataConnectionAttached(mRspHandler,
               EVENT_DATA_ATTACHED + index, null);
    phoneBase.getServiceStateTracker().registerForDataConnectionDetached(mRspHandler,
               EVENT_DATA_DETACHED + index, null);

    mNetworkFilter[index] = new NetworkCapabilities();
    mNetworkFilter[index].addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
    mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);

    mNetworkFactory[index] = new TelephonyNetworkFactory(this.getLooper(),
            mPhones[index].getContext(), "TelephonyNetworkFactory", phoneBase,
            mNetworkFilter[index]);
    mNetworkFactory[index].setScoreFilter(50);
    mNetworkFactoryMessenger[index] = new Messenger(mNetworkFactory[index]);
    cm.registerNetworkFactory(mNetworkFactoryMessenger[index], "Telephony");
}

可以看出來一個NetworkFactory 支持多種網絡類型(NetworkCapabilities),網絡類型與APN的TYPE相對應。
以移動數據網絡爲例,TelephonyNetworkFactory 將會繼承NetworkFactory ,並重寫其中兩個重要的方法,needNetworkFor和releaseNetworkFor,這兩個方法就是ConnectivityService與移動網絡之間橋樑,分別負責請求當前網絡和斷開當前網絡。

private class TelephonyNetworkFactory extends NetworkFactory {
    protected void needNetworkFor(NetworkRequest networkRequest, int score) {
        // figure out the apn type and enable it
        if (!SubscriptionManager.isUsableSubIdValue(mPhone.getSubId())) {
            mPendingReq.put(networkRequest.requestId, networkRequest);
            return;
        }
        if (getRequestPhoneId(networkRequest) == mPhone.getPhoneId()) { 
            DcTrackerBase dcTracker =((PhoneBase)mPhone).mDcTracker;
            String apn = apnForNetworkRequest(networkRequest);
            if (dcTracker.isApnSupported(apn)) {
                requestNetwork(networkRequest, dcTracker.getApnPriority(apn));
            }
        } else {
            mPendingReq.put(networkRequest.requestId, networkRequest);
        }
    }  
    protected void releaseNetworkFor(NetworkRequest networkRequest) {
        if (!SubscriptionManager.isUsableSubIdValue(mPhone.getSubId())) {
            mPendingReq.remove(networkRequest.requestId);
            return;
        }
        if (getRequestPhoneId(networkRequest) == mPhone.getPhoneId()) { 
            DcTrackerBase dcTracker =((PhoneBase)mPhone).mDcTracker;
            String apn = apnForNetworkRequest(networkRequest);
            if (dcTracker.isApnSupported(apn)) {
                releaseNetwork(networkRequest);
            }
        }
    }     

再看NetworkFactory 的註冊cm.registerNetworkFactory(mNetworkFactoryMessenger[index], “Telephony”);其中mNetworkFactoryMessenger是一個包裝了mNetworkFactory的Messenger對象,這個主要是建立AsyncChannel通道時用。

@ConnectivityService.java
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));
}

handleRegisterNetworkFactory處理EVENT_REGISTER_NETWORK_FACTORY消息

private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
    mNetworkFactoryInfos.put(nfi.messenger, nfi);
    nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}

在這裏,ConnectivityService做了兩個事情:
1、將新註冊的NetworkFactoryInfo 保存到mNetworkFactoryInfos中;
2、利用剛纔創建的AsyncChannel向NetworkAgent發起單向連接請求;
nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);即利用傳入的Messenger對象建立起ConnectivityService與NetworkFactory的通訊通道,ConnectivityService後續的消息都將通過這個asyncChannel傳入到數據網絡中的NetworkFactory。
當asyncChannel通道建立成功後ConnectivityService會收到CMD_CHANNEL_HALF_CONNECTED消息。

@Override
public void handleMessage(Message msg) {
    NetworkInfo info;
    switch (msg.what) {
        case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
            handleAsyncChannelHalfConnect(msg);
            break;
        }
}
private void handleAsyncChannelHalfConnect(Message msg) {
    AsyncChannel ac = (AsyncChannel) msg.obj;
    if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {  //此時是鏈接的是NetworkFactory,走這個path
        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
            // A network factory has connected.  Send it all current NetworkRequests.
            for (NetworkRequestInfo nri : mNetworkRequests.values()) {
                if (nri.isRequest == false) continue;
                NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
                ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
            }
        } else {
            mNetworkFactoryInfos.remove(msg.obj);
        }
    } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {

    }
}

此時是鏈接的是NetworkFactory,走這個path,mNetworkFactoryInfos是在handleRegisterNetworkFactory時保存的。
在這裏,ConnectivityService通過AsyncChannel通道向當前的NetworkFactory發起CMD_REQUEST_NETWORK的請求,需要注意的是,該請求所附帶的第二個參數選擇,由於當前處於初始化階段,因此當前的mNetworkForRequestId中爲空,也就是說此時傳遞的第二個參數必然爲0。
我們接下來看NetworkFactory收到該請求時的處理:

@NetworkFactory.java
public void handleMessage(Message msg) {
    switch (msg.what) {
        case CMD_REQUEST_NETWORK: {
            handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
            break;
        }
    }
}
private void handleAddRequest(NetworkRequest request, int score) {
    NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
    if (n == null) {
        if (DBG) log("got request " + request + " with score " + score);
        n = new NetworkRequestInfo(request, score);
        mNetworkRequests.put(n.request.requestId, n);
    } else {
        n.score = score;
    }
    evalRequest(n);
}

接下來評估網絡評分,是需要鏈接網絡還是斷開網路

private void evalRequest(NetworkRequestInfo n) {
    if (n.requested == false && n.score < mScore &&
            n.request.networkCapabilities.satisfiedByNetworkCapabilities(
            mCapabilityFilter) && acceptRequest(n.request, n.score)) {
        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)) {
        releaseNetworkFor(n.request);
        n.requested = false;
    } 
}

該邏輯就是整個網絡評價系統最關鍵的地方,如果NetworkRequestInfo沒有被requested過,並且其分值(n.score)小於當前NetworkFactory自己的分值(mScore),那麼就說明,當前NetworkFactory所處的網絡優先級高於其他網絡的優先級,就會觸發當前NetworkFactory所在網絡的needNetworkFor()流程,也就是連接建立流程,並將標記NetworkRequestInfo.requested=true。
當NetworkRequestInfo被requested過(也就是當前網絡被needNetworkFor過),此時如果再次收到請求,並且攜帶的新score大於當前NetworkFactory所處網絡的mScore,那麼就說明當前NetworkFactory所在網絡優先級已經不是最高,需要將其releaseNetworkFor掉,並標記NetworkRequestInfo.requested=false。

evalRequest中調用TelephonyNetworkFactory 重寫的needNetworkFor或者releaseNetworkFor,分別是鏈接網絡和斷開網絡,後續的流程如下圖(請求網絡的情況)
這裏寫圖片描述
在此數據鏈接的NetworkFactory算是創建完畢,並將自己註冊到ConnectivityService中。

2、NetworkAgent

前面提到NetworkFactory是在系統初始化時就被創建,而NetworkAgent是在真正接入網絡時纔會創建,NetworkAgent的創建在DataConnection狀態機裏的DcActiveState狀態時。

private class DcActiveState extends State {
    @Override public void enter() {
        // If we were retrying there maybe more than one, otherwise they'll only be one.
        notifyAllOfConnected(Phone.REASON_CONNECTED);

        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED,
                mNetworkInfo.getReason(), null);
        mNetworkInfo.setExtraInfo(mApnSetting.apn);
        updateTcpBufferSizes(mRilRat);

        final NetworkMisc misc = new NetworkMisc();
        misc.subscriberId = mPhone.getSubscriberId();
        mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
                "DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
                50, misc);
    }
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
        NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
    super(looper);

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

當網絡鏈接完成之後,就會新建一個DcNetworkAgent,接着分析NetworkAgent的構造,和NetworkFactory類似,也是將自己註冊到ConnectivityService中去,繼續看registerNetworkAgent

public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
        LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
        int currentScore, NetworkMisc networkMisc) {

    NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
        new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
        new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
        new NetworkMisc(networkMisc), mDefaultRequest);
    synchronized (this) {
        nai.networkMonitor.systemReady = mSystemReady;
    }
    mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
}

private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
    mNetworkAgentInfos.put(na.messenger, na);
    assignNextNetId(na);
    na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
    NetworkInfo networkInfo = na.networkInfo;
    na.networkInfo = null;
    updateNetworkInfo(na, networkInfo);
}

在這裏,ConnectivityService做了三個事情:
1、將新註冊的NetworkAgentInfo保存到mNetworkAgentInfos中;
2、利用剛纔創建的AsyncChannel向NetworkAgent發起單向連接請求;
3、更新最新的NetworkAgentInfo狀態;

@Override
public void handleMessage(Message msg) {
    NetworkInfo info;
    switch (msg.what) {
        case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
            handleAsyncChannelHalfConnect(msg);
            break;
        }
}

以上流程和NetworkFactory註冊時幾乎一模一樣的模式

private void handleAsyncChannelHalfConnect(Message msg) {
    AsyncChannel ac = (AsyncChannel) msg.obj;
    if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {  

    }  else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {  //此時是鏈接的是NetworkAgent,走這個path
        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
            // A network agent has requested a connection.  Establish the connection.
            mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
                    sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
        } 
    }
}

唯一的區別是在handleAsyncChannelHalfConnect中這裏,當ConnectivityService與NetworkAgent之間單向通道建立完成後,又發起了雙向通道的請求,此時在NetworkAgent端,將會收到CMD_CHANNEL_FULL_CONNECTION的消息,建立雙向通道的目的是,有時候網絡也需要通過AsyncChannel向ConnectivityService發送消息。至此,NetworkAgent的初始化完畢。
現在的問題是NetworkAgent如何影響網絡鏈接的?
NetworkAgent提供了兩種方法更新評分管理:
1、sendNetworkScore

public void sendNetworkScore(int score) {
    queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
}

2、sendNetworkInfo

public void sendNetworkInfo(NetworkInfo networkInfo) {
    queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}

先來分析第二種情況,比如移動數據網絡的斷開時就會調用此方法:

@DataConnection.java
private class DcActiveState extends State {
    public void exit() {
        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                mNetworkInfo.getReason(), mNetworkInfo.getExtraInfo());
        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
        mNetworkAgent = null;
    }
}

接着就會進入ConnectivityService

@Override
public void handleMessage(Message msg) {
    NetworkInfo info;
        case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
            NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
            info = (NetworkInfo) msg.obj;
            updateNetworkInfo(nai, info);
            break;
        }
}
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
    if (state == NetworkInfo.State.CONNECTED && !networkAgent.created) {

    } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) {
        networkAgent.asyncChannel.disconnect();        
    }   

由於是斷開數據網絡,因此這裏是斷開AsyncChannel,從而進入

AsyncChannel.CMD_CHANNEL_DISCONNECTED
        @Override
        public void handleMessage(Message msg) {
            NetworkInfo info;
            switch (msg.what) {
                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                    handleAsyncChannelDisconnected(msg);
                    break;
                }
        }
private void handleAsyncChannelDisconnected(Message msg) {  
    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);  
    if (nai != null) {  
        //刪掉當前NetworkAgent對象  
        mNetworkAgentInfos.remove(msg.replyTo);  
        final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();  
        for (int i = 0; i < nai.networkRequests.size(); i++) {  
            NetworkRequest request = nai.networkRequests.valueAt(i);  
            NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);  
            if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {  
                mNetworkForRequestId.remove(request.requestId);  
                //將0分更新到各個NetworkFactory中  
                sendUpdatedScoreToFactories(request, 0);  
            }  
        }  
    }  
}  
private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
    for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
        nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0,networkRequest);
    }
}

在這裏,由於當前連接是斷開狀態,因此其分值必然爲0,這樣就把他的0分值通知到各個NetworkFactory中,由NetworkFactory判斷是否需要開啓自己的網絡,通知方法同樣是CMD_REQUEST_NETWORK,也就是說,無論是直接更新NetworkAgent中的分數,還是更新NetworkAgent的狀態,最終都會觸發NetworkFactory中的評分機制。

3、NetworkMonitor

NetworkMonitor的構造是在註冊NetworkAgent,構造NetworkAgentInfo是創建的,其實質ping網絡是在updateNetworkInfo中,細節不分析,但是NetworkMonitor對網絡可用性的評分是有影響的,即當網絡鏈接上之後,會去ping當前網絡是否可用,如果不可用則會影響getCurrentScore獲取的分數值,getCurrentScore是每次網絡評分獲取的分數的必經之路:

private int getCurrentScore(boolean pretendValidated) {
    int score = currentScore;

    if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;
    if (score < 0) score = 0;

    if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;

    return score;
}

當一個網絡連接建立時,系統將用該連接Ping一個Google的網站來判斷該連接是否真的可以上網,如果不可以,那麼就會扣掉該網絡40分,從而可能導致該網絡的評分低於其他網絡評分
如果是用戶指定了網絡那麼分數直接等於EXPLICITLY_SELECTED_NETWORK_SCORE(100分)
至此網絡評分就分析完畢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章