Android系统开发--14.hidl callback

上一小节我们已经完整的跑起来了一个 hidl 的 hello demo,这个 demo 虽然非常简单,但已经包含了 hidl 的大部分知识点了,我们只要在这个框架的基础上不断的扩展功能即可。
上一小节的demo 调用是单向的,即只能由 client 调用 service 的功能,但实际项目中我们是经常需要在 service 里面通知 client 某个事件发生了,需要 client 进行处理。也就是双向调用,或者称为 callback。下面我们在上一小节的基础上增加一个回调功能。

1. 增加回调 hal 接口

在 tvserver/1.0 目录下增加 ITVServerListener.hal 文件

1
2
3
4
5
package vendor.qiushao.hardware.tvserver@1.0;

interface ITVServerListener {
oneway onSourceChange(uint8_t source);
};

2. ITVServer.hal 增加回调注册接口

1
2
3
4
5
6
7
8
9
package vendor.qiushao.hardware.tvserver@1.0;

import ITVServerListener;

interface ITVServer {
hello(string name) generates (string result);
registerListener(uint32_t pid, ITVServerListener listener);
unregisterListener(uint32_t pid);
};

3. 更新 Android.mk, Android.bp, current.txt

current.txt 中增加 ITVServerListener 接口的 hash 值,先随便写一个,然后执行

1
./device/qiushao/common/hidl/interfaces/update-makefiles.sh

会提示 hash 值不对,更新 current.txt 即可,
两个 hal 接口的 hash 值都更新之后,再执行 update-makefiles.sh

4. 实现新增的 hal 接口

TVServer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...

#include <vendor/qiushao/hardware/tvserver/1.0/ITVServerListener.h>
#include <map>
using namespace std;

...

class TVServer : public ITVServer {
public:
Return<void> hello(const hidl_string& name, hello_cb _hidl_cb) override;
Return<void> registerListener(const uint32_t pid, const sp<ITVServerListener>& listener) override;
Return<void> unregisterListener(const uint32_t pid) override;
private:
map<uint32_t, sp<ITVServerListener> > mListeners;
};

...

增加了 registerListener, unregisterListener 两个方法, 这两个方法的参数类型可以根据 hal 文件中声明的参数类型来推断。
hidl 与 c++ 的数据类型转换可以按下表来转换一下,给所有参数都加上 const
数据类型

hal 类型的参数,则使用 const sp& listener 这种智能指针的形式。
当然我们也可以使用 hidl-gen 来自动生成代码。参考上一小节的:根据 .hal 自动生成 cpp 实现。

TVServer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...

Return<void> TVServer::hello(const hidl_string& name, hello_cb _hidl_cb) {
char buf[100];
::memset(buf, 0, 100);
::snprintf(buf, 100, "hello %s from TVServer", name.c_str());
hidl_string result(buf);
_hidl_cb(result);

LOGD("listener size = %d", mListeners.size());
map<uint32_t, sp<ITVServerListener> >::iterator it;
for (it = mListeners.begin(); it != mListeners.end(); ++it) {
sp<ITVServerListener> listener = it->second;
listener->onSourceChange(12);
}

return Void();
}

Return<void> TVServer::registerListener(const uint32_t pid, const sp<ITVServerListener>& listener) {
mListeners[pid] = listener;
LOGI("registerListener %d, %x", pid, listener.get());
return Void();
}

Return<void> TVServer::unregisterListener(const uint32_t pid) {
mListeners.erase(pid);
LOGI("unregisterListener %d", pid);
return Void();
}
...

5. 更新 manifest.xml

增加一个interface节点就行

1
2
3
4
5
6
7
8
9
10
11
12
13
<hal format="hidl">
<name>vendor.qiushao.hardware.tvserver</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>ITVServer</name>
<instance>default</instance>
</interface>
<interface>
<name>ITVServerListener</name>
<instance>default</instance>
</interface>
</hal>

6. 更新 client 

