其实,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 本章小结