9.3.6 Rild实例分析
其实,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(Context context){
synchronized(Phone.class){
……
//根据系统设置获取通信网络的模式。
int networkMode=Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.PREFERRED_NETWORK_MODE,preferredNetworkMode);
int cdmaSubscription=
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(new GSMPhone(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就是电话号码。
Connection cn=phone.dial(number);
……
}
……
}
前面说过,PhoneProxy代理的对象是GSMPhone,直接去看它的dial函数,如下所示:
[—>GSMPhone.java]
public Connection
dial(String dialString)throws CallStateException{
return dial(dialString,null);//调用另外一个dial函数
}
public Connection dial(String dialString,UUSInfo uusInfo)
throws CallStateException{
String newDialString=PhoneNumberUtils.stripSeparators(dialString);
if(mmi==null){
//mCT是GsmCallTrack类型,调用它的dial函数。
return mCT.dial(newDialString,uusInfo);
}else if(mmi.isTemporaryModeCLIR()){
return mCT.dial(mmi.dialingNumber,mmi.getCLIRMode(),uusInfo);
}
……
}
又调用GSMCallTrack的dial函数,它的代码如下所示:
[—>GSMCallTrack.java]
Connection dial(String dialString,int clirMode,UUSInfo uusInfo)
throws CallStateException{
……
//构造一个GsmConnection对象。
pendingMO=new GsmConnection(phone.getContext(),dialString,
this,foregroundCall);
hangupPendingMO=false;
/*
cm对象的真实类型就是我们前面提到的RIL类,它实现了CommandInterface。
下面将调用它的dial函数。
*/
cm.dial(pendingMO.address,clirMode,uusInfo,
obtainCompleteMessage());
……
return pendingMO;
}
3.Java层RIL类的分析
Phone应用是不是很复杂?从创建Phone开始,颇费周折才来到了Java层的RIL类,RIL将是Rild中rild socket的唯一客户端。下面来认识一下RIL。
(1)RIL的架构
RIL的构造函数的代码如下所示。
[—>RIL.java]
public RIL(Context context,int networkMode,int cdmaSubscription){
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=new LocalSocketAddress(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创建后,dial函数该干什么呢?
(2)发送dial请求
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){
Message msg;
//发送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);//请求消息。
RILRequest req=null;
……
switch(msg.what){
case EVENT_SEND:
boolean alreadySubtracted=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_t recordlen;
int ret;
//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){
Parcel p;
status_t status;
int32_t request;
int32_t token;
RequestInfo*pRI;
int ret;
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);
return 0;
}
上面的代码中,出现了一个s_commands数组,它保存了一些CommandInfo结构,这个结构封装了Rild对AT指令的处理函数。另外Rild还定义了一个s_unsolResponses数组,它封装了unsolicited Response对应的一些处理函数。这两个数组如下所示:
[—>Ril.cpp]
typedef struct{//先看看CommandInfo的定义。
int requestNumber;//请求号,一个请求对应一个请求号。
//请求处理函数。
void(dispatchFunction)(Parcel&p,struct RequestInfopRI);
//结果处理函数。
int(responseFunction)(Parcel&p,voidresponse,size_t responselen);
}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_t t;
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;
int err;
……
switch(request){
……
case RIL_REQUEST_DIAL://打电话处理。
requestDial(data,datalen,t);
break;
……
case RIL_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_t datalen,RIL_Token t)
{
RIL_Dial*p_dial;
char*cmd;
const char*clir;
int ret;
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;
int ret;
size_t errorOffset;
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();
RILRequest rr;
//根据完成通知中的请求包编号从请求队列中去掉对应的请求,以释放内存。
rr=findAndRemoveRequestFromList(serial);
Object ret=null;
if(error==0||p.dataAvail()>0){
try{
switch(rr.mRequest){
……
//调用responseVoid函数处理结果。
case RIL_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的精髓。