1 RIL_J與RIL_C通訊
上層通常要和RILD通訊,是通過Socket,在RIL_JAVA層實現;
沿著這樣代碼流程進行Framework——native:
Phone——RIL_JAVA——>RIL_CPP
那麼可不可以直接和RILD(RIL_CPP)進行通訊呢?
肯定是可以的,因為通訊使用的rild socket,只要通過這個socket就可以和RILD進行通訊 ;
但實際中可靠的使用是不可行的,因為RILD在建立的時候,
設計初始化已經決定了RILD同時所支援的用戶端的數量:
單卡僅支援一個用戶端;
雙卡實現方式代碼提供了兩種方式:
1)雙卡兩個RIL用戶端對應一個RILD服務端,以標籤SUB0和SUB1來區分,RILD中資料流程也對應兩個實體;
2)一個RIL用戶端對應一個RILD服務端,也就是雙卡的話,就會有個多個RILD進程
因Phone進程的特殊性,常駐進程開機啟動會和RILD建立串連,作為RILD用戶端.
所以如果你通過socket與RILD建立串連,就會將原來的Phone與RILD的串連斷開掉;
這樣就可能會造成衝突,產生異常,除非你將內建的Phone刪除掉;
通常第三方的撥號軟體也都是在Phone的基礎上實現的。
因為所有的上層代碼都是通過Framework,再傳遞帶C/C++層進行處理;
之前有一些做法是,從底層將需求發到上層,在通過上層正常的流程去調用,再傳遞到底層,
這本身就不是很合理的,但是卻不得不這樣做;
假如現在有這樣一個需求,在不啟動上層的情況下進行手機的功能測試,或者直接和RILD進行底層通訊
比如網路通訊功能,怎麼做呢?
還得在底層C層直接通過socket與RILD層建立串連,進行通訊;
下面就看看這個實現過程:
2 建立socket並串連
sendAtFd createToRildFd(void){ sendAtFd fd = -1; //建立socket並串連 fd = socket_local_client(SOCKET_NAME_RILD, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); if (fd < 0) { perror ("opening radio socket"); exit(-1); } //儲存和RILD通訊的FD s_send_at_fd = fd; return fd; }
這裡建立通訊端是一個UNIX通訊端,參數與網路通訊端不同,
結構體用sockaddr_un,域參數應該是PF_LOCAL,通訊類型應該是SOCK_STREAM或SOCK_DGRAM
UNIX本地通訊端可以參考下面文檔:
http://zerocool60.blog.163.com/blog/static/35270508200772955536291/
3 向RILD發起串連
sendAtErrnoType setupToRild(void){ sendAtErrnoType result = E_SUCCESS; char recvBuffer[RECV_BUFFER_LEN] = {0}; Parcel recvP; sendAtFd fd = s_send_at_fd; //向RILD發起串連 char* SUB1 = "SUB1"; int res = send(fd,(const void *)SUB1,strlen(SUB1),0); //接收RILD返回,建建立串連 while(1){ sleep(10); //等待接收服務端資料 int recvLen = recv(fd,recvBuffer,RECV_BUFFER_HEAD_LEN,0); if(recvLen ==0) continue; //讀取長度 int messageLength = ((recvBuffer[0] & 0xff) << 24) | ((recvBuffer[1] & 0xff) << 16) | ((recvBuffer[2] & 0xff) << 8) | (recvBuffer[3] & 0xff); ALOGI("sendAt messageLength = %d",messageLength); //讀取內容 recvLen = recv(fd,recvBuffer,messageLength,0); recvP.setData((uint8_t*)recvBuffer, recvLen); //解析資料 int type = recvP.readInt32(); int response = recvP.readInt32(); //type: RESPONSE_UNSOLICITED / RESPONSE_SOLICITED ALOGI("sendAt type = %d",type); //response ID: 指RESPONSE_UNSOLICITED類型的 //對於RESPONSE_SOLICITED的可能就不是這樣的了serial一類的,實際中解析時要區分對待兩種類型 //資料格式可以根據RIL_JAVA參考 ALOGI("sendAt response = %d",response); //資料內容 ALOGI("sendAt recvBuffer = %s",recvBuffer); //已建立串連 退出迴圈 break; } return result; }
同樣需要接收RILD傳遞來的訊息,也需要按照這種格式進行,可以另起一個線程專門來負責接收RILD發送來的訊息。
4 發送資料
sendAtErrnoType sendDataToRild(Parcel &p){ unsigned char* sendData = NULL; uint32 sendDataLen = p.dataSize(); unsigned char dataLength[4]; int32 res = -1; sendAtErrnoType error = E_SUCCESS; sendAtFd fd = s_send_at_fd; // parcel length in big endian 轉化資料長度為byte數組 dataLength[0] = dataLength[1] = 0; dataLength[2] = (unsigned char)((sendDataLen >> 8) & 0xff); dataLength[3] = (unsigned char)((sendDataLen) & 0xff); //擷取待發送資料 sendData = (unsigned char*)malloc(p.dataSize()); memcpy(sendData, p.data(), sendDataLen); //發送資料長度 res = send(fd,(const void *)dataLength,4,0); //發送資料內容 res = send(fd,(const void *)sendData,sendDataLen,0); free(sendData); return error; }
看到RILD接收資料使用Parcel進行相關的解析,
因此資料發送的格式組織依然使用Parcel進行組織;
從上面可以看到資料讀取和資料發送,都是先從資料長度開始,然後資料內容
這就是與RILD socket通訊的資料格式,具體可以參考RILJ發送資料的格式。
與RILD socket通訊的用戶端限制許可權只能為“Radio”,才可以與之進行通訊。
這在RIL_CPP的listenCallback中有做限制。
但是這裡如果將UID改為:setuid(AID_RADIO);時,
發現在建立socket時又會出錯,不知如何解決,就納悶了同樣是radio許可權為什麼,
這裡卻不能開啟rild socket。
僅僅是作為測試,於是使用include $(BUILD_EXECUTABLE)編譯出來一個可執行檔
只能去更改RILD對於許可權的要求。
在向rild發送了資料之後如果,程式立即就退出了,也會報相應的錯誤;
引起服務端報出:ECONNRESET 104錯誤 connection reset by peer對方複位串連;
所以要在函數中做一些延遲,不能立即結束程式運行。
在Android中使用Parcel類等某些類,需要在namespace android {}下才行。
namespace 是c++的一個標識符,表示定義一個全域空間。
android代碼把整個android工程看作一個namespace。
所以要在同一個空間下才能引用。
5 實現程式碼片段
int main(int argc, char *argv[]){ send_at_main_init(); send_at_fight_mode(1); //不能立即結束程式,否則造成ECONNRESET 104錯誤 connection reset by peer //sleep的精度是秒,usleep的精度是微妙 sleep(3); //或者不讓程式退出,進入死迴圈 while(0) { // sleep(UINT32_MAX) seems to return immediately on bionic sleep(0x00ffffff); } return 0;}
下面看幾個具體的資料發送流程 和格式:
6 撥打到電話
void send_at_dispatch_dial(send_at_request_params* handle_params){ Parcel p; status_t status; uint32 ril_request_dial = RIL_REQUEST_DIAL; uint32 serial = 0; const char address[20] = "15816891234"; uint32 clirMode = 0; uint32 uusInfoNone = 0; //打包資料 status = p.writeInt32(ril_request_dial); status = p.writeInt32(serial); //字串寫入跟讀取方式保持一致,參考RILD中的函數 writeStringToParcel(p,address); status = p.writeInt32(clirMode); status = p.writeInt32(uusInfoNone); //發送資料 sendDataToRild(p);}
7 設定Radio狀態
void send_at_dispatch_fight_mode(send_at_request_params* handle_params){ Parcel p; status_t status; atRadioEvent* pRadioEvent = (atRadioEvent*)handle_params->data; //打包資料 status = p.writeInt32(pRadioEvent->ril_request_radio); status = p.writeInt32(pRadioEvent->serial); status = p.writeInt32(pRadioEvent->radio); status = p.writeInt32(pRadioEvent->on); //發送資料 sendDataToRild(p);}
8 編譯一個可執行二進位檔案
產生到目錄:/system/bin/sendAt
adb push到手機/system/bin目錄下,更改許可權即可執行
# For sendAt binary# =======================LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES:= \ sendAt.c \ sendAtUtil.cpp \ sendAtEvent.cpp \ sendAtDispatch.cpp LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder \ libcutils \ libril LOCAL_CFLAGS := \LOCAL_MODULE:= sendAtLOCAL_MODULE_TAGS := optionalinclude $(BUILD_EXECUTABLE)