首先拋開Android的一切概念來研究一下電話系統的最基本的描述。咱們的手機首先用來打電話的,隨後是須要一個電話本,隨後是PIM,隨後是網絡應用,隨後是雲計算,隨後是想咱們的手機無所不能,替代PC。可是做爲一個電話的基本功能以下:java
0)撥叫電話,接聽電話,掛斷電話,發送短信,網絡鏈接,PIM管理linux
1)因爲電話運營商爲咱們提供了呼叫等待,電話會議等補充業務,因此咱們的手機須要管理多路通話,如何管理?android
2)來電時,咱們要播出來電鈴聲,接通時咱們須要切換語音通道,這個又跟多媒體系統打上了交道,例若有耳機插上了,有藍牙耳機連上了,系統該作如何的管理和切換?數組
3)上網的網絡通路創建(例如GSM GPRS),如何PPP鏈接並鏈接到LinuxSocket通道上的?系統如何管理數據鏈接?網絡
4)AP跟Modem通信時經過AT指令的,如何將AT指令變成一個個具體的操做函數,如何管理Modem發給咱們的迴應,AT命令通道,數據通道如何管理?架構
5)sim卡的電話本如何管理?框架
上面的關於手機的基本問題,Android電話系統設計者必需要解答的問題。該設計如何的管理框架,提出什麼概念來表達?因此要分析Android的電話部分,仍是須要理解電話實現的背景知識,通信協議,大致框架。異步
咱們回到電話系統基本構成上,先從總體上去把握一下電話模塊的大致框架,先從空中俯瞰。我給出的圖是通常的智能手機的框架圖,該框架基本可以歸納全部手機電話模塊的構成,固然也包括Android的電話系統構成。socket
智能機架構通常是應用處理器+Modem。應用處理器與Modem的鏈接使用串口或者USB。在一個硬件串口通路上實現爲了要同時實現數據傳輸並同時實現 控制Modem,就須要實現多路複用協議(GSM TS07.10),在底層咱們在多路複用的基礎上虛擬了兩個串口,一個用於CMD通道,一個用於DATA通道。電話的全部控制通路都是在這連個通道上。函數
RIL,Radio Interface Layer。本層爲一個協議轉換層,手機框架須要適應多類型的Modem接入到系統中,而對於不一樣的Modem有不一樣的特性,AT指令的格式或者回應有所 不一樣,可是這種特性在設計應用時不可能徹底考慮和兼容。因此設計者在設計電話系統時,創建了一個虛擬電話系統,爲該虛擬電話系統規定了標準的功能,上層的 電話管理都是創建在這些標準的功能基礎之上。而RIL則是將虛擬電話系統的標準功能轉換成實際的所使用的Modem的AT指令。
Android設計者將電話系統設計成了三部分。
Andoird的Phone Service實際上是PhoneApp。GSMPhone(CDMAPhone)是Phone Service核心的對象,他包含了以下的相關對象。
咱們的分析任務就是要把這些對象的相互關係,及其對象間數據傳遞關係弄清楚。首先咱們給出如下的Android電話系統的框架,以便對Android電話系統有個概要的認識,而後從數據流的角度,以及對象的引用關係來分析系統。下面是android電話系統總體框架圖。
Rild是Init進程啓動的一個本地服務,這個本地服務並無使用Binder之類的通信手段,而是採用了socket通信這種方式。RIL(Radio Interface Layer)
Android 給出了一個ril實現框架。因爲Android開發者使用的Modem是不同的,各類指令格式,初始化序列均可能不同,GSM和CDMA就差異更大 了,因此爲了消除這些差異,Android設計者將ril作了一個抽象,使用一個虛擬電話的概念。這個虛擬電話對象就是 GSMPhone(CDMAPhone),Phon對象所提供的功能協議,以及要求下層的支撐環境都有一個統一的描述,這個底層描述的實現就是靠RIL來 完成適配。
Andoid將RIL層分爲兩個代碼空間:RILD管理 框架,AT相關的xxxril.so動態連接庫。將RIL獨立成一個動態連接庫的好處就是Android系統適應不一樣的Modem,不一樣的Mode能夠有 一個獨立的Ril與之對應。從這個層面上看,Rild更可能是一個管理框架。
而ril是具體的AT指令合成者和應答解析者。從最基本的功能來說,ril創建了一個偵聽Socket,等待客戶端的鏈接,而後從該鏈接上讀取RIL- Java成傳遞來的命令並轉化成AT指令發送到Modem。並等待Modem的迴應,而後將結果經過套接口傳回到Ril-Java層。下圖是Ril-D的 基本框架:
下面的數據流傳遞描述圖表描述了RIL-JAVA層發出一個電話指令的5 步曲。
在 AT通信的過程當中有兩類響應:一種是請求後給出應答,一種是通知類,即爲不請自來的,例如短信通知達到,咱們稱該類通知爲URC。在Rild中URC和一 般的Response是分開處理的,概念上URC由handleUnsolicited@Atchannel.c處理,而Response由 handleFinalResponse來處理。
Rild 管理的真正精髓在ril.cpp,ril_event.cpp中,在研究的過程當中,能夠看到設計者在抽象上所下的功夫,設計得很優美。Event Loop的基本工做就是等待在事件端口(串口,Socket),一旦有數據到達就根據登記的Event回調函數進行處理。如今來看Ril設計者是如何創建 一套管理框架來完成這些工做的?
Event對象構成:(fd,index,persist,func,param)
fd | 事件相關設備句柄。例如對於串口數據事件,fd就是相關串口的設備句柄 |
index | |
persist | 若是是保持的,則不從watch_list中刪除。 |
func | 回調事件處理函數 |
param | 回調時參數 |
爲了統一管理事件,Android使用了三個隊列:watch_list,timer_list,pending_list,並使用了一個設備句柄池readFDS。
readFDS:是Linux的fd_set,readFDS保存了Rild中全部的設備文件句柄,以便利用select函數統一的完成事件的偵聽。
watch_list:監測時間隊列。須要檢測的事件都放入到該隊列中。
timer_list:timer隊列
pending_list:待處理事件隊列,事件已經觸發,須要所回調處理的事件。
事件隊列隊列的操做:ril_event_add,ril_event_del, ril_timer_add
在添加操做中,有兩個動做:
(1) 加入到watch_list
(2) 將句柄加入到readFDS事件句柄池。
咱們知道對於Linux設備來說,咱們可使用select函數等待在FDS上,只要FDS中記錄的設備有數據到來,select就會設置相應的標誌位並 返回。readFDS記錄了全部的事件相關設備句柄。readFDS中句柄是在在AddEvent加入的。全部的事件偵聽都是創建在linux的 select readFDS基礎上。
ril_event_loop 利用select等待在readFDS(fd_set)上,當select設備有數據時,ril_event_loop會從select返回,在 watch_list中相應的Event放置到pend_list,若是Event是持久性的則不從watch_list中刪除。而後 ril_event_loop遍歷pengding_list處理Event事件,發起事件回調函數。
上面分析了ril-d的框架,在該框架上跑的事件有什麼
(1)s_listen_event- (s_fdListen,listenCallback)
listenCallback處理函數,
接收客戶端鏈接:s_fdCommand=accepte(..)
添加s_commands_event()
從新創建s_listen_event,等待下一次鏈接
(2) s_command_event(s_fdCommand,ProcessCommandsCallback)
從fdCommand Socket鏈接中讀取StreamRecord
使用ProcessCommandBufer處理數據
s_listen_event在大的功能上處理客戶端鏈接(Ril-JAVA層發起的connect),並創建s_commands_event去處理Socket鏈接發來的Ril命令。ProcessCommandBufer實際上包含了Ril指令的下行過程。
RIL_JAVA傳遞的命令格式:Parcel , 由命令號,令牌,內容組成。RIL_JAVA到達RIL_C時轉爲構建本地RequestInfo,並將被翻譯成具體的AT指令。因爲每條AT命令的參數 是不一樣的,因此對不一樣的AT指令,有不一樣的轉換函數,在此Android設計在這裏作了一個抽象,作了一個分發框架,經過命令號,利用sCommand數 組,得到該命令的處理函數。
sComand[]={
<...>
}
sComand 存在於Ril_command.h中。
&sComand[]=
<
{RIL_REQUEST_GET_IMEI, dispatchVoid, responseString},
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
{….}
>
dispatchXxx函數通常都放在在Reference-ril.c中,Reference-ril.c這個就是咱們須要根據不一樣的Modem來修改的文件。
send_at_command是同步的,命令發送後,send_at_command將等待在s_commandcond,直到有sp_response->finalResponse。
Read loop是解決的問題是:解析從Modem發過來的迴應。若是遇到URC則經過handleUnsolicited上報的RIL_JAVA。若是是命令的應答,則經過handleFinalResponse通知send_at_command有應答結果。
對於URC,Rild一樣使用一個抽象數組@Ril.CPP.
static UnsolResponseInfo s_unsolResponses[] = {
#include "ril_unsol_commands.h"
};
並利用RIL_onUnsolicitedResponse將URC向上層發送。
RIL-Java在本質上就是一個RIL代理,起到一個轉發的做用,是Android Java概念空間中的電話系統的起點。在RIL-D的分析中,咱們知道RILD創建了一個偵聽套接口,等待RIL-Java的鏈接。一旦鏈接成 功,RIL-JAVA就可發起一個請求,並等待應答,並將結構發送到目標處理對象。在RIL-Java中,這個請求稱爲RILRequest。爲了直觀起 見,我仍是不厭其煩的給出RIL-Java的框架圖。
RIL-Java的大框架包含了四個方面:
Receiver,Sender,CommandInterface,異步通知機制
在ril.java源代碼中,咱們能夠看到RIL-JAVA對象提供了以下的Command Interface:
…
getlccCardStatus
getCurrrentCalls
dial
acceptCall
rejectCall
sendDTMF
sendSMS
setupDataCall
setRadioPower
…
爲何要定義這些接口呢?這函數接口不是憑空捏造出來的,這些都是電話的基本功能的描述,是對Modem AT指令的提煉抽象。大多數Modem都是根據通信協議提供接口,咱們若是不熟悉通信協議,請參閱3GPP的相關文檔,以及本身使用的Modem的SPEC說明。
V.25ter AT Commands
3GPP 07.07 AT Comamnds-General commands
3GPP 07.07 AT Comamnds-Call Control commans
3GPP 07.07 AT Comamnds-Network Service related commands
3GPP 07.07 AT Comamnds-MT control and status command
3GPP 07.07 AT Comamnds-GPRS Commands
3GPP 07.07 Mobile Termination Errors
3GPP 07.05 SMS AT Commands
Receiver 鏈接到RILD的服務套接口,接收讀取RILD傳遞過來的Response Parcel。Response分爲兩種類型,一種是URC,一種是命令應答。對於URC將會直接分發到通知註冊表中的Handler。而命令應答則經過 Receiver的異步通知機制傳遞到命令的發送者進行相應處理。
Sender應該分爲兩部分架構,
上層函數調用Command Interface將請求消息發送到Sender的架構。
Sender接收到EVENT_SEND消息後,將請求發送到RILD的架構。
對於異步應答來說,命令的發起者發送後,並不等待應答就返回,應答的迴應是異步的,處理結果經過消息的方式返回。站在設計者的角度思考如何設計合適的框架 來完成異步通信的功能呢?對於異步系統咱們首先應該考慮的是如何標識命令和結果,讓命令和結果有一個對應關係,還有命令沒有響應了,如何管理命令超時?讓 咱們來看看Android設計者如何完成這些工做。
Android設計者利用了Result Message 和RILRequest對象來完成Request和Result的對 應對於關係。在上層作調用的時候生成Result Message對象傳遞到ril_java,並在Modem有應答後,經過Result Message對象帶回結果。如何保證該應答是該RILRequest的呢?Android設計者還提供了一個Token(令牌)的概念。在源代碼中 RILRequest的mSerail就用做了Token。Token用來惟一標識每次發送的請求,而且Token將被傳遞到RILD,RILD在組裝應 答是將Token寫入,並傳回到ril-java,ril-java根據該Token找到相應的Request對象。
協議的真正實現是在rild中,RIL-JAVA更多的是一個抽象和代理,咱們在研究源代碼的過程當中就會體會到到RIL-JAVA中的命令函數都有一個共同的框架。
SendXxxCmd(傳入參數Data,傳出參數result){
組合RILRequest(請求號,result,mSerail)
Data->RR
send(RILRequest): Message
}
1)RILRequest
請求號:
request 將傳遞到RILD用以標識命令,request表明某個功能。例如撥叫的request號爲:RIL_REQUEST_DIAL。在 libs/telephony/ril_commands.h有定義。RILRequest.obtain@RILRequest根據命令請求號,傳入參 數Result Message,mSerail構造了一個RILRequest。Result Message將帶回應答信息回到命令的發起者。
mSerail:
Android使用了一個RILRequest對象池來管理Andoird RILRequest。mSerail是一個遞增的變量,用來惟一標識一個RILRequest。在發送時正是用了該變量爲Token,在rild層看到的token就是該mSerail。
EVENT_END:http://zhanghaihua415.blog.163.com/blog/getBlog.do?bid=fks_087067086082088067082094094068072087081069085080087065080081
EVENT_END@handleMessage@RILSender@RIL.java
2)發送步驟:
第一步:
生成RILRequest,此時將生成m_Serial(請求的Token)並將請求號,數據,及其Result Message 對象填入到RILRequest中
第二步:
使用send將RILRequest打包到EVENT_SEND消息中發送到到RIL Sender Handler,
第三步:
RilSender 接收到EVENT_SEND消息,將RILRequest經過套接口發送到RILD,同時將RILRequest保存在mRequest中以便應答消息的返回。
(4.2) 接收模式
第一步:分析接收到的Parcel,根據類型不一樣進行處理。
第二步:根據數據中的Token(mSerail),反查mRequest,找到對應的請求信息。
第三步:將是數據轉換成結果數據。
第四步:將結果放在RequestMessage中發回到請求的發起者。
通話鏈接管理
GSMCallTracker在本質上是一個Handler。
GSMCallTracker是Android的通話管理層。GSMCallTracker創建了ConnectionList來管理現行的通話鏈接,並向上層提供電話調用接口。
在 GSMCallTracker中維護着通話列表:connections。順序記錄了正鏈接上的通話,這些通話包 括:ACTIVE,DIALING,ALERTING,HOLDING,INCOMING,WAITING等狀態的鏈接。GSMCallTracker將 這些鏈接分爲了三類別進行管理:
RingingCall: INCOMING ,WAITING
ForegourndCall: ACTIVE, DIALING ,ALERTING
BackgroundCall: HOLDING
上層函數經過getRingCall(),getForegrouandCall()等來得到電話系統中特定通話鏈接。
爲了管理電話狀態,GSMCallTracker在構造時就將本身登記到了電話狀態變化通知表中。RIL-Java一收到電話狀態變化的通知,就會使用EVENT_CALL_STATE_CHANGE通知到GSMCallTacker
在通常的實現中,咱們的通話Call Table是經過AT+CLCC查詢到的,CPI能夠通知到電話的改變,可是CPI在各個Modem的實現中差異比較大,因此參考設計都沒有用到CPI這 樣的電話鏈接改變通知,而是使用最爲傳統的CLCC查詢CALL TABLE。在GSMTracker中使用connections來管理Android電話系統中的通話鏈接。每次電話狀態發生變化是GSMTracker就會使用CLCC查詢來更新connections內容,若是內容有發生變化,則向上層發起電話狀態改變的通知。
在RIL-JAVA中涉及到CurrentCallList查詢的有如下幾個操做:
(1)hangup
(2)dial
(3)acceptCall
(4)rejectCall
在GSMcallTracker在發起這些調用的時候都有一個共同的ResultMessage構造函數:obtainCompleteMessage()。obtainCompleteMessage()其實是調用:
obtainCompleteMessage(EVENT_OPERATION_COMPLETE)
這 就意味着在這些電話操做後,GSMCallTracker會收到EVENT_OPERATION_COMPLETE消息,因而咱們將目光轉移到 handleMessage()@GSMCallTracker的EVENT_OPERATION_COMPLETE事件處 理:operationComplete@GSMCallTracker。
operationComplete() 操做會使用cm.getCurrentCalls(lastRelevantPoll)調用,向RILD發起 RIL_REQUEST_GET_CURRENT_CALLS調用,這個最終就是向Modem發起AT+CLCC,獲取到真正的電話列表。
(1)在RILD中,收到URC消息:
+CRING
RING
NO CARRIER
+CCWA
將會使用RIL_onUnsolicitedResponse( RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED),主動向ril-java上報RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息。
(2) 在處理requestCurrentCalls時,使用CLCC查詢通話鏈接(CALL TABLE)後,如何發現有call Table不爲空則開啓一個定時器,主動上報RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息,直到沒有電話鏈接爲止。
在 RIL-Java層收到RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED這個URC,並利用 mCallStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null))來通知電話狀態的變化,此時GSMTracker會接收到EVENT_CALL_STATE_CHANGE消息,並使用
pollCallsWhenSafe()-> cm.getCurrentCalls(lastRelevantPoll);
來發起查詢,並更新JAVA層的電話列表。
首先咱們來看看是什麼引發了handlePollCalls的調用。
上面的1,2分析了,Android電話系統中全部引發電話鏈接列表更新的條件及其處理。他們共同的調用了cm.getCurrentCalls(lastRelevantPoll) 來完成電話列表的獲取。
lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT)
咱們這裏就從能夠看到獲取到的電話列表Result使用handlePollCalls進行了處理。Result其實是一個DriverCall列表,handlePollCalls的工做就是將當前電話列表與RIL-Java的電話列表對比,使用DriverCall列表更新CallTracker的電話列表
想要得到成功,首先要本身相信本身,再者要贏得周圍朋友的信任!