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++等),一起成长进步。

在这里插入图片描述

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值