9.3.6 Rild实例分析
最后更新于:2022-04-02 05:53:42
其实,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的精髓。
';