1. 程式人生 > >Android電話系統之概述篇

Android電話系統之概述篇

首先拋開Android的一切概念來研究一下電話系統的最基本的描述。我們的手機首先用來打電話的,隨後是需要一個電話本,隨後是PIM,隨後是網路應用,隨後是雲端計算,隨後是想我們的手機無所不能,替代PC。但是作為一個電話的基本功能如下:

0)撥叫電話,接聽電話,結束通話電話,傳送簡訊,網路連線,PIM管理

1)由於電話運營商為我們提供了呼叫等待,電話會議等補充業務,所以我們的手機需要管理多路通話,如何管理?

2)來電時,我們要播出來電鈴聲,接通時我們需要切換語音通道,這個又跟多媒體系統打上了交道,例如有耳機插上了,有藍芽耳機連上了,系統該做如何的管理和切換?

3)上網的網路通路建立(例如GSM GPRS),如何PPP連線並連線到LinuxSocket通道上的?系統如何管理資料連線?

4)AP跟Modem通訊時通過AT指令的,如何將AT指令變成一個個具體的操作函式,如何管理Modem發給我們的迴應,AT命令通道,資料通道如何管理?

5)sim卡的電話本如何管理?

上面的關於手機的基本問題,Android電話系統設計者必須要解答的問題。該設計如何的管理框架,提出什麼概念來表達?所以要分析Android的電話部分,還是需要理解電話實現的背景知識,通訊協議,大體框架。

    我們回到電話系統基本構成上,先從整體上去把握一下電話模組的大體框架,先從空中俯瞰。我給出的圖是一般的智慧手機的框架圖,該框架基本能夠概括所有手機電話模組的構成,當然也包括Android的電話系統構成。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

    智慧機架構一般是應用處理器+Modem。應用處理器與Modem的連線使用串列埠或者USB。在一個硬體串列埠通路上實現為了要同時實現資料傳輸並同時實現 控制Modem,就需要實現多路複用協議(GSM TS07.10),在底層我們在多路複用的基礎上虛擬了兩個串列埠,一個用於CMD通道,一個用於DATA通道。電話的所有控制通路都是在這連個通道上。

   RIL,Radio Interface Layer。本層為一個協議轉換層,手機框架需要適應多型別的Modem接入到系統中,而對於不同的Modem有不同的特性,AT指令的格式或者回應有所 不同,但是這種特性在設計應用時不可能完全考慮和相容。所以設計者在設計電話系統時,建立了一個虛擬電話系統,為該虛擬電話系統規定了標準的功能,上層的 電話管理都是建立在這些標準的功能基礎之上。而RIL則是將虛擬電話系統的標準功能轉換成實際的所使用的Modem的AT指令。

Android設計者將電話系統設計成了三部分。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

    Andoird的Phone Service其實是PhoneApp。GSMPhone(CDMAPhone)是Phone Service核心的物件,他包含了如下的相關物件。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

       我們的分析任務就是要把這些物件的相互關係,及其物件間資料傳遞關係弄清楚。首先我們給出以下的Android電話系統的框架,以便對Android電話系統有個概要的認識,然後從資料流的角度,以及物件的引用關係來分析系統。下面是android電話系統整體框架圖。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Android電話系統之-rild

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更多是一個管理框架。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

  

 

    而ril是具體的AT指令合成者和應答解析者。從最基本的功能來講,ril建立了一個偵聽Socket,等待客戶端的連線,然後從該連線上讀取RIL- Java成傳遞來的命令並轉化成AT指令傳送到Modem。並等待Modem的迴應,然後將結果通過套介面傳回到Ril-Java層。下圖是Ril-D的 基本框架:

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

   

 

下面的資料流傳遞描述圖表描述了RIL-JAVA層發出一個電話指令的5 步曲。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

 

在 AT通訊的過程中有兩類響應:一種是請求後給出應答,一種是通知類,即為不請自來的,例如簡訊通知達到,我們稱該類通知為URC。在Rild中URC和一 般的Response是分開處理的,概念上URC由[email protected]處理,而Response由 handleFinalResponse來處理。

1 Event Loop

Rild 管理的真正精髓在ril.cpp,ril_event.cpp中,在研究的過程中,可以看到設計者在抽象上所下的功夫,設計得很優美。Event Loop的基本工作就是等待在事件埠(串列埠,Socket),一旦有資料到達就根據登記的Event回撥函式進行處理。現在來看Ril設計者是如何建立 一套管理框架來完成這些工作的?

1.1 Event物件

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

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

在新增操作中,有兩個動作:

(1) 加入到watch_list

(2) 將控制代碼加入到readFDS事件控制代碼池。

1.2 ril_event_loop()

   我們知道對於Linux裝置來講,我們可以使用select函式等待在FDS上,只要FDS中記錄的裝置有資料到來,select就會設定相應的標誌位並 返回。readFDS記錄了所有的事件相關裝置控制代碼。readFDS中控制代碼是在在AddEvent加入的。所有的事件偵聽都是建立在linux的 select readFDS基礎上。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

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事件,發起事件回撥函式。

