1. 程式人生 > >Android7.0 資料業務中的短連線

Android7.0 資料業務中的短連線

資料業務中的短連線,是一種為了滿足業務需求,臨時建立起來的連線。當業務完成通訊需求後,這種資料連線會被框架釋放掉。與之相對,長連線一旦撥號成功就會一直存在下去,除非使用者主動關閉或者終端受到網路等因素的影響導致連線不可用。
一種比較常見的例子,就是傳送彩信時,終端將建立短連線;當彩信傳送結束時,短連線將被釋放掉。
在這篇部落格中,我們就從彩信入手,看看Android中是如何建立和釋放短連線的。

1 MmsService
Android中彩信相關的應用為MmsService,我們看看它AndroidManifest.xml中的部分片段:

<application android:label
="MmsService" android:process="com.android.phone" android:usesCleartextTraffic="true">
<service android:name=".MmsService" android:enabled="true" android:exported="true"/> </application>

容易看出MmsService是執行在Phone程序中的。在這篇部落格中,我們不深入研究彩信服務的啟動和收發彩信的過程,主要看看彩信如何建立和釋放短連線。

在MmsService.java中,每一次傳送彩信均會形成一個MmsRequest(抽象類,實現類為SendRequest和DownloadRequest),並將其加入到執行佇列中:

private void addToRunningRequestQueueSynchronized(final MmsRequest request) {
    ...........
    final int queue = request.getQueueType();
    //判讀queue值的有效性
    ............
    mRunningRequestCount++;
    mCurrentSubId = request.getSubId();

    mRunningRequestExecutors[queue].execute(new
Runnable() { @Override public void run() { try { //MmsRequest執行 request.execute(MmsService.this, getNetworkManager(request.getSubId())); } finally { synchronized (MmsService.this) { mRunningRequestCount--; if (mRunningRequestCount <= 0) { //將位於pending佇列中的請求,加入到running佇列中 movePendingSimRequestsToRunningSynchronized(); } } } } } }

在上面對程式碼中,利用getNetworkManager建立了MmsRequest專屬的MmsNetworkManger:

//subId對應於傳送彩信的卡
private MmsNetworkManager getNetworkManager(int subId) {
    synchronized (mNetworkManagerCache) {
        MmsNetworkManager manager = mNetworkManagerCache.get(subId);
        if (manager == null) {
            manager = new MmsNetworkManager(this, subId);
            mNetworkManagerCache.put(subId, manager);
        }
        return manager;
    }
}

先看看MmsNetworkManager的建構函式:

 public MmsNetworkManager(Context context, int subId) {
     ............
     //構造出申請網路的request
     //注意Capability指定為MMS,同時NetworkSpecifier指定為對應的sub id
     mNetworkRequest = new NetworkRequest.Builder()
             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
             .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
             .setNetworkSpecifier(Integer.toString(mSubId))
             .build();
 }

接下來,看看MmsRequest的execute方法:

public void execute(Context context, MmsNetworkManager networkManager) {
    ..........
    //檢查是否做好準備工作
    if (!ensureMmsConfigLoaded()) {
        ........
    } else if (!prepareForHttpRequest()){
        ........
    } else {
        long retryDelaySecs = 2;

        for (int i = 0; i < RETRY_TIMES; i++) {
            try {
                //利用MmsNetworkManager建立短連線
                networkManager.acquireNetwork(requestId);
                try {
                    //進行實際的傳送或接收
                    ............
                } finally {
                    //利用MmsNetworkManager釋放段連線
                    networkManager.releaseNetwork(requestId);
                }
            } .........//進行捕獲異常等操作
        }
        //處理髮送結果
        processResult(context, result, response, httpStatusCode);
    }
}

2 建立短連線
MmsNetworkManager中的acquireNetwork負責申請網路:

public void acquireNetwork(final String requestId) throws MmsNetworkException {
    synchronized (this) {
        mMmsRequestCount += 1;

        //若之前已經申請過網路,則不重新申請
        if (mNetwork != null) {
            return;
        }

        if (mNetworkCallback == null) {
            //申請網路
             startNewNetworkRequestLocked();
        }

        //定義申請網路所應耗費的時間
        final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS;
        long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS;
        while (waitTime > 0) {
            try {
                //等待;申請網路後,ConnectivityService將呼叫MmsNetworkManager的回撥函式,在回撥函式進行通知
                this.wait(waitTime);
            } catch (InterruptedException e) {
                ............
            }

            if (mNetwork != null) {
                //成功獲取到網路
                return;
            }
            waitTime = shouldEnd - SystemClock.elapsedRealtime();
        }

        //獲取網路失敗,釋放請求
        releaseRequestLocked(mNetworkCallback);
        throw new MmsNetworkException("Acquiring network timed out");
    }
}

順著流程,看看MmsNetworkManager中的startNewNetworkRequestLocked:

private void startNewNetworkRequestLocked() {
    final ConnectivityManager connectivityManager = getConnectivityManager();
    //定義回撥函式
    mNetworkCallback = new NetworkRequestCallback();

    //利用ConnectivityManager向ConnectivityService傳送請求,並註冊回撥函式
    connectivityManager.requestNetwork(
            mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
}

在進入connectivityManager前,先看看MmsNetworkManager中的回撥函式:

private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback {
    @Override
    public void onAvailable(Network network) {
        super.onAvailable(network);

        synchronized (MmsNetworkManager.this) {
            //網路可用時,儲存並通知
            //於是acquireNetwork函式,可以返回結果
            mNetwork = network;
            MmsNetworkManager.this.notifyAll();
        }
    }
    //網路斷開和不可用時的回撥函式
    ............
}

現在我們可以看看connectivityManager的requestNetwork函式:

public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
        int timeoutMs) {
    requestNetwork(request, networkCallback, timeoutMs,
            //根據網路能力返回type;在之前的版本中會返回TYPE_MOBILE_MMS,android7.0中返回TYPE_NONE
            inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
}

public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
        int timeoutMs, int legacyType) {
    sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs, REQUEST,
            legacyType);
}

private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
        NetworkCallback networkCallback, int timeoutSec, int action,
        int legacyType) {
    ...................
    try {
        ..............
        synchronized(sNetworkCallback) {
            if (action == LISTEN) {
                //這種型別的Request僅用來監聽網路的變化,無法申請實際的網路
                .............
            } else {
                //呼叫ConnectivityService的requestNetwork函式
                networkCallback.networkRequest = mService.requestNetwork(need,
                        new Messenger(sCallbackHandler), timeoutSec, new Binder(), legacyType);
            }
        }
    } catch(RemoteException e) {
        ............
    }
    ..............
}

從上面的程式碼,可以看到申請網路還是需要依靠ConnectivityService:

@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
        Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
    //檢查引數有效性
    ............

    //利用引數構造networkRequest及NetworkRequestInfo
    NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
            nextNetworkRequestId());
    NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, type);

    mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
    ....................
}

