前置文章
《HIDL》
前言
在 Android 8.0(不含,下同)之前,Telephony 和 modem 之间一直用 socket 进行连接通信,它是 RILD 。其实通过 socket 连接的两个上下层模块,已经非常的解耦,也具有 HIDL 独立编译的特性,但是应用范围受到限制,socket 通信的速度和接口的定义等不是很理想,没有大范围的应用到各个模块。HIDL 技术的推出,可以替换通过 socket 连接的各个模块,发挥 HIDL 技术的优势。
采用 socket 的 RIL 连接架构通信如下:
从 socket 升级到 HIDL,只需修改 RIL.java 和 ril.cpp 两个地方的接口即可。JAVA 通过绑定式 HAL 直接连接到 HW service。如下图:
下文中,我采用 Android 8.0 之前的版本和 Android 8.0 的版本的差异化通过比较的形式列出来,以便更好的理解 HIDL 的技术。
如下链接一下笔者以往关于 telephony 的文章,供有需要的读者踩一踩
《Android无线电信息管理开篇准备工作》
《初识com.android.phone》
《PhoneInterfaceManager》
《TelephonyTesgistry》
《UICC》
《SubscriptionController》
RIL.java的脱变
初始化
Socket版本
在 Android 8.0 之前的版本,需要初始化 socket,如下:
class RILReceiver implements Runnable {
byte[] buffer;
.....
@Override
public void
run() {
try {for (;;) {
//声明socket对象
LocalSocket s = null;
LocalSocketAddress l;
if (mInstanceId == null || mInstanceId == 0 ) {
//socket server的名字={"rild", "rild2", "rild3"}
rilSocket = SOCKET_NAME_RIL[0];
} else {
.....
try {
//实例化socket对象
s = new LocalSocket();
l = new LocalSocketAddress(rilSocket,
LocalSocketAddress.Namespace.RESERVED);
//创建socket连接
s.connect(l);
} catch (IOException ex){
.....
try {
//获取输入流(输出流这里就不贴出来了)
InputStream is = mSocket.getInputStream();
} catch (java.io.IOException ex) {
.....
}
这个类定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
HIDL版本
Android 8.0 升级到 HIDL,初始化就变成了如下这样子:
protected IRadio getRadioProxy(Message result) {
.....
try {
//通过IRadio的getService()获取HIDL的客户端,参数为mPhoneId,即slot id;因此每个卡槽,对已一个HIDL service。
mRadioProxy = IRadio.getService(HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId]);
if (mRadioProxy != null) {
mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
mRadioProxyCookie.incrementAndGet());
mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
}
} catch (RemoteException | RuntimeException e) {
.....
}
.....
return mRadioProxy;
}
这个方法定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
阶段小结
从初始化的对比上,HIDL 表面上的代码就比 socket 的方式要简洁,容易理解。
发送数据
以拨号为例,从上层向底层发送数据,Android 8.0 之前的版本和 Android 8.0 的版本,socket 和 HIDL 的差异。
Android 8.0 之前的 socket 版本:
@Override
public void
dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
if (!PhoneNumberUtils.isUriNumber(address)) {
//封装待发送的数据,特别注意参数 RIL_REQUEST_DIAL
RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
rr.mParcel.writeString(address);
rr.mParcel.writeInt(clirMode);
.....
//发送,继续往下看这个方法(跳至下一代码片段)
send(rr);
}
.....
}
这个方法定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
private void
send(RILRequest rr) {
Message msg;
.....
//推送到 Sender 队列,我们不管它,继续看message的处理
msg = mSender.obtainMessage(EVENT_SEND, rr);
acquireWakeLock(rr, FOR_WAKELOCK);
msg.sendToTarget();
}
这个方法定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
class RILSender extends Handler implements Runnable {
@Override public void
handleMessage(Message msg) {
//待发送的数据
RILRequest rr = (RILRequest)(msg.obj);
RILRequest req = null;
switch (msg.what) {
case EVENT_SEND:
case EVENT_SEND_ACK:
try {
//初始化 socket 对象
LocalSocket s;
s = mSocket;
.....
//数据格式变换
byte[] data;
data = rr.mParcel.marshall();
.....
//往 server 端发送数据
s.getOutputStream().write(dataLength);
s.getOutputStream().write(data);
.....
}
}
}
这个类定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
Android 8.0 HIDL 版本:
@Override
public void dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
//获取 HIDL 客户端对象
IRadio radioProxy = getRadioProxy(result);
if (radioProxy != null) {
//封装待发送数据
RILRequest rr = obtainRequest(RIL_REQUEST_DIAL, result,
mRILDefaultWorkSource);
Dial dialInfo = new Dial();
dialInfo.address = convertNullToEmptyString(address);
dialInfo.clir = clirMode;
.....
}
try {
//远程调用,直接到达HW service
radioProxy.dial(rr.mSerial, dialInfo);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "dial", e);
}
}
}
这个类定义在文件 frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java 中。
阶段总结
在数据下发方面 HIDL 的代码依然是那么简洁。HIDL 在使用上和 AIDL 类似,更容易让 Android 开发者接受。Socket 只是把一堆数据往下发送,底层需要通过数据区分数据的目的;而 HIDL 直接调用底层的对应接口。在接口定义上,HIDL 比 socket 要简洁、条理与科学。
底层的脱变
初始化
Socket版本
从 rild.c 开始:
int main(int argc, char **argv) {
.....
const RIL_RadioFunctions *funcs;
.....
//注册RIL,跳至下一代码片段
RIL_register(funcs);
.....
}
这儿函数定义在文件 hardware/ril/rild/rild.c 中。
extern "C" void RIL_register (const RIL_RadioFunctions *callbacks) {
.....
// socket连接参数
s_ril_param_socket = {
RIL_SOCKET_1, /* socket_id */
-1, /* fdListen */
-1, /* fdCommand */
PHONE_PROCESS, /* processName */
&s_commands_event, /* commands_event */
&s_listen_event, /* listen_event */
processCommandsCallback, /* processCommandsCallback */
NULL /* p_rs */
};
.....
//监听(建立)socket,对应RIL_SOCKET_1,即卡卡槽1,每个卡槽,建立一个 socket
//继续看下一代码片段
startListen(RIL_SOCKET_1, &s_ril_param_socket);
}
这个函数定义在文件 hardware/ril/libril/ril.cpp 中。
static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {
//初始化 socket 名字,即 rild,和 RIL.java 保持一致
char socket_name[10];
switch(socket_id) {
case RIL_SOCKET_1:
strncpy(socket_name, RIL_getRilSocketName(), 9);
break;
.....
//开启socket,监听 socket 连接。等待上层(RIL.java)的连接,连接后即可通信
fdListen = android_get_control_socket(socket_name);
.....
}
这个函数定义在文件 hardware/ril/libril/ril.cpp 中。
HIDL版本
从 rild.c 开始:
int main(int argc, char **argv) {
.....
const RIL_RadioFunctions *funcs;
.....
//注册RIL,跳至下一代码片段
RIL_register(funcs);
.....
}
这儿函数定义在文件 hardware/ril/rild/rild.c 中。
extern "C" void RIL_register (const RIL_RadioFunctions *callbacks) {
.....
//注册RIL service,从这里开始和socket版本已经不一样。
//跳至下一代码片段
radio::registerService(&s_callbacks, s_commands);
.....
}
这个方法定义在文件 hardware/ril/libril/ril.cpp 中。
void radio::registerService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
//HIDL server端的名字,即 slot1、slot2...,每个卡槽一个 HIDL server端
const char *serviceNames[] = {
android::RIL_getServiceName()
.....
};
....
//为每个卡槽创建且注册一个RadioImpl(HIDL server端)
for (int i = 0; i < simCount; i++) {
//保存在radioService[]数组中
radioService[i] = new RadioImpl;
radioService[i]->mSlotId = i;
//注册 HIDL 服务
android::status_t status = radioService[i]->registerAsService(serviceNames[i]);
}
}
这个函数定义在文件 hardware/ril/libril/ril_service.cpp 中。
接收上层数据
承接上文中“发送数据”的章节,以上层发起拨号为例,底层 socket 版本和 HIDL 版本的差异。
Socket版本
从上文“发送数据”的章节中的代码片段可知,拨号时 RIL.java 下发的数据包含 RIL_REQUEST_DIAL,在如下代码中匹配触发的方法:
.....
//上层下发RIL_REQUEST_DIAL,触发dispatchDial()函数
//继续跳至下一代码片段
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
.....
这数组定义在文件 hardware/ril/libril/ril_commands.h 中。
static void dispatchDial (Parcel &p, RequestInfo *pRI) {
.....
//提取上层下发的数据,调用CALL_ONREQUEST(),即onRequest()
//继续跳至下一代码片段
CALL_ONREQUEST(pRI->pCI->requestNumber, &dial, sizeOfDial, pRI, pRI->socket_id);
.....
}
这个函数定义在文件 hardware/ril/libril/ril.cpp 中。
static void onRequest (int request, void *data, size_t datalen, RIL_Token t){
.....
//
case RIL_REQUEST_DIAL:
//继续跳至一下代码片段
requestDial(data, datalen, t);
break;
.....
}
这个方法定义在文件 hardware/ril/reference-ril/reference-ril.c 中。
static void requestDial(void *data, size_t datalen __unused, RIL_Token t){
.....
//下发 AT 命令到modem,触发拨号。
//我们跟踪到这里截止。
ret = at_send_command(cmd, NULL);
.....
}
这个方法定义在文件 hardware/ril/reference-ril/reference-ril.c 中。
HIDL版本
从上文中“发送数据”的章节中可知,在 RIL.java 是直接远程调用 dial() 函数,因此,直达 RadioImpl 中的 dial() 函数,如下:
Return RadioImpl::dial(int32_t serial, const Dial& dialInfo) {
.....
//HIDL到这里,调用 CALL_ONREQUEST() 和 socket 的版本一样的了,就不往下分析了
CALL_ONREQUEST(RIL_REQUEST_DIAL, &dial, sizeOfDial, pRI, mSlotId);
.....
}
这个方法定义在文件 hardware/ril/libril/ril_service.cpp 中。
阶段总结
底层接收上层的数据,HIDL 比 socket 都简洁、条理和易懂。也读可以看出,从 socket 切换到 HIDL 只需把接口的地方更换即可,相当方便。
总结
通过对比 telephony 从 RIL socket 的方式升级到 Android 8.0 的 HIDL,除开文章《HIDL》 中阐明的 HIDL 的目标和优点外,除开 HIDL 进程通信的优点外,HIDL 相比 socket 更加简洁、条理和易懂;client端和server端接口定义更加严谨、统一与便捷。