HIDL In Telephony

前置文章

《HIDL》

前言

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

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

HIDL In Telephony_第1张图片
socket 的 RIL 连接架构

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

HIDL In Telephony_第2张图片
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 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端接口定义更加严谨、统一与便捷。

HIDL In Telephony_第3张图片
微信扫一扫关注公众号获取更多精彩内容

你可能感兴趣的:(HIDL In Telephony)