ConnectivityService將呼叫handleRegisterNetworkRequestWithIntent處理EVENT_REGISTER_NETWORK_REQUEST:

private void handleRegisterNetworkRequestWithIntent(Message msg) {
    //檢查msg攜帶Request的有效性
    ............
    handleRegisterNetworkRequest(nri);
}

private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
    mNetworkRequests.put(nri.request, nri);
    if (!nri.isRequest()) {
        //非申請網路的request
        ................
    }
    //重新匹配所有的NetworkAgent和NetworkRequest
    rematchAllNetworksAndRequests(null, 0);

    //預設建立的長連線對應的NetworkAgent,其capabilities不包含MMS,可以看看DataConnection中的makeNetworkCapabilities函式
    //因此無論資料業務開關是否開啟,此時mNetworkForRequestId.get(nri.request.requestId)返回值均為null
    if (nri.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
        //傳送request給所有的NetworkFactory,分數為0
         sendUpdatedScoreToFactories(nri.request, 0);
    }
}

與之前介紹資料撥號準備工作的部落格中描述的一樣,請求MMS網路的NetworkRequest將同時傳送給TelephonyNetworkFactory和PhoneSwitcher中建立的NetworkFactory。
這裡需要注意的是,TelephonyNetworkFactory申明自己的網路能力時,指定匹配對應卡的subId;
PhoneSwitcher中建立的NetworkFactory申明自己的網路能力時,指定匹配所有的subId。
於是,不匹配MMS NetworkRequest對應subId的TelephonyNetworkFactory,將無法處理該NetworkRequest。
此外,當TelephonyNetworkFactory處於啟用態時,才能處理NetworkRequest。

