美文网首页Android
HIDL In Telephony

HIDL In Telephony

作者: FamilyYuan | 来源:发表于2018-03-06 18:12 被阅读530次

    前置文章

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

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

    相关文章

      网友评论

        本文标题:HIDL In Telephony

        本文链接:https://www.haomeiwen.com/subject/czyrfftx.html