編輯:關於Android編程
在之前的博客中,我們分析了數據業務長連接的撥號過程,現在我們來看看Android如何實現去撥號過程。
與撥號過程一樣,用戶也是通過點擊設置界面,發起主動斷開數據連接的命令。
從界面到框架,同樣經過了CellDataPreference、TelephonyManager、PhoneInterfaceManager、GsmCdmaPhone和DcTracker的處理。去撥號和撥號調用的框架接口是一樣的,只是參數不一致。
最終,從DcTracker的onSetUserDataEnabled函數開始,去撥號與撥號的流程產生了分歧:
1 DcTracker
//去撥號時參數為false
private void onSetUserDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
if (mUserDataEnabled != enabled) {
mUserDataEnabled = enabled;
//寫數據庫,某些情況下發送通知
..............
if (enabled) {
//進入撥號流程
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
//進入去撥號號流程
onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
}
}
}
}
private void onCleanUpAllConnections(String cause) {
cleanUpAllConnections(true, cause);
}
我們進一步看看cleanUpAllConnections:
private boolean cleanUpAllConnections(boolean tearDown, String reason) {
boolean didDisconnect = false;
boolean specificDisable = false;
if (!TextUtils.isEmpty(reason)) {
//主動撥號的reason為REASON_DATA_SPECIFIC_DISABLED,將specificDisable置為true
specificDisable = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED) ||
reason.equals(Phone.REASON_ROAMING_ON);
}
for (ApnContext apnContext : mApnContexts.values()) {
//根據ApnContext中記錄的狀態,判斷是否有未斷開的連接
if (apnContext.isDisconnected() == false) didDisconnect = true;
//specificDisable的值為true
if (specificDisable) {
ApnSetting apnSetting = apnContext.getApnSetting();
//默認的數據撥號,均滿足以下條件
//isMetered的返回值,由CarrierConfigManager中的配置決定,default類型將返回true
if (apnSetting != null && apnSetting.isMetered(mPhone.getContext(),
mPhone.getSubId(), mPhone.getServiceState().getDataRoaming())) {
apnContext.setReason(reason);
//進入斷開連接的流程
cleanUpConnection(tearDown, apnContext);
}
} else {
............
}
}
//不再檢測數據業務數據的收發的情況
//數據業務上下行圖標消失
stopNetStatPoll();
//同樣,不再檢測數據業務是否僅發送無接收
stopDataStallAlarm();
..................
}
隨著流程我們進入到cleanUpConnection:
private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
..............
//取出ApnContext中存儲的DcTracker與DataConnection通信的AsyncChannel
DcAsyncChannel dcac = apnContext.getDcAc();
..............
if (tearDown) {
if (apnContext.isDisconnected()) {
............
} else {
if (dcac != null) {
if (apnContext.getState() != DctConstants.State.DISCONNECTING) {
..................
//取出將要斷開的DataConnection的id,打印log和記錄用
final int generation = apnContext.getConnectionGeneration();
..................
Pair pair =
new Pair(apnContext, generation);
//斷開處理完後,將消息返回給DcTracker處理
Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
//APN類型為DUN才會進入
if (disconnectAll) {
...........
} else {
//利用DcAsyncChannel向DataConnection發送消息
apnContext.getDcAc()
.tearDown(apnContext, apnContext.getReason(), msg);
}
//更新ApnContext的狀態
apnContext.setState(DctConstants.State.DISCONNECTING);
mDisconnectPendingCount++;
}
}
}
} else {
............
}
if (dcac != null) {
cancelReconnectAlarm(apnContext);
}
............
}
我們看看DcAsyncChannel的tearDown函數:
public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
............
sendMessage(DataConnection.EVENT_DISCONNECT,
new DisconnectParams(apnContext, reason, onCompletedMsg));
}
從上面的代碼可以看到,DcAsyncChannel將向DataConnection發送EVENT_DISCONNECT消息。
2 DataConnection
2.1 DcActiveState
在長連接撥號的博客中,我們已經提過,當數據業務撥號成功後,DataConnection會處於DcActiveState,因此去撥號時將由DcActiveState來處理EVENT_DISCONNECT消息:
private class DcActiveState extends State {
..............
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
...........
case EVENT_DISCONNECT: {
DisconnectParams dp = (DisconnectParams) msg.obj;
............
if (mApnContexts.containsKey(dp.mApnContext)) {
.............
if (mApnContexts.size() == 1) {
mApnContexts.clear();
...................
//利用RIL向modem發送DEACTIVATE_DATA_CALL消息
//發送成功後,RIL將返回DataConnection EVENT_DEACTIVATE_DONE
tearDownData(dp);
//DataConnection轉移狀態
transitionTo(mDisconnectingState);
} else {
//注意到當一個DataConnection被多個ApnContext復用時
//去撥號只會移除這個DataConnection,並沒有進行斷開操作
mApnContexts.remove(dp.mApnContext);
notifyDisconnectCompleted(dp, false);
}
} else {
.............
}
retVal = HANDLED;
break;
}
}
...........
}
}
在DataConnection離開DcActiveState時,將調用其中的exit函數:
@Override
public void exit() {
............
//不用再監聽通話的開始與結束
mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
reason, mNetworkInfo.getExtraInfo());
if (mNetworkAgent != null) {
//更新NetworkAgent的狀態,將通知ConnectivityService移除網絡,後文分析
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent = null;
}
}
2.2 DcDisconnectingState
private class DcDisconnectingState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
............
case EVENT_DEACTIVATE_DONE:
AsyncResult ar = (AsyncResult) msg.obj;
DisconnectParams dp = (DisconnectParams) ar.userObj;
..................
//保證發送和接受的消息是一一對應的
if (dp.mTag == mTag) {
................
//回到最初未撥號時的InactiveState
transitionTo(mInactiveState);
} else {
...............
}
retVal = HANDLED;
break;
.............
}
return return retVal;
}
}
2.3 DcInactiveState
從其它狀態進入DcInactiveState時,需要調用對應的enter函數:
private class DcInactiveState extends State {
...........
@Override
public void enter() {
...........
if (mDisconnectParams != null) {
..............
//發送EVENT_DISCONNECT_DONE消息給DcTracker處理
notifyDisconnectCompleted(mDisconnectParams, true);
}
...........
}
...........
}
至此,數據業務長連接去撥號的流程基本結束了,我們只需要再看看ConnectivityService移除NetworkAgent的操作,以及DcTracker收到去撥號完成消息後的處理流程。
3 ConnectivityService
前面的代碼已經提到,在DataConnection離開DcActiveState時,調用NetworkAgent的sendNetworkInfo函數,其中NetworkAgent的NetworkInfo已經處於DISCONNECTED狀態。
NetworkAgent中sendNetworkInfo函數如下:
public void sendNetworkInfo(NetworkInfo networkInfo) {
//其實就是利用NetworkAgent與ConnectivityService連接的AsyncChannel,向ConnectivityService發送消息
queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}
ConnectivityService中的NetworkStateTrackerHandler將處理該信息:
.............
case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
NetworkInfo info = (NetworkInfo) msg.obj;
//之前撥號時,也調用過updateNetworkInfo
updateNetworkInfo(nai, info);
break;
}
.............
隨著流程我們再次分析一下updateNetworkInfo:
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
............
if (!networkAgent.created
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
//撥號流程創建NetworkAgent的工作
.................
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
//執行撥號對應的操作
........
} else if (state == NetworkInfo.State.DISCONNECTED) {
//ConnectivityService斷開與NetworkAgent的通信
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
//VPN相關
.................
}
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
//suspend, 目前不關注
............
}
}
進入到AsyncChannel中的disconnect函數:
public void disconnect() {
if ((mConnection != null) && (mSrcContext != null)) {
//解除源端的綁定
mSrcContext.unbindService(mConnection);
mConnection = null;
}
try {
Message msg = Message.obtain();
msg.what = CMD_CHANNEL_DISCONNECTED;
msg.replyTo = mSrcMessenger;
//發送消息給NetworkAgent
mDstMessenger.send(msg);
} catch(Exception e) {
}
//回復消息給ConnectivityService
replyDisconnected(STATUS_SUCCESSFUL);
mSrcHandler = null;
.................
}
private void replyDisconnected(int status) {
if (mSrcHandler == null) return;
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
mSrcHandler.sendMessage(msg);
}
ConnectivityService中的NetworkStateTrackerHandler收到消息後,將調用handleAsyncChannelDisconnected進行處理:
private void handleAsyncChannelDisconnected(Message msg) {
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai != null) {
//清除與NetworkAgent相關的信息,同時通過回調通知觀察者
................
for (int i = 0; i < nai.networkRequests.size(); i++) {
NetworkRequest request = nai.networkRequests.valueAt(i);
NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
//NetworkAgent如果是其它Request的最優匹配對象,那麼當該NetworkAgent被移除時
//需要更新分數通知給NetworkFactory,讓它們嘗試重新聯網,以選出新的最優NetworkAgent
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
mNetworkForRequestId.remove(request.requestId);
sendUpdatedScoreToFactories(request, 0);
}
........
//讓所有的NetworkAgent與NetworkRequest進行重新匹配
//移除一個NetworkAgent後,感覺不需要進行這個操作;畢竟上面已經做過sendUpdatedScoreToFactories的操作,會觸發重新匹配的
rematchAllNetworksAndRequests(null, 0);
if (nai.created) {
try {
//移除Native層創建的網絡對象,會同時移除對應的路由規則
//這裡也是通過NetworkManagementService到CommandListener中完成的
//與撥號流程
mNetd.removeNetwork(nai.network.netId);
} catch (Exception e) {
loge("Exception removing network: " + e);
}
..........
}
}
} else {
............
}
}
至此,去撥號流程中,ConnectivityService的工作就完成了。
4 DcTracker
在DataConnection進入到DcInactiveState後,將向DcTracker發送EVENT_DISCONNECT_DONE消息,DcTracker將調用onDisconnectDone進行處理。
private void onDisconnectDone(AsyncResult ar) {
//取出去撥號的apnContext
ApnContext apnContext = getValidApnContext(ar, "onDisconnectDone");
if (apnContext == null) return;
//重新置為Idle態
apnContext.setState(DctConstants.State.IDLE);
//通過DefaultPhoneNotifier,TelephonyRegistry通知給APK
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
//飛行模式相關的判斷
...................
//此時,APN仍處於激活狀態;滿足重撥條件時,會嘗試撥號
if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
............
long delay = apnContext.getInterApnDelay(mFailFast);
if (delay > 0) {
//經過一段時間後,將發送Intent,對應action為INTENT_RECONNECT_ALARM
startAlarmForReconnect(delay, apnContext);
}
} else {
.............
}
if (mDisconnectPendingCount > 0)
mDisconnectPendingCount--;
if (mDisconnectPendingCount == 0) {
..................
//通知觀察者,數據連接斷開
notifyDataDisconnectComplete();
notifyAllDataDisconnected();
..................
}
}
DcTrackerINTENT_RECONNECT_ALARM後,調用onActionIntentReconnectAlarm進行處理:
private void onActionIntentReconnectAlarm(Intent intent) {
..............
if ((apnContext != null) && (apnContext.isEnabled())) {
................
if ((apnContextState == DctConstants.State.FAILED)
|| (apnContextState == DctConstants.State.IDLE)) {
DcAsyncChannel dcac = apnContext.getDcAc();
if (dcac != null) {
dcac.tearDown(apnContext, "", null);
}
apnContext.setDataConnectionAc(null);
apnContext.setState(DctConstants.State.IDLE);
} else {
............
}
//重新發起撥號請求;由於數據開關已經關閉,不會再進行實際的撥號
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
apnContext.setReconnectIntent(null);
}
}
結束語
以上就是長連接去撥號的主要過程。與撥號流程相比,去撥號流程相對簡單,涉及的類也與撥號基本相似,這裡就不再補充類圖和流程圖了。
Android Camera API2中采用CameraMetadata用於從APP到HAL的參數交互
前沿:在全新的Camera API2架構下,常常會有人疑問再也看不到熟悉的SetParameter/Paramters等相關的身影,取而代之的是一
Android/java源代碼實現DES算法原理+整理
1.初始置換/IP置換// 初始置換表 private static final int[] IP_Table = { 58, 50, 42, 34, 26, 18, 1
Android APP--兩個Activity傳遞數據
父Activity啟動子Activity,並且向其傳遞消息,子Activity啟動後完成相應的操作後回饋父Activity消息,父Activity完成相應的操作。The
Android studio share項目到svn倉庫
我們有新的項目要進行開發了,一直想用用android studio。所以在新項目上,果斷使用。這裡是我將android studio項目share到svn倉庫的全過程。後