綜上所述,我們可以得出結論:
當具有資料能力的Phone傳送彩信時,對應的TelephonyNetworkFactory可以直接處理;
當不具有資料能力的Phone傳送彩信時,沒有TelephonyNetworkFactory可以處理MMS Request;此時,必須先通過PhoneSwitcher切換Phone的資料能力,然後再進行處理,這個過程也被成為DDS。

我們來看看DDS的過程。在PhoneSwitcher中,負責處理NetworkRequest的函式為onRequestNetwork:

private void onRequestNetwork(NetworkRequest networkRequest) {
    //之前分析過,將NetworkRequest封裝成DcRequest時,按照xml中的配置,定義了優先順序
    final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
    if (mPrioritizedDcRequests.contains(dcRequest) == false) {
        mPrioritizedDcRequests.add(dcRequest);
        //MMS request的優先順序高於default,於是會排在最前面
        Collections.sort(mPrioritizedDcRequests);
        onEvaluate(REQUESTS_CHANGED, "netRequest");
    }
}

private void onEvaluate(boolean requestsChanged, String reason) {
    ..............
    if (diffDetected) {
        List<Integer> newActivePhones = new ArrayList<Integer>();
            int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
            if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
            if (newActivePhones.contains(phoneIdForRequest)) continue;
            //MMS NetworkRequest的phone id加入到newActivePhones
            newActivePhones.add(phoneIdForRequest);
            if (newActivePhones.size() >= mMaxActivePhones) break;
        }
        .............
        for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
            if (newActivePhones.contains(phoneId) == false) {
                //登出不匹配MMS NetworkRequest subId的phone的資料能力
                //通過RIL下發命令,完成後通知觀察者
                deactivate(phoneId);
            }
        }

        for (int phoneId : newActivePhones) {
            //賦予MMS Request subId對應phone的資料能力
            //通過RIL下發命令,完成後通知觀察者,TelephonyNetworkFactory
            activate(phoneId);
        }
    }
}

不論TelephonyNetworkFactory可以直接處理MMS NetworkRequest,還是DDS後才能進行處理,最終TelephonyNetworkFactory將呼叫DcTracker的requestNetwork函式。
之後,框架就會啟用彩信對應的APN,並用彩信APN進行撥號。這部分流程與之前部落格描述的,資料撥號的準備工作及資料長連線撥號基本一致,不再贅述。

3 釋放短連線
MmsNetworkManager中的releaseRequestLocked負責釋放短連線:

private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
    if (callback != null) {
        final ConnectivityManager connectivityManager = getConnectivityManager();
        try {
            connectivityManager.unregisterNetworkCallback(callback);
        } catch (IllegalArgumentException e) {
            ............
        }
    }
    ............
}

進入到ConnectivityManager的unregisterNetworkCallback函式:

public void unregisterNetworkCallback(NetworkCallback networkCallback) {
    .............
    try {
        mService.releaseNetworkRequest(networkCallback.networkRequest);
    } catch(RemoteException e) {
        ............
    }
    ............
}

隨著流程進入到ConnectivityService:

@Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
    mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
            0, networkRequest));
}

private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
        int callingUid) {
    NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
    if (nri != null) {
        handleReleaseNetworkRequest(nri.request, callingUid);
    }
}

private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
    NetworkRequestInfo nri = mNetworkRequests.get(request);
    if (nri != null) {
        .............
        if (nri.isRequest()) {
            ...............
            for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                //釋放每個NetworkAgent中對networkRequest的記錄
                if (nai.networkRequests.get(nri.request.requestId) != null) {
                    nai.networkRequests.remove(nri.request.requestId);
                    ...........
                    //該NetworkAgent不再是任何NetworkRequest的最優匹配物件
                    if (unneeded(nai)) {
                        //登出掉該NetworkAgent,呼叫AsyncChannel的disconnect函式,與之前部落格所述的長連線去撥號流程一致
                        //MMS建立的NetworkAgent將在此處被登出
                        teardownUnneededNetwork(nai);
                    } else {
                        ..............
                    }
                }
            }
            //移除ConnectivityService中NetworkAgentInfo的記錄資訊
            NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
            if (nai != null) {
                mNetworkForRequestId.remove(nri.request.requestId);
            }
            ............
            for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
                //移除NetworkFactory中關於該NetworkRequest的記錄
                //該訊息將通知給PhoneSwitcher中建立的NetworkFactory和TelephonyNetworkFactory
                nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
                    nri.request);
            }
        } else {
            .........
        }
        .......
    }
}

