Android7.0 資料撥號前的準備工作
背景
在介紹PhoneApp的建立過程時,我們知道為了支援雙卡手機,PhoneFactory建立了兩個Phone物件。
然而由於通訊制式、功耗等的限制,目前底層的晶片廠商規定modem工作於DSDS模式下,於是同一時間內只有一個Phone具有上網的能力。
本文旨在揭示啟用Phone撥號能力的過程,即講述資料撥號前的準備工作。
版本
android 7.0
1 TelephonyProvider的啟動
資料業務在建立之前,必須有可用的APN,因此我們首先看看Android 7.0中APN載入的過程。
之前分析PhoneApp啟動過程時,我們知道PhoneApp的onCreate函式是靠ActivityThread.java中的handleBindApplication函式呼叫的。
private void handleBindApplication(AppBindData data) {
........
try {
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
//載入App中的provider
installContentProviders(app, data.providers);
...........
}
}
try {
mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception e) {
.........
}
try {
//呼叫App的onCreate函式
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
..............
}
} finally {
.............
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
從上面的程式碼,我們知道在PhoneApp的onCreate被呼叫前,先載入了PhoneApp中的ContentProvider(PM解析xml得到這種包含關係)。
private void installContentProviders(Context context, List<ProviderInfo> providers) {
........
for (ProviderInfo cpi : providers) {
..........
//依次安裝provider
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
............
}
try {
//釋出provider
ActivityManagerNative.getDefault().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
從TelephonyProvider的AndroidManifest.xml,我們知道TelephonyProvider就是執行在Phone程序中的,因此結合上面的程式碼,可以得出結論:
TelephonyProvider將在PhoneApp的onCreate被呼叫前被載入。
Android7.0 TelephonyProvider啟動後的過程,與Android6.0中一致。
之前的blog分析Android6.0中APN載入過程時,已經做過描述了,其實就是建立資料庫、解析xml檔案並存入資料庫的過程,此處不再贅述。
2 設定具有撥號能力的Phone
我們回憶一下PhoneApp啟動後,利用PhoneFactory的makeDefaultPhone建立物件的程式碼,其中:
.........
//建立了一個PhoneSwitcher
sPhoneSwitcher = new PhoneSwitcher(MAX_ACTIVE_PHONES, numPhones,
sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
sPhones);
.........
sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
for (int i = 0; i < numPhones; i++) {
//建立了兩個TelephonyNetworkFactory,分別與每個Phone關聯起來
sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(),
sContext, i, sPhones[i].mDcTracker);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在剛開機時,不插卡的情況下,上面程式碼提及的PhoneSwitcher和TelephonyNetworkFactory將決定具有撥號能力的Phone。
我們在這裡先分析不插卡情況下的流程,主要原因是:
框架對卡資訊有記錄,將會根據記錄資訊改變具有撥號能力的Phone。這個過程是通過呼叫Phone程序提供的介面完成的,我們以後再做分析。
2.1 PhoneSwitcher
我們先來看看PhoneSwitcher:
//PhoneSwitcher繼承自Handler
public class PhoneSwitcher extends Handler {
......
public PhoneSwitcher(......) {
........
//建立NetworkCapabilities,為建立NetworkFactory作準備
//NetworkCapabilities決定了NetworkFactory可以處理的Network Request種類
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
netCap.setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
//建立了一個NetworkFactory物件
NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
netCap, this);
//每個NetworkFactory僅能處理分數比自己低的NetworkRequest
//這裡設定分數為101,將能夠處理所有的request
networkFactory.setScoreFilter(101);
//註冊該NetworkFactory物件
networkFactory.register();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
上面的程式碼涉及到了很多內容,以後有機會再分析,此時我們僅需要有一個映像就是:
PhoneSwitcher建立了一個能夠處理所有Network Request的NetworkFactory(實際上是子類),並呼叫了register方法。
我們來看看NetworkFactory的register方法:
//NetworkFactory繼承Handler
public classNetworkFactoryextendsHandler {
........
public void register() {
if (DBG) log("Registering NetworkFactory");
if (mMessenger == null) {
//構造Messenger物件,其中包含Handler
mMessenger = new Messenger(this);
//呼叫ConnectivityManager的registerNetworkFactory方法
ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
}
}
..........
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
繼續跟進ConnectivityManager中的函式:
public static ConnectivityManager from(Context context) {
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void registerNetworkFactory(Messenger messenger, String name) {
try {
//可以看到將呼叫用mService的registerNetworkFactory函式
mService.registerNetworkFactory(messenger, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
mService的型別為IConnectivityManager,其實是ConnectivityService的Binder通訊的服務端代理,因此上述程式碼最終將呼叫到ConnectivityService的registerNetworkFactory函式。
ConnectivityService是開機時,由SystemServer載入的,這裡我們不分析ConnectivityService啟動的過程,以後有機會單獨介紹ConnectivityService。這裡我們只需要知道ConnectivityService主要負責管理Android中的各種網路。
我們看看ConnectivityService的registerNetworkFactory函式:
@Override
public void registerNetworkFactory(Messenger messenger, String name) {
//許可權檢查
enforceConnectivityInternalPermission();
//構造NetworkFactoryInfo, 該變數用於在ConnectivityService中代表註冊的NetworkFactory
//注意Messager中,包含註冊NetworkFactory內部的Handler
NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
//傳送訊息給給內部的Handler處理,將並行的介面呼叫變為序列的訊息處理
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
ConnectivityService內部的handler將呼叫handleRegisterNetworkFactory處理EVENT_REGISTER_NETWORK_FACTORY訊息:
............
case EVENT_REGISTER_NETWORK_FACTORY: {
handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
break;
}
...........
- 1
- 2
- 3
- 4
- 5
- 6
private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
//以鍵值對的方式,儲存NetworkFactory的messenger和對應的NetworkFactoryInfo
mNetworkFactoryInfos.put(nfi.messenger, nfi);
//呼叫AsyncChannel的connect方法,將ConnectivityService的TrackerHandler與NetworkFactory的Messenger聯絡起來
nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我們看看AsyncChannel的connect方法:
//此處傳遞的srcHandler是ConnectivityService的mTrackerHandler
//dstMessenger為NetworkFactory內部的Messenger
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log("connect srcHandler to the dstMessenger E");
// We are connected
connected(srcContext, srcHandler, dstMessenger);
// Tell source we are half connected
replyHalfConnected(STATUS_SUCCESSFUL);
if (DBG) log("connect srcHandler to the dstMessenger X");
}
public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log("connected srcHandler to the dstMessenger E");
// Initialize source fields
mSrcContext = srcContext;
mSrcHandler = srcHandler;
mSrcMessenger = new Messenger(mSrcHandler);
// Initialize destination fields
mDstMessenger = dstMessenger;
//監聽目的端斷開
linkToDeathMonitor();
if (DBG) log("connected srcHandler to the dstMessenger X");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
看到connnected函式,我們知道每個NetworkFactory註冊後,ConnectivityService將維護對應的NetworkFactoryInfo,對應的鍵值為NetworkFactory中的Messenger物件。
每個NetworkFactoryInfo中有一個AsyncChannel物件,該物件的源端Messenger包裹著ConnectivityService的mTrackerHandler,目的端Messenger為註冊NetworkFactory的Messenger。
ConnectivityService這麼做的目的是:簡化ConnectivityService與每個NetworkFactory通訊時的函式呼叫。
//這裡傳送CMD_CHANNEL_HALF_CONNECTED給ConnectivityService的mTrackerHandler
//replyTo NetworkFactory中的Messenger
private void replyHalfConnected(int status) {
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
if (!linkToDeathMonitor()) {
// Override status to indicate failure
msg.arg1 = STATUS_BINDING_UNSUCCESSFUL;
}
mSrcHandler.sendMessage(msg);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
根據上面的程式碼,我們知道接下來應該是ConnectivityService的mTrackerHandler處理CMD_CHANNEL_HALF_CONNECTED事件。
mTrackerHandler的型別為ConnectivityService的內部類NetworkStateTrackerHandler:
private classNetworkStateTrackerHandlerextendsHandler {
...........
@Override
public void handleMessage(Message msg) {
//這裡的程式碼寫法還是很讚的,特意看了Android6.0之前的程式碼
//在Android6.0之前,是在一個handleMessage中,根據msg.what使用大量的switch-case語句
//現在這樣寫,雖然本質上仍是大量的switch-case,但至少作了個分類
if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
.......
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在這裡我們看看maybeHandleAsyncChannelMessage:
private boolean maybeHandleAsyncChannelMessage(Message msg) {
switch (msg.what) {
//說實話這個程式碼寫的辣眼睛
//自己寫switch-case,一直將default寫在最後,導致自己認為default是可以匹配所有的case
//實際上,當剩餘的case匹配不上時,default才會去匹配,default寫在之前之後,沒有關係
default:
return false;
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
//這裡我們呼叫這個函式
handleAsyncChannelHalfConnect(msg);
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai != null) nai.asyncChannel.disconnect();
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
handleAsyncChannelDisconnected(msg);
break;
}
}
return true;
}
private void handleAsyncChannelHalfConnect(Message msg) {
AsyncChannel ac = (AsyncChannel) msg.obj;
//從前面的程式碼,我們知道msg.replyTo為NetworkFactory對應的Messenger
if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log("NetworkFactory connected");
// A network factory has connected. Send it all current NetworkRequests.
//這裡的NetworkRequest等下說明
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
//NetworkRequest分為監聽網路用的,和申請網路用的
//僅處理申請網路用的
if (!nri.isRequest()) continue;
//判斷NetworkRequest是否有對應的NetworkAgentInfo
//當一個網路建立後,將會生成對應的NetworkAgent註冊到ConnectivityService
//這裡是取出NetworkRequest對應的NetworkAgentInfo
NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
//將CMD_REQUEST_NETWORK的訊息傳送給NetworkFactory,同時指定該request的分數
ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
}
} else {
loge("Error connecting NetworkFactory");
mNetworkFactoryInfos.remove(msg.obj);
}
} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
//這裡是傳送給NetworkAgent的訊息,我們暫時不關注
.........
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
上面的程式碼其實就是發訊息給NetworkFactory。這裡存在的疑點是:
1、開機時是否有NetworkRequest?
2、傳送CMD_REQUEST_NETWORK時為什麼要攜帶分數?
我們先來分析第一個問題:
//建構函式
public ConnectivityService(.......) {
if (DBG) log("ConnectivityService starting up");
//初始時建立了DefaultRequest
mDefaultRequest = createInternetRequestForTransport(-1);
//建立對應的NetworkRequestInfo
NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder(), NetworkRequestType.REQUEST);
//按鍵值對儲存
mNetworkRequests.put(mDefaultRequest, defaultNRI);
mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
............
}
//初始時,ConnectivityService建立NetworkRequest傳入的引數為-1
private NetworkRequest createInternetRequestForTransport(int transportType) {
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (transportType > -1) {
netCap.addTransportType(transportType);
}
return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
從上面的程式碼,我們知道了開機後,ConnectivityService中預設就會存在一個NetworkRequest,於是每當一個新的NetworkFactory註冊到ConnectivityService後,都會處理這個預設的NetworkRequest。
現在我們再來回答第二個問題,為什麼傳送CMD_REQUEST_NETWORK時,需要攜帶分數。
這個問題其實涉及了Android框架,管理整個網路的架構。具體的情況,今後介紹ConnectivityService時,會詳細介紹。
在這裡直接丟擲近似的答案:Android通過ConnectivityService管理所有的網路,每一種網路都有各自的NetworkFactory。當NetworkFactory收到NetworkRequest後,將建立實際的網路物件。ConnectivityService用NetworkAgent和NetworkAgentInfo來抽象實際的網路物件的一些特性(還有其它的物件,例如Network,LinkProperties等共同抽象網路)。
然而,同一個裝置在同一時間內不需要連線多個網路。比如說,使用者的目的就是上網,但WiFi和行動網路均能滿足使用者上網的需求,因此裝置沒有必要同時連線WiFi和行動網路。因此,Android需要一種機制來權衡各種網路的是否應該保留。
為此android引入了分數的概念,當兩個網路能同時滿足一個NetworkRequest時,分數高者就留下。根據這個規則,我們其實也知道如果兩個網路分別滿足不同的NetworkRequest時,是可以共存的。
以上是基本的結論,今後分析ConnectivityService時,會分析對應的原始碼。
知道原理後,
現在回過頭來看看傳送CMD_REQUEST_NETWORK時,為什麼要攜帶分數。
當訊息傳送給NetworkFactory後,NetworkFactory如果能滿足NetworkRequest的需求,需要生成對應當NetworkAgent註冊到ConnectivityService。如果這個新生成的NetworkAgent的分數,比之前滿足NetworkRequest的已存在的NetworkAgent分數低,那麼這個NetworkAgent將被處理掉。
因此,與其浪費資源生成一個無用的NetworkAgent,不如一開始就讓NetworkFactory通過比較分數,不處理高分NetworkRequest。
這種設計類似於從聲源阻止噪聲吧!
回答完問題後,讓我們繼續回到之前ConnectivityService的流程,接下來看看AsyncChannel如何傳送訊息:
//一層層的sendMessage呼叫,最後會呼叫到這個sendMessage
public void sendMessage(Message msg) {
//根據前面的分析,我們知道ConnectivityService中NetworkFactoryInfo的AsyncChannel中
//mSrcMessenger中包裹的是mTrackerHandler
msg.replyTo = mSrcMessenger;
try {
//DstMessenger中包裹的是NetworkFactory中的handler
//binder通訊,發往了NetworkFactory
mDstMessenger.send(msg);
} catch (RemoteException e) {
replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
至此,訊息終於又發回了PhoneSwitcher中建立的NetworkFactory,訊息的處理由父類NetworkFactory處理:
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CMD_REQUEST_NETWORK: {
//呼叫該函式處理
handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
break;
}
case CMD_CANCEL_REQUEST: {
handleRemoveRequest((NetworkRequest) msg.obj);
break;
}
case CMD_SET_SCORE: {
handleSetScore(msg.arg1);
break;
}
case CMD_SET_FILTER: {
handleSetFilter((NetworkCapabilities) msg.obj);
break;
}
}
}
@VisibleForTesting
protected void handleAddRequest(NetworkRequest request, int score) {
//判斷之前是否處理過該NetworkRequest
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 {
if (VDBG) log("new score " + score + " for exisiting request " + request);
//對於處理過的NetworkRequest,僅更新器分數
//原因是:ConnectivityService中匹配該NetworkRequest的NetworkAgent可能變化,所以需要更新分數
n.score = score;
}
if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
//評估NetworkRequest
evalRequest(n);
}
private void evalRequest(NetworkRequestInfo n) {
if (VDBG) log("evalRequest");
//n.requested == false表示該request未被處理過
//n.score < mScore表示request的分數小於NetworkFactory的分數(子類定義)
//satisfiedByNetworkCapabilities是為了判斷NetworkFactory(子類)的網路能力能滿足NetworkRequest
//acceptRequest函式衡返回true,這是留給子類覆蓋用的吧,一般沒有覆蓋
if (n.requested == false && n.score < mScore &&
n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter) && acceptRequest(n.request, n.score)) {
if (VDBG) log(" needNetworkFor");
//進入子類的實現
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)) {
if (VDBG) log(" releaseNetworkFor");
releaseNetworkFor(n.request);
n.requested = false;
} else {
if (VDBG) log(" done");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
繞了一圈,我們現在可以看看PhoneSwitcher中建立的NetworkFactory的needNetworkFor函式:
@Override
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
if (VDBG) log("needNetworkFor " + networkRequest + ", " + score);
Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
msg.obj = networkRequest;
//傳送訊息給自己的handler處理,將呼叫PhoneSwitcher中的onRequestNetwork處理
msg.sendToTarget();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
private void onRequestNetwork(NetworkRequest networkRequest) {
//利用NetworkRequest構造DcRequest
final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
//之前沒有處理過的dcRequest,才會進行處理
if (mPrioritizedDcRequests.contains(dcRequest) == false) {
mPrioritizedDcRequests.add(dcRequest);
//根據優先順序進行排序
Collections.sort(mPrioritizedDcRequests);
//評估,REQUESTS_CHANGED的值為true
onEvaluate(REQUESTS_CHANGED, "netRequest");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
這裡的唯一的問題就是“根據優先順序排序”的概念。
我們花點時間來看看DcRequest這個類:
public classDcRequestimplementsComparable<DcRequest> {
..........
public DcRequest(NetworkRequest nr, Context context) {
//初始化優先順序排序規則
initApnPriorities(context);
networkRequest = nr;
//從NetworkReqeust得出對應的匹配APN id
apnId = apnIdForNetworkRequest(networkRequest);
//得到這個NetworkReqeust的優先順序
priority = priorityForApnId(apnId);
}
.........
private void initApnPriorities(Context context) {
synchronized (sApnPriorityMap) {
//初始化優先順序配置僅用執行一次
if (sApnPriorityMap.isEmpty()) {
//解析xml檔案,得到配置資訊
String[] networkConfigStrings = context.getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String networkConfigString : networkConfigStrings) {
NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
//從配置資訊中,得到apnId
final int apnId = ApnContext.apnIdForType(networkConfig.type);
//以鍵值對的方式,將apnId和優先順序資訊放入到sApnPriorityMap中
sApnPriorityMap.put(apnId, networkConfig.priority);
}
}
}
}
.........
private int apnIdForNetworkRequest(NetworkRequest nr) {
NetworkCapabilities nc = nr.networkCapabilities;
// For now, ignore the bandwidth stuff
if (nc.getTransportTypes().length > 0 &&
nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == false) {
return APN_INVALID_ID;
}
int apnId = APN_INVALID_ID;
boolean error = false;
//其實就是根據NetworkRequest申請的NetworkCapbility,來決定對應的apnId
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
if (apnId != APN_INVALID_ID) error = true;
apnId = APN_DEFAULT_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
if (apnId != APN_INVALID_ID) error = true;
apnId = APN_MMS_ID;
}
......
return apnId;
}
..........
public int compareTo(DcRequest o) {
return o.priority - priority;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
從上面的程式碼,我們知道DcRequest其實就是根據xml中的配置檔案得到每個Apn對應的優先順序;然後,根據每個NetworkRequest需要的NetworkCapabilities得到對應的apnId;最終,根據apnId的優先順序,完成對NetworkRequest的優先順序排序。
其中,networkAttributes定義於frameworks/base/core/res/res/values/config.xml中:
<string-array translatable="false" name="networkAttributes">
<item>"wifi,1,1,1,-1,true"</item>
<item>"mobile,0,0,0,-1,true"</
相關推薦
Android7.0 資料撥號前的準備工作
背景
在介紹PhoneApp的建立過程時,我們知道為了支援雙卡手機,PhoneFactory建立了兩個Phone物件。
然而由於通訊制式、功耗等的限制,目前底層的晶片廠商規定modem工作於DSDS模式下,於是同一時間內只有一個Phone具有上網的
Android7.0 資料撥號前的準備工作
背景
在介紹PhoneApp的建立過程時,我們知道為了支援雙卡手機,PhoneFactory建立了兩個Phone物件。
然而由於通訊制式、功耗等的限制,目前底層的晶片廠商規定modem工作於DSDS模式下,於是同一時間內只有一個Phone具有上網的能力。
STS使用前準備工作
ini 是個 編碼 too blog 包裝 顯示 src tor
STS(Spring Tool Suite)其實是個被包裝過的eclipse,明白這個,其他就簡單了。
首先,調整字體。
中文很麻煩的,因為編碼問題。習慣性將編碼都設置成utf-8。
顯
我的第一個python web開發框架(5)——開發前準備工作(了解編碼前需要知道的一些常識)
turn 框架 strong pep8 加密與解密 python開發 lan 二次 沒有 中午吃飯時間到了,小白趕緊向老菜坐的位置走過去。
小白:老大,中午請你吃飯。
老菜:哈哈...又遇到問題了吧,這次得狠狠宰你一頓才行。
小白:行行行,只要您賞臉,
企業官網升級前準備工作有哪些
企業官方網站運營一段時間後,因為網路技術的發展以及企業官網伺服器環境的改變,企業原有企業官網可能會出現相容性、整體視覺、功能實現等方面的缺陷,而企業官網升級將迅速彌補以上不足。伴隨著使用者需求和商業需求的變更,我們會給企業官網逐步作一些調整,以便更適應企業官網一步步發展的需求。當這些小的改變
openstack--JUNO10搭建手冊整理1:搭建前準備工作
一、提前宣告:
1.本系列整理只是完整的openstack--JUNO10搭建過程,沒有過多的原理,如果你想詳細瞭解openstack的執行原理及機制,請參考其他資料。
2.openstack--JUNO10對所使用的系統版本有要求,必須是CENTOS7-1406,如果沒
CentOS下asterisk安裝前準備工作
新搞了臺機器,安裝一下asterisk試試,配了塊4e1的卡,記錄一下安裝過程。
第一步,作業系統
安裝CentOS 5.2 DVD版,硬碟大,把能裝的服務都安裝一下。
這個安裝過程,還是單獨寫一下吧,免得忘記了。
第二步,asterisk的安裝
1.檢查系統核
人工智慧與資料探勘準備工作--配置環境--TensorFlow(3)
這次我們來安裝Tensorflow。Tensorflow是做深度學習的一個很好的框架,最近很火的~我以前做過caffe的框架,好難啊·····太多不理解,只會照葫蘆畫瓢,自己訓練的模型手寫數字的識別率只達到20%所以這次我嘗試學習了TensorFlowTensorFlow在W
Android專案開發前準備工作(一),android專案開發
1:專案開發前,我們一定要全面瞭解專案中所有的介面需求,實現介面都需要用到哪些元件,比如現在的Android應用中特別流行圓形
Icon、Listview上滑載入、下拉重新整理、百度定位、各種分享、Listview動態載入網路圖片等等所有的需求,在應用開發前,
Android7.0 資料業務中的短連線
資料業務中的短連線,是一種為了滿足業務需求,臨時建立起來的連線。當業務完成通訊需求後,這種資料連線會被框架釋放掉。與之相對,長連線一旦撥號成功就會一直存在下去,除非使用者主動關閉或者終端受到網路等因素的影響導致連線不可用。
一種比較常見的例子,就是傳送彩信時,
人工智慧與資料探勘準備工作--配置環境--同時安裝python3.6和2.7並做到切換(1)
本人德國本科生電子資訊工程剛剛畢業,準備德國讀研,空出來4個月假期,所以想分享一下對於人工智慧的看法以及一些入門準備操作。網上關於人工智慧的教程鋪天蓋地,本次部落格也是記錄了一個零基礎小白跨入人工智慧的一切過程。記錄部落格一方面是將自己所學知識進行鞏固,一方面也是想將自己一些
Java資料採集--1.準備工作
前言:自從2014年4月大一開始接觸Java,7月開始接觸網路爬蟲至今已經兩年的時間,共抓取非同型別網站150餘個,其中包括一些超大型網站,比如百度文庫,亞馬遜,魔方格,學科網等。也在學長五年經驗留下來的程式碼的基礎上,整合成一個小型的爬蟲框架,主要用於抓取期刊
Ubuntu 16.04(x64)內安裝lightgbm前的準備工作
6.0 attribute htm 配置 github 自帶 ecn 滿足 rem Ubuntu 16.04自帶python 2.7,滿足了lightgbm最低要求
1、添加必備的一些包
sudo apt-get install python-numpy sudo apt-
2.11.1.移植前的準備工作
down windows共享 三星 介紹 inux 重啟 都是 配置 編譯運行 本節介紹uboot移植工作正式開始前的準備工作,主要是環境搭建和必備工具的使用。
2.11.1.1、三星移植過的uboot源代碼準備
(1)三星對於S5PV210的官方開發板為SMDKV210,
【JVM譯文】JVM問題定位前的準備工作有哪些
print 圖片 files 機器 ali 你會 cat 方便 pdu 一、序
最近在學習jvm工具時,不少鏈接直指oracle官網。才發現有不少好東西。
本文翻譯自:
https://docs.oracle.com/javase/8/docs/technotes/g
ESP32那些事兒(二):磨刀不誤砍柴功-做好專案開發前的準備工作
如果你是第一次接觸FreeRTOS和ESPRESSIF的產品,例如我,那還是要先來個整體印象,然後再逐個深化。做專案的都知道,老闆們是不允許我們四平八穩的研究完然後再開始專案。 那也不妨礙咱們
微信小程式小白總結全攻略3-簡易開發前的準備工作
老規矩,廢話全在第一章說了,下面直奔主題。
Why?
為什麼要做這些準備工作?
答:對於一個微信小程式的簡易開發,儘管核心在於其設計和程式設計實現。但是有很多東西要提前準備好,否則都程式設計完了再去弄,會有很多
大資料學習準備工作
一、linux安裝
1.1安裝vmware軟體
1.2驗證VMWARE是否安裝成功
(如果機器在安裝vmware的時候會出現一個錯誤:virtual XT,這需要重啟電腦<按F2/按DELET/.........
Python 資料分析:第一篇 準備工作
一、安裝或升級Python包
1、安裝Anaconda中的Python包conda install package_name 或者pip install package_name
⽤conda update命令升級包conda update package_name 或者pip install --upg
建立maven專案前的準備工作
第二步:
在maven中的settings.xml檔案中指定
2.1 本地倉庫:計算機中一個資料夾,自己定義是哪個資料夾.
2.1 示例語法 <localRepository>D:/maven/r2/myrepository</localRepository>