HIDL In Telephony

前置文章

《HIDL》

前言

在 Android 8.0(不含,下同)之前,Telephony 和 modem 之间一直用 socket 进行连接通信,它是 RILD 。其实通过 socket 连接的两个上下层模块,已经非常的解耦,也具有 HIDL 独立编译的特性,但是应用范围受到限制,socket 通信的速度和接口的定义等不是很理想,没有大范围的应用到各个模块。HIDL 技术的推出,可以替换通过 socket 连接的各个模块,发挥 HIDL 技术的优势。

采用 socket 的 RIL 连接架构通信如下:

socket 的 RIL 连接架构

从 socket 升级到 HIDL,只需修改 RIL.java 和 ril.cpp 两个地方的接口即可。JAVA 通过绑定式 HAL 直接连接到 HW service。如下图:

HIDL的 RIL 连接架构

下文中,我采用 Android 8.0 之前的版本和 Android 8.0 的版本的差异化通过比较的形式列出来,以便更好的理解 HIDL 的技术。

如下链接一下笔者以往关于 telephony 的文章,供有需要的读者踩一踩

  1. Android无线电信息管理开篇准备工作

  2. 初识com.android.phone

  3. PhoneInterfaceManager

  4. TelephonyTesgistry

  5. UICC

  6. 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<void> 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端接口定义更加严谨、统一与便捷。

微信扫一扫关注公众号获取更多精彩内容

推荐阅读更多精彩内容