1. 项目背景与核心价值
在Android系统开发中,硬件抽象层(HAL)与框架层的通信一直是开发者需要面对的技术难点。传统方式通过HIDL实现跨进程通信存在接口定义复杂、兼容性要求高等问题。而AIDL(Android Interface Definition Language)作为Android系统进程间通信的成熟方案,在Android 11及后续版本中获得了对HAL层的完整支持。
这个项目的核心价值在于:
- 提供了一种更简洁的HAL层通信实现方案
- 利用AIDL的强类型接口特性降低开发复杂度
- 兼容Android 11及后续版本的标准化开发模式
- 为RK3568这类主流嵌入式平台提供可复用的参考实现
2. 环境准备与基础配置
2.1 开发环境搭建
对于RK3568平台的Android 11开发,需要准备以下基础环境:
-
硬件准备:
- RK3568开发板(建议使用官方EVB开发板)
- USB转串口调试工具
- 配套电源和线材
-
软件环境:
- Ubuntu 20.04 LTS开发主机
- Android 11 SDK for RK3568(需从Rockchip官网获取)
- 编译工具链:
bash复制sudo apt-get install git-core gnupg flex bison gperf build-essential \ zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \ lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \ libgl1-mesa-dev libxml2-utils xsltproc unzip
-
源码获取:
bash复制repo init -u https://gitlab.com/rockchip-linux/manifest.git -b android-11.0 repo sync -j$(nproc)
2.2 AIDL基础概念
在开始实现前,需要明确几个关键概念:
-
AIDL文件类型:
- 接口定义(.aidl):声明通信接口
- Parcelable对象(.aidl):定义跨进程传输的数据结构
-
通信方向:
- 框架层→HAL层(通常用于控制指令)
- HAL层→框架层(通常用于事件上报)
-
线程模型:
oneway关键字用于异步调用- 默认同步调用会阻塞调用方线程
3. HAL层AIDL接口实现
3.1 创建AIDL接口定义
在hardware/interfaces/目录下创建我们的HAL服务:
-
新建目录结构:
bash复制mkdir -p hardware/interfaces/myhal/1.0/aidl/android/hardware/myhal -
创建接口文件
IMyHal.aidl:aidl复制package android.hardware.myhal; interface IMyHal { int getVersion(); void setConfig(in int param1, in String param2); String readData(); oneway void registerCallback(IMyHalCallback callback); oneway void unregisterCallback(IMyHalCallback callback); } -
创建回调接口
IMyHalCallback.aidl:aidl复制package android.hardware.myhal; interface IMyHalCallback { oneway void onEvent(int eventId, in String eventData); }
3.2 实现HAL服务
-
在
hardware/interfaces/myhal/1.0/default/创建服务实现:cpp复制#include <android/binder_manager.h> #include <android/binder_process.h> #include <android-base/logging.h> #include "MyHal.h" using ::android::hardware::myhal::IMyHal; int main() { ABinderProcess_setThreadPoolMaxThreadCount(4); std::shared_ptr<IMyHal> myhal = ndk::SharedRefBase::make<MyHal>(); const std::string instance = std::string(IMyHal::descriptor) + "/default"; binder_status_t status = AServiceManager_addService( myhal->asBinder().get(), instance.c_str()); if (status != STATUS_OK) { LOG(ERROR) << "Failed to register service"; return 1; } ABinderProcess_joinThreadPool(); return 0; } -
核心服务类实现
MyHal.cpp:cpp复制#include "MyHal.h" namespace aidl::android::hardware::myhal { ndk::ScopedAStatus MyHal::getVersion(int* _aidl_return) { *_aidl_return = 1; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus MyHal::setConfig(int param1, const std::string& param2) { // 实现配置逻辑 return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus MyHal::readData(std::string* _aidl_return) { *_aidl_return = "sample data"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus MyHal::registerCallback( const std::shared_ptr<IMyHalCallback>& callback) { std::lock_guard<std::mutex> lock(callback_mutex_); callbacks_.push_back(callback); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus MyHal::unregisterCallback( const std::shared_ptr<IMyHalCallback>& callback) { std::lock_guard<std::mutex> lock(callback_mutex_); callbacks_.erase( std::remove(callbacks_.begin(), callbacks_.end(), callback), callbacks_.end()); return ndk::ScopedAStatus::ok(); } } // namespace aidl::android::hardware::myhal
3.3 构建系统集成
-
在
hardware/interfaces/myhal/1.0/目录下创建Android.bp:bp复制aidl_interface { name: "android.hardware.myhal", srcs: ["aidl/android/hardware/myhal/*.aidl"], stability: "vintf", backend: { cpp: { enabled: true, }, }, } -
在
default/目录下创建Android.bp:bp复制cc_binary { name: "android.hardware.myhal-service", defaults: ["hidl_defaults"], vendor: true, relative_install_path: "hw", srcs: ["MyHal.cpp", "service.cpp"], shared_libs: [ "libbase", "libbinder_ndk", "liblog", "android.hardware.myhal", ], }
4. 框架层客户端实现
4.1 客户端接口封装
-
创建Java客户端封装类:
java复制public class MyHalManager { private static final String SERVICE_NAME = "android.hardware.myhal.IMyHal/default"; private IMyHal mService; private final Context mContext; public MyHalManager(Context context) { mContext = context; } public void connect() throws RemoteException { mService = IMyHal.Stub.asInterface( ServiceManager.getService(SERVICE_NAME)); if (mService == null) { throw new RemoteException("Service not found"); } } public int getVersion() throws RemoteException { return mService.getVersion(); } public void setConfig(int param1, String param2) throws RemoteException { mService.setConfig(param1, param2); } public String readData() throws RemoteException { return mService.readData(); } } -
实现回调接口:
java复制private class MyHalCallback extends IMyHalCallback.Stub { @Override public void onEvent(int eventId, String eventData) { // 处理来自HAL层的事件 Log.d(TAG, "Received event: " + eventId + ", data: " + eventData); } }
4.2 服务绑定与生命周期管理
-
在SystemServer中初始化服务:
java复制public class SystemServer { private void startOtherServices() { try { MyHalManager myHalManager = new MyHalManager(context); myHalManager.connect(); ServiceManager.addService("myhal", myHalManager); } catch (Throwable e) { reportWtf("Starting MyHal Service", e); } } } -
添加SELinux策略:
在
device/rockchip/rk3568/sepolicy/目录下添加:te复制type myhal_service, system_api_service, system_server_service, service_manager_type; # 允许系统服务访问 allow system_server myhal_service:service_manager find; # 允许HAL服务注册 allow hal_myhal_default myhal_service:service_manager add;
5. 调试与问题排查
5.1 常见编译问题
-
AIDL文件找不到:
code复制error: could not find AIDL interface 'android.hardware.myhal.IMyHal'解决方案:
- 检查AIDL文件路径是否符合规范
- 确保
Android.bp中srcs包含所有AIDL文件 - 执行
mm前先source build/envsetup.sh和lunch
-
服务注册失败:
code复制Failed to register service解决方案:
- 检查服务名称是否符合
<interface>/<instance>格式 - 确认SELinux策略已正确配置
- 检查服务进程是否具有足够权限
- 检查服务名称是否符合
5.2 运行时调试技巧
-
检查服务是否注册:
bash复制
adb shell service list | grep myhal -
手动启动服务调试:
bash复制
adb shell /vendor/bin/hw/android.hardware.myhal-service -
查看Binder调用统计:
bash复制
adb shell dumpsys binder stats | grep myhal -
调试日志过滤:
bash复制adb logcat | grep -E 'MyHal|myhal'
5.3 性能优化建议
-
对于高频调用的接口:
- 使用
oneway异步调用避免阻塞 - 批量传输数据而非多次小数据量调用
- 考虑使用共享内存传递大数据
- 使用
-
回调处理优化:
- 在HAL层使用线程池处理回调
- 避免在回调中进行耗时操作
- 实现回调的批量通知机制
-
内存管理:
- 注意跨进程传递大对象的开销
- 及时释放不再使用的回调引用
- 使用
ParcelFileDescriptor传递文件描述符而非数据内容
6. 进阶应用与扩展
6.1 多实例管理
对于需要支持多设备的场景,可以扩展实现:
-
修改接口支持多实例:
aidl复制interface IMyHal { static final String INSTANCE_PRIMARY = "primary"; static final String INSTANCE_SECONDARY = "secondary"; // ... } -
在服务端实现实例管理:
cpp复制std::map<std::string, std::shared_ptr<IMyHal>> instances; binder_status_t addService(const std::string& instance) { auto hal = ndk::SharedRefBase::make<MyHal>(instance); instances[instance] = hal; return AServiceManager_addService( hal->asBinder().get(), (std::string(IMyHal::descriptor) + "/" + instance).c_str()); }
6.2 与HIDL共存方案
对于需要兼容旧版HIDL实现的场景:
-
创建适配层:
cpp复制class HalAdapter : public IMyHal { public: explicit HalAdapter(sp<IOldHal> hidlHal) : mHidlHal(hidlHal) {} ndk::ScopedAStatus getVersion(int* _aidl_return) override { return mHidlHal->getVersion().then([](int ver) { return ScopedAStatus::ok(ver); }); } // ... private: sp<IOldHal> mHidlHal; }; -
在服务创建时检测HIDL服务:
cpp复制sp<IOldHal> hidlHal = IOldHal::tryGetService(); if (hidlHal != nullptr) { auto adapter = ndk::SharedRefBase::make<HalAdapter>(hidlHal); AServiceManager_addService(adapter->asBinder().get(), instance.c_str()); }
6.3 VTS测试集成
为确保实现符合兼容性要求:
-
创建VTS测试用例:
java复制@RunWith(AndroidJUnit4.class) public class MyHalVtsTest { private IMyHal mHal; @Before public void setUp() throws Exception { mHal = IMyHal.Stub.asInterface( ServiceManager.getService("android.hardware.myhal.IMyHal/default")); assertNotNull(mHal); } @Test public void testGetVersion() throws Exception { int version = mHal.getVersion(); assertTrue(version >= 1); } } -
在
Android.bp中添加测试配置:bp复制vts_test_binary { name: "VtsHalMyHalTargetTest", srcs: ["test/**/*.java"], static_libs: [ "android.hardware.myhal-V1.0-java", "vts-testbase", ], test_suites: ["vts"], test_config: "test/config.xml", }
7. 项目实践心得
在实际为RK3568实现AIDL HAL的过程中,有几个关键点值得特别注意:
-
线程安全处理:
- HAL服务默认运行在binder线程池中
- 对共享数据(如回调列表)必须加锁保护
- 避免在回调中执行耗时操作导致线程阻塞
-
版本兼容性:
- 接口设计时应考虑未来扩展
- 新增方法尽量保持向后兼容
- 使用版本号管理接口变更
-
性能考量:
- 减少跨进程调用次数
- 大数据传输考虑使用ashmem
- 高频调用接口考虑批处理设计
-
调试技巧:
- 使用
dumpsys检查服务状态 - 通过
binderCall统计分析调用频率 - 在关键路径添加详细日志
- 使用
-
稳定性保障:
- 处理所有可能的异常情况
- 实现心跳检测机制
- 添加合理的超时控制
对于RK3568这类资源受限的设备,还需要特别注意内存使用和CPU占用。在实际测试中,我们发现合理设置binder线程池大小(通常4-8个线程)能取得较好的性能平衡。此外,由于AIDL默认的同步调用特性,在设计接口时需要特别注意避免调用链过长导致的响应延迟。