其實,Rild沒什么難度,相信見識過Audio和Surface系統的讀者都會有同感。但Java層的Phone應用及相關的Telephony模塊卻相當復雜,這里不去討論Phone的實現,而是通過實例來分析一個電話是如何撥打出去的。這個例子和Rild有關的東西比較簡單,但在分析代碼的路途上,讀者可以領略到Java層Phone代碼的復雜。
1. 創建Phone
Android支持GSM和CDMA兩種Phone,到底創建哪種Phone呢?來看PhoneApp.java是怎么做的:
**PhoneApp.java**
~~~
public void onCreate() {
......
if(phone == null) {
//創建一個Phone,這里使用了設計模式中的Factory(工廠)模式
PhoneFactory.makeDefaultPhones(this);
phone = PhoneFactory.getDefaultPhone();
......
}
~~~
工廠模式的好處在于,將Phone(例如代碼中的GSMPhone或CDMAPhone)創建的具體復雜過程屏蔽起來了,因為用戶只關心工廠的產出物Phone,而不關心創建過程。通過工廠模式可降低使用者和創建者代碼之間的耦合性,即使以后增加TDPhone,使用者也不需要修改太多的代碼。
下面來看這個Phone工廠:
**PhoneFactory.java**
~~~
public static void makeDefaultPhones(Context context){
makeDefaultPhone(context);//調用makeDefaultPhone函數,直接去看看
}
public static void makeDefaultPhone(Contextcontext) {
synchronized(Phone.class) {
......
//根據系統設置獲取通信網絡的模式
intnetworkMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.PREFERRED_NETWORK_MODE,preferredNetworkMode);
intcdmaSubscription =
Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION,
preferredCdmaSubscription);
//RIL這個對象就是rild socket的客戶端,AT命令由它發送給Rild
sCommandsInterface= new RIL(context, networkMode,cdmaSubscription);
int phoneType =getPhoneType(networkMode);
if(phoneType == Phone.PHONE_TYPE_GSM) {
//先創建GSMPhone,然后創建PhoneProxy,這里使用了設計模式中的Proxy模式
sProxyPhone = new PhoneProxy(newGSMPhone(context,
sCommandsInterface,sPhoneNotifier));
}else if (phoneType == Phone.PHONE_TYPE_CDMA) {
//創建CDMAPhone
sProxyPhone = new PhoneProxy(new CDMAPhone(context,
sCommandsInterface,sPhoneNotifier));
}
sMadeDefaults = true;
}
}
}
~~~
假設創建的是GSMPhone,makeDefaultPhones函數將返回PhoneProxy對象,不過這是一個代理Phone,具體工作還是會由GSMPhone完成。
Phone創建完后,就要撥號了。
2. Dial撥號
Phone應用提供了一個PhoneUtils類,最終的撥號是由它完成的:
**PhoneUtils.java**
~~~
static int placeCall(Phone phone, String number,Uri contactRef) {
int status = CALL_STATUS_DIALED;
try {
//調用Phone的dial函數,這個Phone的真實類型是PhoneProxy,number就是電話號碼
Connectioncn = phone.dial(number);
......
}
......
}
~~~
前面說過,PhoneProxy代理的對象是GSMPhone,直接去看它的dial函數:
**GSMPhone.java**
~~~
public Connection
dial(String dialString) throws CallStateException {
returndial(dialString, null);//調用另外一個dial函數
}
public Connection dial (String dialString,UUSInfo uusInfo)
throws CallStateException {
StringnewDialString = PhoneNumberUtils.stripSeparators(dialString);
if (mmi== null) {
//mCT是GsmCallTrack類型,調用它的dial函數。
returnmCT.dial(newDialString, uusInfo);
} elseif (mmi.isTemporaryModeCLIR()) {
returnmCT.dial(mmi.dialingNumber, mmi.getCLIRMode(), uusInfo);
}
......
}
~~~
又調用GSMCallTrack的dial函數,它的代碼如下所示:
**GSMCallTrack.java**
~~~
Connection dial (String dialString, intclirMode, UUSInfo uusInfo)
throws CallStateException {
......
//構造一個GsmConnection對象
pendingMO = newGsmConnection(phone.getContext(), dialString,
this, foregroundCall);
hangupPendingMO = false;
/*
cm對象的真實類型就是我們前面提到的RIL類,它實現了CommandInterface。
下面將調用它的dial函數。
*/
cm.dial(pendingMO.address, clirMode, uusInfo,
obtainCompleteMessage());
......
returnpendingMO;
}
~~~
3. Java層RIL類的分析
Phone應用是不是很復雜?從創建Phone開始,頗費周折才來到了Java層的RIL類,RIL將是Rild中rild socket的唯一客戶端。下面來認識一下RIL。
(1)RIL的架構
RIL的構造函數的代碼如下所示。
**RIL.java**
~~~
public RIL(Context context, int networkMode, intcdmaSubscription) {
super(context);
mCdmaSubscription =cdmaSubscription;
mNetworkMode = networkMode;
//創建一個HandlerThread,從名字上看它是用來發送消息的
mSenderThread = new HandlerThread("RILSender");
mSenderThread.start();
Looper looper = mSenderThread.getLooper();
mSender = new RILSender(looper);
mReceiver = new RILReceiver();
//創建一個RILReceiver線程,從名字上看它是用來接收消息的
mReceiverThread = new Thread(mReceiver, "RILReceiver");
mReceiverThread.start();
......
}
~~~
那么和Rild中rild socket通信的socket是在哪創建的呢?答案是在接收線程中,其代碼:
**RIL.java**
~~~
class RILReceiver implements Runnable {
byte[] buffer;
......
public void
run() {
int retryCount = 0;
try {for (;;) {
LocalSocket s = null;
LocalSocketAddress l;
try {
s = new LocalSocket();
l = newLocalSocketAddress(SOCKET_NAME_RIL,
LocalSocketAddress.Namespace.RESERVED);
//和Rild進行連接
s.connect(l);
......
}
mSocket = s;
int length = 0;
try {
InputStream is =mSocket.getInputStream();
for (;;) {
Parcel p;
//讀數據
length = readRilMessage(is,buffer);
//解析數據
p = Parcel.obtain();
p.unmarshall(buffer, 0,length);
p.setDataPosition(0);
//處理請求,以后再看
processResponse(p);
p.recycle();
}
}
......
}
~~~
從上面代碼中可知,RIL封裝了兩個線程:
- mSenderThread,用來向Rild發送消息。
- mReceiverThread,用來從Rild中接收消息。
待RIL創建后,dail函數該干什么呢?
(2)發送dail請求
dial的處理過程,其代碼如下所示:
**RIL.java**
~~~
public void dial(String address, int clirMode,UUSInfo uusInfo, Message result) {
//創建一個Java層的RIL請求包
RILRequest rr =RILRequest.obtain(RIL_REQUEST_DIAL, result);
rr.mp.writeString(address);
rr.mp.writeInt(clirMode);
rr.mp.writeInt(0);
if(uusInfo == null) {
rr.mp.writeInt(0); // UUS information is absent
}else {
rr.mp.writeInt(1); // UUS information is present
rr.mp.writeInt(uusInfo.getType());
rr.mp.writeInt(uusInfo.getDcs());
rr.mp.writeByteArray(uusInfo.getUserData());
}
//發送數據
send(rr);
}
private void send(RILRequest rr) {
Messagemsg;
//發送EVENT_SEND消息,由mSender這個Handler處理
msg= mSender.obtainMessage(EVENT_SEND, rr);
acquireWakeLock();
msg.sendToTarget();//由發送線程處理
}
~~~
下面看handleMessage函數:
**RIL.java**
~~~
public void handleMessage(Message msg) {
RILRequest rr = (RILRequest)(msg.obj);//請求消息
RILRequestreq = null;
......
switch (msg.what) {
caseEVENT_SEND:
booleanalreadySubtracted = false;
try{
LocalSocket s;
s = mSocket; //這個mSocket就是和Rild通信的socket
/*
執行異步請求/處理時,請求方需要將請求包保存起來,待收到完成通知后再從請求隊列
中找到對應的那個請求包并做后續處理。請求包一般會保存請求時的上下文信息。
以酒店的Morning Call服務為例。假設預約了7、8、9點的服務,那么當7點鐘
接到電話時,一看表便知道是7點的那個請求完成了,而不是8點或9點的請求完成了。
這個7便是請求號的標示,而且完成通知必須回傳這個請求號。至于上下文信息,則
保存在請求包中。例如酒店會在電話中通知說7點鐘要開一個會,這個開會的信息是
預約服務的時候由你提供給酒店的。
保存請求包是異步請求/處理或異步I/O中常見的做法,不過這種做法有一個
很明顯的缺點,就是當請求量比較大的時候,會占用很多內存來保存請求包信息。
*/
synchronized (mRequestsList) {
mRequestsList.add(rr);
}
byte[] data;
data = rr.mp.marshall();
rr.mp.recycle();
rr.mp = null;
......
s.getOutputStream().write(dataLength);
s.getOutputStream().write(data); //發送數據
}
......
}
~~~
至止,應用層已經通過RIL對象將請求數據發送了出去。由于是異步模式,請求數據發送出去后應用層就直接返回了,而且目前還不知道處理結果。那么Rild是如何處理這個請求的呢?
4. Rild處理請求的分析
根據前面對Rild的分析可知,當收到客戶端的數據時會由eventLoop調用對應的任務處理函數進行處理,而這個函數就是processCommandsCallback。看它的代碼:
(1)Rild接收請求
Rild接收請求的代碼如下所示:
**Ril.cpp**
~~~
static void processCommandsCallback(int fd,short flags, void *param) {
RecordStream *p_rs;
void*p_record;
size_trecordlen;
intret;
//RecordStream為processCommandsCallback的參數,里面維護了一個接收緩沖區并
//有對應的緩沖讀寫位置控制
p_rs =(RecordStream *)param;
for(;;) {
/*
下面這個函數將從socket中read數據到緩沖區,并從緩沖區中解析命令。
注意,該緩沖區可能累積了多條命令,也就是說,客戶端可能發送了多個命令,而
Rild通過一次read就全部接收到了。這個特性是由TCP的流屬性決定的。
所以這里有一個for循環來接收和解析命令。
*/
ret = record_stream_get_next(p_rs, &p_record, &recordlen);
if(ret == 0 && p_record == NULL) {
/* end-of-stream */
break;
}else if (ret < 0) {
break;
}else if (ret == 0) {
//處理一條命令
processCommandBuffer(p_record, recordlen);
}
}
if(ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
......//出錯處理,例如socket read出錯
}
}
~~~
每解析出一條命令,就調用processCommandBuffer函數進行處理,看這個函數:
**Ril.cpp**
~~~
static int processCommandBuffer(void *buffer,size_t buflen) {
Parcelp;
status_t status;
int32_t request;
int32_t token;
RequestInfo *pRI;
intret;
p.setData((uint8_t *) buffer, buflen);
status= p.readInt32(&request);
status= p.readInt32 (&token);
......
//s_commands定義了Rild支持的所有命令及其對應的處理函數
if(request < 1 || request >= (int32_t)NUM_ELEMS(s_commands)) {
......
return 0;
}
//Rild內部處理也是采用的異步模式,所以它也會保存請求,又分配一次內存。
pRI =(RequestInfo *)calloc(1, sizeof(RequestInfo));
pRI->token= token;
//s_commands是什么?
pRI->pCI = &(s_commands[request]);
//請求信息保存在一個單向鏈表中。
ret =pthread_mutex_lock(&s_pendingRequestsMutex);
pRI->p_next = s_pendingRequests;//p_next指向鏈表的后繼結點
s_pendingRequests = pRI;
ret =pthread_mutex_unlock(&s_pendingRequestsMutex);
//調用對應的處理函數
pRI->pCI->dispatchFunction(p, pRI);
return0;
}
~~~
上面的代碼中,出現了一個s_commands數組,它保存了一些CommandInfo結構,這個結構封裝了Rild對AT指令的處理函數。另外Rild還定義了一個s_unsolResponses數組,它封裝了unsolicited Response對應的一些處理函數。這兩個數組,如下所示:
**Ril.cpp**
~~~
typedef struct {//先看看CommandInfo的定義
intrequestNumber; //請求號,一個請求對應一個請求號
//請求處理函數
void(*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
//結果處理函數
int(*responseFunction) (Parcel &p, void *response, size_tresponselen);
} CommandInfo;
//下面是s_commands的定義
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
//下面是s_unsolResponses的定義
static UnsolResponseInfo s_unsolResponses[] = {
#include "ril_unsol_commands.h" //這個頭文件讀者可以自己去看看
};
~~~
再來看ril_commands.h的定義:
**ril_commands.h**
~~~
{0, NULL, NULL}, //除了第一條外,一共定義了103條CommandInfo
{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid,responseSimStatus},
......
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},//打電話的處理
......
{RIL_REQUEST_SEND_SMS, dispatchStrings,responseSMS}, //發短信的處理
......
~~~
根據上面的內容可知,在Rild中打電話的處理函數是dispatchDial,它的結果處理函數是responseVoid。
(2)Rild處理請求
Rild處理請求的代碼如下所示:
**Ril.c**
~~~
static void dispatchDial (Parcel &p,RequestInfo *pRI) {
RIL_Dial dial; //創建一個RIL_Dial對象,它存儲打電話時所需要的一些參數。
RIL_UUS_Info uusInfo;
int32_t sizeOfDial;
int32_tt;
int32_t uusPresent;
status_t status;
memset(&dial, 0, sizeof(dial));
dial.address = strdupReadString(p);
status= p.readInt32(&t);
dial.clir = (int)t;
...... //中間過程我們略去
//調用RIL_RadioFunctions的onRequest函數,也就是向RefRil庫發送一個請求。
s_callbacks.onRequest(pRI->pCI->requestNumber, &dial,sizeOfDial, pRI);
......
return;
}
~~~
下面去RefRil庫,看這個onRequest的處理:
**Reference_Ril.c**
~~~
static void onRequest (int request, void *data,size_t datalen, RIL_Token t)
{
ATResponse *p_response;
interr;
......
switch(request) {
......
case RIL_REQUEST_DIAL: //打電話處理
requestDial(data, datalen, t);
break;
......
caseRIL_REQUEST_SEND_SMS: //發短信處理
requestSendSMS(data, datalen, t);
break;
default:
RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
break;
}
}
//我們看看requestDial函數
static void requestDial(void *data, size_tdatalen, RIL_Token t)
{
RIL_Dial*p_dial;
char*cmd;
constchar *clir;
intret;
p_dial= (RIL_Dial *)data;
......
//at_send_command將往串口設備發送這條AT指令
ret =at_send_command(cmd, NULL);
free(cmd);
/*
對于dail請求,把數據發送給串口就算完成了,所以dial發送完數據后直接調用
RIL_onRequestComplete函數來通知請求處理的結果。而有一些請求需要先由
AT模塊的readLoop線程從串口中讀取BP的處理結果后再行通知。
*/
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
}
~~~
看RIL_onRequestComplete函數,這個函數的實現由RIL_Env提供。
**Ril.c**
~~~
extern "C" void
RIL_onRequestComplete(RIL_Token t, RIL_Errno e,void *response,
size_t responselen) {
RequestInfo *pRI;
intret;
size_terrorOffset;
pRI= (RequestInfo *)t;
//由于已經收到了請求的處理結果,這表明該請求已經完成,所以需要從請求隊列中去掉這個請求。
if(!checkAndDequeueRequestInfo(pRI)) {
}
......
if(pRI->cancelled == 0) {
Parcel p;
p.writeInt32 (RESPONSE_SOLICITED);
p.writeInt32 (pRI->token);
errorOffset = p.dataPosition();
p.writeInt32 (e);
if(response != NULL) {
//dial請求的responseFunction函數是responseVoid,讀者可以看這個函數
ret =pRI->pCI->responseFunction(p, response, responselen);
if (ret != 0) {
p.setDataPosition(errorOffset);
p.writeInt32 (ret);
}
}
......
sendResponse(p);//將結果發送給Java的RIL
}
done:
free(pRI);
}
~~~
Rild內部也采用了異步請求/處理的結構,這樣做有它的道理,因為有一些請求執行的時間較長,例如在信號不好的地方搜索網絡信號往往會花費較長的時間。采用異步的方式,能避免工作線程阻塞在具體的請求函數中,從而騰出手來做一些別的工作。
(3)Java RIL處理完成的通知
Rild將dial請求的結果,通過socket發送給Java中的RIL對象。前面說過,RIL中有一個接收線程,它收到數據后會調用processResponse函數進行處理,看這個函數:
**RIL.java**
~~~
private void processResponse (Parcel p) {
int type;
type = p.readInt();
if(type == RESPONSE_UNSOLICITED) {
processUnsolicited (p);
}else if (type == RESPONSE_SOLICITED) {
processSolicited (p); //dial是應答式的,所以走這個分支
}
releaseWakeLockIfDone();
}
private void processSolicited (Parcel p) {
int serial, error;
boolean found = false;
serial= p.readInt();
error= p.readInt();
RILRequestrr;
//根據完成通知中的請求包編號從請求隊列中去掉對應的請求,以釋放內存
rr= findAndRemoveRequestFromList(serial);
Objectret = null;
if(error == 0 || p.dataAvail() > 0) {
try {
switch (rr.mRequest) {
......
//調用responseVoid函數處理結果
caseRIL_REQUEST_DIAL: ret = responseVoid(p);break;
......
if (rr.mResult != null) {
/*
RILReceiver線程將處理結果投遞到一個Handler中,這個Handler屬于
另外一個線程,也就是處理結果最終將交給另外一個線程做后續處理,例如切換界面顯示等工作,
具體內容就不再詳述了。為什么要投遞到別的線程進行處理呢?因為RILReceiver
負責從Rild中接收數據,而這個工作是比較關鍵的,所以這個線程除了接收數據外,最好
不要再做其他的工作了。
*/
AsyncResult.forMessage(rr.mResult, ret,null);
rr.mResult.sendToTarget();
}
rr.release();
}
~~~
實例分析就到此為止。相信讀者已經掌握了Rild的精髓。
- 前言
- 第1章 閱讀前的準備工作
- 1.1 系統架構
- 1.1.1 Android系統架構
- 1.1.2 本書的架構
- 1.2 搭建開發環境
- 1.2.1 下載源碼
- 1.2.2 編譯源碼
- 1.3 工具介紹
- 1.3.1 Source Insight介紹
- 1.3.2 Busybox的使用
- 1.4 本章小結
- 第2章 深入理解JNI
- 2.1 JNI概述
- 2.2 學習JNI的實例:MediaScanner
- 2.3 Java層的MediaScanner分析
- 2.3.1 加載JNI庫
- 2.3.2 Java的native函數和總結
- 2.4 JNI層MediaScanner的分析
- 2.4.1 注冊JNI函數
- 2.4.2 數據類型轉換
- 2.4.3 JNIEnv介紹
- 2.4.4 通過JNIEnv操作jobject
- 2.4.5 jstring介紹
- 2.4.6 JNI類型簽名介紹
- 2.4.7 垃圾回收
- 2.4.8 JNI中的異常處理
- 2.5 本章小結
- 第3章 深入理解init
- 3.1 概述
- 3.2 init分析
- 3.2.1 解析配置文件
- 3.2.2 解析service
- 3.2.3 init控制service
- 3.2.4 屬性服務
- 3.3 本章小結
- 第4章 深入理解zygote
- 4.1 概述
- 4.2 zygote分析
- 4.2.1 AppRuntime分析
- 4.2.2 Welcome to Java World
- 4.2.3 關于zygote的總結
- 4.3 SystemServer分析
- 4.3.1 SystemServer的誕生
- 4.3.2 SystemServer的重要使命
- 4.3.3 關于 SystemServer的總結
- 4.4 zygote的分裂
- 4.4.1 ActivityManagerService發送請求
- 4.4.2 有求必應之響應請求
- 4.4.3 關于zygote分裂的總結
- 4.5 拓展思考
- 4.5.1 虛擬機heapsize的限制
- 4.5.2 開機速度優化
- 4.5.3 Watchdog分析
- 4.6 本章小結
- 第5章 深入理解常見類
- 5.1 概述
- 5.2 以“三板斧”揭秘RefBase、sp和wp
- 5.2.1 第一板斧--初識影子對象
- 5.2.2 第二板斧--由弱生強
- 5.2.3 第三板斧--破解生死魔咒
- 5.2.4 輕量級的引用計數控制類LightRefBase
- 5.2.5 題外話-三板斧的來歷
- 5.3 Thread類及常用同步類分析
- 5.3.1 一個變量引發的思考
- 5.3.2 常用同步類
- 5.4 Looper和Handler類分析
- 5.4.1 Looper類分析
- 5.4.2 Handler分析
- 5.4.3 Looper和Handler的同步關系
- 5.4.4 HandlerThread介紹
- 5.5 本章小結
- 第6章 深入理解Binder
- 6.1 概述
- 6.2 庖丁解MediaServer
- 6.2.1 MediaServer的入口函數
- 6.2.2 獨一無二的ProcessState
- 6.2.3 時空穿越魔術-defaultServiceManager
- 6.2.4 注冊MediaPlayerService
- 6.2.5 秋風掃落葉-StartThread Pool和join Thread Pool分析
- 6.2.6 你徹底明白了嗎
- 6.3 服務總管ServiceManager
- 6.3.1 ServiceManager的原理
- 6.3.2 服務的注冊
- 6.3.3 ServiceManager存在的意義
- 6.4 MediaPlayerService和它的Client
- 6.4.1 查詢ServiceManager
- 6.4.2 子承父業
- 6.5 拓展思考
- 6.5.1 Binder和線程的關系
- 6.5.2 有人情味的訃告
- 6.5.3 匿名Service
- 6.6 學以致用
- 6.6.1 純Native的Service
- 6.6.2 扶得起的“阿斗”(aidl)
- 6.7 本章小結
- 第7章 深入理解Audio系統
- 7.1 概述
- 7.2 AudioTrack的破解
- 7.2.1 用例介紹
- 7.2.2 AudioTrack(Java空間)分析
- 7.2.3 AudioTrack(Native空間)分析
- 7.2.4 關于AudioTrack的總結
- 7.3 AudioFlinger的破解
- 7.3.1 AudioFlinger的誕生
- 7.3.2 通過流程分析AudioFlinger
- 7.3.3 audio_track_cblk_t分析
- 7.3.4 關于AudioFlinger的總結
- 7.4 AudioPolicyService的破解
- 7.4.1 AudioPolicyService的創建
- 7.4.2 重回AudioTrack
- 7.4.3 聲音路由切換實例分析
- 7.4.4 關于AudioPolicy的總結
- 7.5 拓展思考
- 7.5.1 DuplicatingThread破解
- 7.5.2 題外話
- 7.6 本章小結
- 第8章 深入理解Surface系統
- 8.1 概述
- 8.2 一個Activity的顯示
- 8.2.1 Activity的創建
- 8.2.2 Activity的UI繪制
- 8.2.3 關于Activity的總結
- 8.3 初識Surface
- 8.3.1 和Surface有關的流程總結
- 8.3.2 Surface之乾坤大挪移
- 8.3.3 乾坤大挪移的JNI層分析
- 8.3.4 Surface和畫圖
- 8.3.5 初識Surface小結
- 8.4 深入分析Surface
- 8.4.1 與Surface相關的基礎知識介紹
- 8.4.2 SurfaceComposerClient分析
- 8.4.3 SurfaceControl分析
- 8.4.4 writeToParcel和Surface對象的創建
- 8.4.5 lockCanvas和unlockCanvasAndPost分析
- 8.4.6 GraphicBuffer介紹
- 8.4.7 深入分析Surface的總結
- 8.5 SurfaceFlinger分析
- 8.5.1 SurfaceFlinger的誕生
- 8.5.2 SF工作線程分析
- 8.5.3 Transaction分析
- 8.5.4 關于SurfaceFlinger的總結
- 8.6 拓展思考
- 8.6.1 Surface系統的CB對象分析
- 8.6.2 ViewRoot的你問我答
- 8.6.3 LayerBuffer分析
- 8.7 本章小結
- 第9章 深入理解Vold和Rild
- 9.1 概述
- 9.2 Vold的原理與機制分析
- 9.2.1 Netlink和Uevent介紹
- 9.2.2 初識Vold
- 9.2.3 NetlinkManager模塊分析
- 9.2.4 VolumeManager模塊分析
- 9.2.5 CommandListener模塊分析
- 9.2.6 Vold實例分析
- 9.2.7 關于Vold的總結
- 9.3 Rild的原理與機制分析
- 9.3.1 初識Rild
- 9.3.2 RIL_startEventLoop分析
- 9.3.3 RIL_Init分析
- 9.3.4 RIL_register分析
- 9.3.5 關于Rild main函數的總結
- 9.3.6 Rild實例分析
- 9.3.7 關于Rild的總結
- 9.4 拓展思考
- 9.4.1 嵌入式系統的存儲知識介紹
- 9.4.2 Rild和Phone的改進探討
- 9.5 本章小結
- 第10章 深入理解MediaScanner
- 10.1 概述
- 10.2 android.process.media分析
- 10.2.1 MSR模塊分析
- 10.2.2 MSS模塊分析
- 10.2.3 android.process.media媒體掃描工作的流程總結
- 10.3 MediaScanner分析
- 10.3.1 Java層分析
- 10.3.2 JNI層分析
- 10.3.3 PVMediaScanner分析
- 10.3.4 關于MediaScanner的總結
- 10.4 拓展思考
- 10.4.1 MediaScannerConnection介紹
- 10.4.2 我問你答
- 10.5 本章小結