TVServerTest.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <vendor/qiushao/hardware/tvserver/1.0/ITVServer.h>
#include <vendor/qiushao/hardware/tvserver/1.0/ITVServerListener.h>
#include <hidl/Status.h>
#include <utils/misc.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>

using ::android::hardware::hidl_string;
using ::android::sp;
using vendor::qiushao::hardware::tvserver::V1_0::ITVServer;
using vendor::qiushao::hardware::tvserver::V1_0::ITVServerListener;

class TVServerListener : public ITVServerListener {
public:
android::hardware::Return<void> onSourceChange(uint8_t source) override {
printf("onSourceChange %d\n", source);
return android::hardware::Return<void>();
}
};

int main(){

sp<ITVServerListener> listener = new TVServerListener();

android::sp<ITVServer> service = ITVServer::getService();
if (service == nullptr){
printf("Failed to get service\n");
return -1;
}

service->registerListener(getpid(), listener);

service->hello("qiushao", [&](hidl_string result){
printf("%s\n", result.c_str());
});
sleep(1);

service->unregisterListener(getpid());
return 0;
}

7. 编译验证

整编系统,执行

1
2
3
4
generic_pure:/vendor/bin/hw # ./TVServerTest                                                                                                            
hello qiushao from TVServer
onSourceChange 12
generic_pure:/vendor/bin/hw #

可以看到收到了 onSourceChange 回调。

8. hidl_death_recipient

我们提供了 register 和 unregister 两个接口,以便正常的注册,注销 listener。
但有时候,client 调用了 register 后,由于某种意外 client 进程被 kill 掉了,这个时候还没来得及注销 listener, 如果 service 继续调用已经 GG 的 client listener,则 service 会出现异常挂掉。为了避免这种情况发生,我们在 service 端需要监听 client 是否GG。

TVServer.h 修改如下:

1
2
3
4
5
6
7
8
9
10
11
using ::android::hardware::hidl_death_recipient;
class ClientDeathRecipient;

class TVServer : public ITVServer {
...
public:
TVServer();
private:
map<uint32_t, sp<ITVServerListener> > mListeners;
sp<ClientDeathRecipient> mDeathRecipient;
};

只是声明了一个 mDeathRecipient 。

TVServer.cpp 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
class ClientDeathRecipient : public hidl_death_recipient {
private:
sp<TVServer> mTVServer;
public:
ClientDeathRecipient(sp<TVServer> tvserver): mTVServer(tvserver) {}
virtual void serviceDied(uint64_t cookie, const android::wp<::android::hidl::base::V1_0::IBase>& who) {
LOGE("serviceDied cookie = %d", cookie);
mTVServer->unregisterListener(cookie);
}
};

TVServer::TVServer() {
mDeathRecipient = new ClientDeathRecipient(this);
}

Return<void> TVServer::registerListener(const uint32_t pid, const sp<ITVServerListener>& listener) {
mListeners[pid] = listener;
listener->linkToDeath(mDeathRecipient, pid);
LOGI("registerListener %d, %x", pid, listener.get());
return Void();
}

Return<void> TVServer::unregisterListener(const uint32_t pid) {
mListeners[pid]->unlinkToDeath(mDeathRecipient);
mListeners.erase(pid);
LOGI("unregisterListener %d", pid);
return Void();
}

在注册 listener 的时候顺便给 listener linkToDeath 到一个 hidl_death_recipient 死亡讣告上。
linkToDeath 的第二个参数为 cookie, 用来标志当前的 listener,若当前的 listener GG了, 系统会自动调用 ClientDeathRecipient 的 serviceDied 函数。其中的 cookie 值即为我们在 linkToDeath 传的第二个参数。收到 serviceDied 回调后,我们把对应 cookie 的 listener 从 mListener 中移除就行。
当然上面的代码还没有考虑多线程的情况, 实际项目代码中我们对所有的临界区资源访问前都应该进行加锁。