💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
其实,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的精髓。