編輯:關於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); } }
結束語
以上就是長連接去撥號的主要過程。與撥號流程相比,去撥號流程相對簡單,涉及的類也與撥號基本相似,這裡就不再補充類圖和流程圖了。
前沿:在全新的Camera API2架構下,常常會有人疑問再也看不到熟悉的SetParameter/Paramters等相關的身影,取而代之的是一
1.初始置換/IP置換// 初始置換表 private static final int[] IP_Table = { 58, 50, 42, 34, 26, 18, 1
父Activity啟動子Activity,並且向其傳遞消息,子Activity啟動後完成相應的操作後回饋父Activity消息,父Activity完成相應的操作。The
我們有新的項目要進行開發了,一直想用用android studio。所以在新項目上,果斷使用。這裡是我將android studio項目share到svn倉庫的全過程。後