• Android NDK开发之震动服务客户端编写程序(C++)


    一、背景

    最近有个小伙伴问我可不可以写一个可执行程序(C/C++) 来实现Android设备的震动的功能。 作为一个多年的Android开发者,我觉得这是可以实现的。 但是由于过去我一直做App开发,也就把这个实现过程想简单了。 经过了几天的折腾,终于算是实现了这个程序。 本文就是记录一下 整个“折腾”的经历。

    环境描述: Android12 (由于AOSP针对于 "震动服务"的架构 在Android12上有调整)
    开发工具: Android Studio
    开发形式: NDK

    二、实现思路

    • 思路一: 获取到VibratorManagerService对相应的BpBinder对象,然后进行IPC通信。
    • 思路二: 模拟VibratorManagerService,直接“访问硬件”,实震动效果。
    • 思路三: 写在最后(苦笑~~)。

    当我被问到这个问题的时候,我首先想到了前面两种实现方式,但是又考虑到 “思路二” 直接“访问硬件设备” 不太符合Android的架构理念, 故想尝试一下“方式一”的实现。

    2.1 可行性分析

    熟悉binder架构的同学,可能都知道:android系统中的binder ipc 就是通过native层调用驱动层来实现的。即便在应用层开发的时候,我们常采用的AIDL,其本质上也是通过native的bpbinder和bbinder实现。 具体的原理,可以看一下面的这张图:

    在这里插入图片描述

    同上图中看出 Bpbinder 与 Bbinder 是成对儿且对等的;同理在上层Binder.Stub 与 BinderProxy是成对儿且对等的。

    我们是可以实现通过native层的bpbinder调用到java层的BinderServer对象(例如:IPackageManger.Stub)。

    2.2 VibratorManagerService 结构图

    在这里插入图片描述

    从上图中VibratorManagerService(后面简称‘VMS’)的结构中,我们可以看出:

    要想实现编写一个C++可执行程序实现控制振动器的功能,需要两个:a. 获取VibratorManagerService的代理对象 b. 遵顼IVbratorManagerService AIDL协议

    三、功能实现 VibratorManagerService 结构图

    • 获取VMS代理对象
      • 获取SM代理对象
      • 获取VMS代理对象
    • AIDL协议生成
    • 编译构建
    • 测试IPC调用

    3.1 获取VMS代理对象

    熟悉AOSP源码的小伙伴,可能都会知道:获取一个BinderProxy代理对象,是通过ServiceManager的代理对象来完成的。 因此咱们要做的第一步就是拿到SM代理对象,然后再通过SM代理对象去“getService”拿到VMS代理对象。

    3.1.1 导入libbinder.so并获取SM代理对象

    AOSP中,Native层获取SM代理对象的方式是:

    sp<IServiceManager> sm = defaultServiceManager();
    

    defaultServiceManager() 函数是定义在IServiceManager.h中,但是在NDK中,binder包下并没有IServiceManager.h
    因此我们只能将 libbinder.so 以第三方库的形式导入到项目中,并将相关的 头文件拷贝进来。

    3.1.1.1 导入 libbinder.so

    AOSP中,构建libinder.so 时, 依赖了 liblog.so , libcutils.so , libutils.so ,因此需要依次导入:

    // frameworks/native/libs/binder/Android.bp
    cc_library_shared {
        name: "libbinder",
        ...
        ...
        shared_libs: [
            "libbase",
            "liblog",
            "libcutils",
            "libutils",
            "libbinderthreadstate",
        ]
        ...
    }
    

    在这里插入图片描述

    接下来就是每个头文件的导入:

    这里笔者是根据so对应的android.bp文件,找到对应“导出头文件”

    在这里插入图片描述

    头文件目录如下

    库名称头文件路径备注
    libbinder.soframeworks/native/include/binder/*
    frameworks/native/include/binder/*
    libutils.sosystem/core/libutils/include/utils/*
    liblog.sosystem/logging/liblog/include/log/*
    其它system/core/libsystem/include/system/*

    详情可以见下面的demo

    3.1.1.2 获取SM代理对象
    sp<IServiceManager> sm = defaultServiceManager();
    

    3.1.2 获取VMS代理对象

    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("vibrator_manager"));
    if (binder != nullptr) {
        LOGD("Sharknade Binder info: %p", binder.get());
    }
    

    编译成功之后, 测试结果如下:
    在这里插入图片描述

    获得VMS代理成功了 ,接下是就是如何使用该代理对象进行RPC通信了。

    3.2 AIDL协议生成

    Android SDK中 提供了AIDL工具,可以实现AIDL文件转C++、Java、NDK等形式转换。 我们可以通过该工具 快速获得VMS 相关的头文件。
    VMS相关的AIDL文件有5个:

    • IVibratorManagerService.aidl
    • CombinedVibration.aidl
    • IVibratorStateListener.aidl
    • VibrationAttributes.aidl
    • VibratorInfo.aidl

    执行下面的shell,获得对应的头文件:

    <SDK_PATH>/build-tools/31.0.0/aidl -h ./ -o ./ --lang=cpp -I ./aidl  aidl/android/os/IVibratorManagerService.aidl
    <SDK_PATH>/build-tools/31.0.0/aidl -h ./ -o ./ --lang=cpp -I ./aidl  aidl/android/os/CombinedVibration.aidl
    <SDK_PATH>/build-tools/31.0.0/aidl -h ./ -o ./ --lang=cpp -I ./aidl  aidl/android/os/IVibratorStateListener.aidl
    <SDK_PATH>/build-tools/31.0.0/aidl -h ./ -o ./ --lang=cpp -I ./aidl  aidl/android/os/VibrationAttributes.aidl
    <SDK_PATH>/build-tools/31.0.0/aidl -h ./ -o ./ --lang=cpp -I ./aidl  aidl/android/os/VibratorInfo.aidl
    
    参数含义
    -h生成的头文件路径
    -o生成的源文件路径
    -I导入的头文件路径

    3.3 编译构建项目

    3.3.1 项目整体结构

    在这里插入图片描述

    目录功能
    aidlVMS(VibratorManagerService) 相关的AIDL文件
    so项目依赖的so目录
    include项目依赖的so对应的导出头文件
    vibrator_main.cpp主程序入口
    CmakeLists.txtCmake构建脚本
    *.hAIDL生成的头文件

    *.h 头文件的间的关系:

    在这里插入图片描述

    3.3.2 CmakeLists内容及注意事项

    cmake_minimum_required(VERSION 3.22.1)
    
    project("vibrator_sample")
    
    # 设置C++标准为C++17
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # -fno-rtti 这个标记很重要,否则编译不通过。
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
    
    set(VIBRATOR vibrator_main)
    include_directories(include)
    add_executable(${VIBRATOR}
            vibrator_main.cpp
            IVibratorManagerService.h
            BpVibratorManagerService.h
            CombinedVibration.h
            IVibratorStateListener.h
            VibrationAttributes.h
            VibratorInfo.h
            IVibratorManagerService.cpp
            )
    
    set_target_properties(${VIBRATOR} PROPERTIES
            RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out
            )
    
    # 指定NDK的STL库
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
    
    set(SHARED_LIB_DIR ${CMAKE_SOURCE_DIR}/so)
    link_directories(${SHARED_LIB_DIR})
    
    add_library(binder SHARED IMPORTED)
    set_target_properties(binder PROPERTIES IMPORTED_LOCATION ${SHARED_LIB_DIR}/libbinder.so)
    
    add_library(cutils SHARED IMPORTED)
    set_target_properties(cutils PROPERTIES IMPORTED_LOCATION ${SHARED_LIB_DIR}/libcutils.so)
    
    add_library(utils SHARED IMPORTED)
    set_target_properties(utils PROPERTIES IMPORTED_LOCATION ${SHARED_LIB_DIR}/libutils.so)
    
    add_library(log SHARED IMPORTED)
    set_target_properties(log PROPERTIES IMPORTED_LOCATION ${SHARED_LIB_DIR}/liblog.so)
    
    
    # 链接目标库
    target_link_libraries(
            ${VIBRATOR}
            binder
            utils
            cutils
            log
    )
    

    3.4 测试结果

    右侧执行 vibrator_main 可执行程序,左侧通过adb 能够看到RPC调用日志。
    在这里插入图片描述

    四、实现思路三(补充)

    该功能可以通过shell实现

    我在查看VibratorManagerService源码时,发现该执行shell命令操作:

    class VibratorManagerService {
        @Override
        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
              String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
            new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
        }
        
        private final class VibratorManagerShellCommand extends ShellCommand {
            public static final String SHELL_PACKAGE_NAME = "com.android.shell";
            ...
            ...
        }
    }
    

    因此,用下面的命令,也可以实现震动效果。

    adb shell
    cmd vibrator_manager synced oneshot 100 #执行震动操作
    

    那么,我们可以写一个执行shell的C++程序来完成此功能。

    五、写在最后

    Demo功能地址

    ADB 相关命令



    最后,欢迎小伙伴关注我的公众号(主要内容Android、鸿蒙、FW、C/C++等),一起成长进步。

    在这里插入图片描述

  • 相关阅读:
    python练习题集锦之一
    【Python百日进阶-WEB开发】Day173 - Django案例:05用户注册业务逻辑
    常见LLM使用的分词算法总结
    自主设计,模拟实现 RabbitMQ - 实现 拒绝/否定 应答机制
    汽车行业深度报告:域控制器,汽车电子电气架构演进下的黄金赛道
    redis五大常见数据结构的操作命令(string, hash, list, set和zset)
    微信公众号菜单栏规划怎么设置?
    黑马Java热门面试题Redis(九)
    RT-Thread STM32F407 PWM
    「学习笔记」可持久化线段树
  • 原文地址:https://blog.csdn.net/dirksmaller/article/details/140463567