當PhoneSwitcher中建立的NetworkFactory收到CMD_CANCEL_REQUEST訊息後,最終將呼叫onReleaseNetwork進行處理:

private void onReleaseNetwork(NetworkRequest networkRequest) {
    final DcRequest dcRequest = new DcRequest(networkRequest, mContext);

    //移除MMS對應的DcRequest
    if (mPrioritizedDcRequests.remove(dcRequest)) {

        //重新評估所有的DcRequest
        //此時,由於高優先順序的MMS DcRequest被移除了,於是Default data request成為了最優先的request
        //如果之前為了傳送彩信進行過DDS,那麼此時將重新進行DDS,將資料能力切換到原來的default subId對應的phone
        onEvaluate(REQUESTS_CHANGED, "netReleased");
    }
}

當TelephonyNetworkFactory收到CMD_CANCEL_REQUEST訊息後,同樣會呼叫自己的onReleaseNetworkFor函式進行處理:

private void onReleaseNetworkFor(Message msg) {
    //移除對NetworkRequest的記錄
    .............
    if (mIsActive && isApplicable) {
        String s = "onReleaseNetworkFor";
        localLog.log(s);
        log(s + " " + networkRequest);
        //呼叫DcTracker的releaseNetwork函式,將去啟用MMS對應APN,同時移除MMS對應的dataConnection
        mDcTracker.releaseNetwork(networkRequest, localLog);
    } else {
        String s = "not releasing - isApp=" + isApplicable + ", isAct=" + mIsActive;
        localLog.log(s);
        log(s + " " + networkRequest);
    }
}

注意到TelephonyNetworkFactory呼叫DcTracker的releaseNetwork函式有個前提條件,那就是該TelephonyNetworkFactory處於active狀態。
考慮到ConnectivityService也傳送過CMD_CANCEL_REQUEST給PhoneSwitcher,
因此可能由於時序上的原因,導致TelephonyNetworkFactory在執行onReleaseNetworkFor之前,PhoneSwitcher先進行了DDS。
此時TelephonyNetworkFactory將無法通過onReleaseNetworkFor來完成releaseNetwork的操作。

為了避免這個問題,TelephonyNetworkFactory監聽了DDS變化的動作:

private void onActivePhoneSwitch() {
    final boolean newIsActive = mPhoneSwitcher.isPhoneActive(mPhoneId);
    if (mIsActive != newIsActive) {
         mIsActive = newIsActive;
         if (mIsDefault) {
             applyRequests(mDefaultRequests, (mIsActive ? REQUEST : RELEASE), logString);
         }
         applyRequests(mSpecificRequests, (mIsActive ? REQUEST : RELEASE), logString);
    }
}

private void applyRequests(HashMap<NetworkRequest, LocalLog> requestMap, boolean action,
        String logStr) {
    for (NetworkRequest networkRequest : requestMap.keySet()) {
        if (action == REQUEST) {
            mDcTracker.requestNetwork(networkRequest, localLog);
        } else {
            //同樣呼叫了releaseNetwork
            mDcTracker.releaseNetwork(networkRequest, localLog);
        }
    }
}

綜上所述我們可以看到:
若PhoneSwitcher先進行了DDS,那麼TelephonyNetworkFactory將通過applyRequests來呼叫releaseNetwork;然後在onReleaseNetworkFor中僅進行清除NetworkRequest的記錄;
若TelephonyNetworkFactory先呼叫onReleaseNetworkFor,那麼將清除NetworkRequest的記錄並執行releaseNetwork;發生DDS後,由於已經清除過NetworkRequest的記錄,於是不會重複執行releaseNetwork。

結束語
我們從MMS入手,分析了資料業務中短連線的建立和斷開過程。
可以看到短連線主要利用ConnectivityManager的介面,來完成建立和斷開的操作,同時在必要的時候利用PhoneSwitcher完成DDS。
之後短連線將依賴於長連線的建立和斷開流程,完成實際的操作。