1.3 幾個重要的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指令的下行過程。

1.4 下行命令翻譯及其組織@ProcessCommandBuffer

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來修改的檔案。

1.5 send_at_command框架

send_at_command是同步的,命令傳送後,send_at_command將等待在s_commandcond,直到有sp_response->finalResponse。

2 read [email protected]

Read loop是解決的問題是:解析從Modem發過來的迴應。如果遇到URC則通過handleUnsolicited上報的RIL_JAVA。如果是命令的應答,則通過handleFinalResponse通知send_at_command有應答結果。 

 

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

   

 

對於URC,Rild同樣使用一個抽象陣列@Ril.CPP.

static UnsolResponseInfo s_unsolResponses[] = {

#include "ril_unsol_commands.h"

};

並利用RIL_onUnsolicitedResponse將URC向上層傳送。

3 Ril-d的整體資料流及其控制流示意圖

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Android RIL-Java

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

    RIL-Java在本質上就是一個RIL代理,起到一個轉發的作用,是Android Java概念空間中的電話系統的起點。在RIL-D的分析中,我們知道RILD建立了一個偵聽套介面,等待RIL-Java的連線。一旦連線成 功,RIL-JAVA就可發起一個請求,並等待應答,並將結構傳送到目標處理物件。在RIL-Java中,這個請求稱為RILRequest。為了直觀起 見,我還是不厭其煩的給出RIL-Java的框架圖。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

RIL-Java的大框架包含了四個方面:

Receiver,Sender,CommandInterface,非同步通知機制

(1) Command Interface

   在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

(2)Receiver

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Receiver 連線到RILD的服務套介面,接收讀取RILD傳遞過來的Response Parcel。Response分為兩種型別,一種是URC,一種是命令應答。對於URC將會直接分發到通知登錄檔中的Handler。而命令應答則通過 Receiver的非同步通知機制傳遞到命令的傳送者進行相應處理。

(3)Sender

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Sender應該分為兩部分架構,

上層函式呼叫Command Interface將請求訊息傳送到Sender的架構。

Sender接收到EVENT_SEND訊息後,將請求傳送到RILD的架構。

(4)非同步應答框架

    對於非同步應答來講,命令的發起者傳送後,並不等待應答就返回,應答的迴應是非同步的,處理結果通過訊息的方式返回。站在設計者的角度思考如何設計合適的框架 來完成非同步通訊的功能呢?對於非同步系統我們首先應該考慮的是如何標識命令和結果,讓命令和結果有一個對應關係,還有命令沒有響應了,如何管理命令超時?讓 我們來看看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物件。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

(4.1)RIL命令的傳送模式

協議的真正實現是在rild中,RIL-JAVA更多的是一個抽象和代理,我們在研究原始碼的過程中就會體會到到RIL-JAVA中的命令函式都有一個共同的框架。

SendXxxCmd(傳入引數Data,傳出引數result){

組合RILRequest(請求號,result,mSerail)

Data->RR

send(RILRequest): Message

}

1)RILRequest

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

請求號:

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@[email protected]

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格 Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格 

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,根據型別不同進行處理。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

第二步:根據資料中的Token(mSerail),反查mRequest,找到對應的請求資訊。

第三步:將是資料轉換成結果資料。

第四步:將結果放在RequestMessage中發回到請求的發起者。

4.3)詳細的GSMCallTracker,RIL-Java函式對照

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

 

Android電話系統之GSMCallTracker

通話連線管理

GSMCallTracker在本質上是一個Handler。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

GSMCallTracker是Android的通話管理層。GSMCallTracker建立了ConnectionList來管理現行的通話連線,並向上層提供電話呼叫介面。

Android電話系統之概述篇 - zhanghaihua415 - 水落石出的部落格

 

在 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內容,如果內容有發生變化,則向上層發起電話狀態改變的通知。

1 RIL-JAVA中發起電話連線列表操作

在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,獲取到真正的電話列表。

2 在RILD中,引起getCurrentCalls呼叫

(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層的電話列表。

3 handlePollCalls電話列表重新整理

      首先我們來看看是什麼引起了handlePollCalls的呼叫。

     上面的1,2分析了,Android電話系統中所有引起電話連線列表更新的條件及其處理。他們共同的呼叫了cm.getCurrentCalls(lastRelevantPoll) 來完成電話列表的獲取。

lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT)

我們這裡就從可以看到獲取到的電話列表Result使用handlePollCalls進行了處理。Result實際上是一個DriverCall列表,handlePollCalls的工作就是將當前電話列表與RIL-Java的電話列表對比,使用DriverCall列表更新CallTracker的電話列表 

想要獲得成功,首先要自己相信自己,再者要贏得周