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:= sendAt LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE)