一、背景
最近有个小伙伴问我可不可以写一个可执行程序(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.so | frameworks/native/include/binder/* frameworks/native/include/binder/* | |
libutils.so | system/core/libutils/include/utils/* | |
liblog.so | system/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 项目整体结构
目录 | 功能 |
---|---|
aidl | VMS(VibratorManagerService) 相关的AIDL文件 |
so | 项目依赖的so目录 |
include | 项目依赖的so对应的导出头文件 |
vibrator_main.cpp | 主程序入口 |
CmakeLists.txt | Cmake构建脚本 |
*.h | AIDL生成的头文件 |
*.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++程序来完成此功能。
五、写在最后
最后,欢迎小伙伴关注我的公众号(主要内容Android、鸿蒙、FW、C/C++等),一起成长进步。