添加 Native 系统服务回调

7/26/2023

本文配套源码下载地址:https://github.com/yuandaimaahao/AIDLCppCallbackDemo

首先我们要明确的是无论是从 client 调用 server, 还是 server 回调 client,本质上都是跨进程通信,都是需要借助 binder 框架的。

接下来我们在Binder 程序示例之 aidl-cpp 篇 (opens new window)介绍的示例程序上修改,给它添加回调功能。

首先我们在我们的自定义 Product device/jelly/rice14 下创建如下的文件与文件夹(关于自定义 Product,请查看 添加 Product (opens new window)):

AIDLCppCallbackDemo
├── aidl.sh
├── Android.bp
├── com
│   └── yuandaima
│       ├── ICallback.aidl
│       └── IHello.aidl
├── HelloClient.cpp
└── HelloServer.cpp
1
2
3
4
5
6
7
8
9

# AIDL 文件编写

其中 ICallback.aidl 是我们定义的回调接口,其内容如下:

package com.yuandaima;

interface ICallback
{
    void onCallback(String str);
}
1
2
3
4
5
6

接着我们通过如下命令,将 aidl 转换为对应的 cpp 源码:

# 第一个路径是头文件位置,会在这个路径下创建包对应的文件夹
# 第二路径是 cpp 文件路径
aidl-cpp com/yuandaima/ICallback.aidl ./ ./ICallback.cpp
1
2
3

通过上述命令生成如下的文件结构:

AIDLCppCallbackDemo
├── aidl.sh
├── Android.bp
├── com
│   └── yuandaima
│       ├── BnCallback.h
│       ├── BpCallback.h
│       ├── ICallback.aidl
│       ├── ICallback.h
│       └── IHello.aidl
├── HelloClient.cpp
├── HelloServer.cpp
└── ICallback.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13

IHello.aidl 是我们定义的 binder 服务端对外提供的服务接口,其内容如下:

package com.yuandaima;

import com.yuandaima.ICallback;

interface IHello
{
    void hello();
    int sum(int x, int y);
    void registerCallback(ICallback cb);
}
1
2
3
4
5
6
7
8
9
10

接着我们通过如下命令,将 aidl 转换为对应的 cpp 源码:

# -I 用于指示我们 import 的部分在哪里找
# 第一个路径是头文件位置,会在这个路径下创建包对应的文件夹
# 第二路径是 cpp 文件路径
aidl-cpp -I./com/yuandaima com/yuandaima/IHello.aidl ./ ./IHello.cpp
1
2
3
4

通过上述命令生成如下的文件结构:

AIDLCppCallbackDemo
├── aidl.sh
├── Android.bp
├── com
│   └── yuandaima
│       ├── BnCallback.h
│       ├── BnHello.h
│       ├── BpCallback.h
│       ├── BpHello.h
│       ├── ICallback.aidl
│       ├── ICallback.h
│       ├── IHello.aidl
│       └── IHello.h
├── HelloClient.cpp
├── HelloServer.cpp
├── ICallback.cpp
└── IHello.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

为了方便,我们把两个命令写为一个 shell 脚本 aidl.sh,后面修改了 aidl 文件,直接使用 shell 脚本即可生成新的源码文件了

#!/bin/bash

aidl-cpp com/yuandaima/ICallback.aidl ./ ./ICallback.cpp
aidl-cpp -I./com/yuandaima com/yuandaima/IHello.aidl ./ ./IHello.cpp
1
2
3
4

# 服务端实现

服务端实现在 HelloServer.cpp 中:

#define LOG_TAG "aidl_cpp"
#include <log/log.h>
#include <stdlib.h>
#include <utils/RefBase.h>
#include <utils/Log.h>
#include <binder/TextOutput.h>
#include <binder/IInterface.h>
#include <binder/IBinder.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>

#include "com/yuandaima/IHello.h"
#include "com/yuandaima/BnHello.h"
#include "com/yuandaima/ICallback.h"
#include "com/yuandaima/BpCallback.h"

using namespace android;

//定义服务端
class IHelloServer : public com::yuandaima::BnHello
{
private:
    //回调接口
    sp<com::yuandaima::ICallback> mCallback;
public:
    binder::Status hello() override
    {
        ALOGI("hello");
        return binder::Status();
    }

    binder::Status sum(int32_t v1, int32_t v2, int32_t *_aidl_return) override
    {
        ALOGI("server: sum: %d + %d", v1, v2);
        *_aidl_return = v1 + v2;

        //使用回调接口
        if(mCallback.get() != nullptr) {
            mCallback->onCallback(String16("str from server"));
        }

        return binder::Status();
    }

    //注册回调接口
    binder::Status registerCallback(const sp<::com::yuandaima::ICallback>& cb) override 
    {
        ALOGI("Server registerCallback");
        mCallback = cb;
        return binder::Status();
    }
};

int main(int argc, char const *argv[])
{   
    ALOGD("HelloServer is running");
    defaultServiceManager()->addService(String16("IHello"), new IHelloServer());
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    return 0;
}

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

首先我们需要定义我们的服务端类 IHelloServer,这个类继承自 BnHello,这个类来自 aidl 生成的头文件 com/yuandaima/BnHello.h。我们需要实现三个函数的服务端实现,我们重点关注 registerCallback,该函数用于组成回调接口,会把回调对象保存在成员变量 mCallback 中,当我们的调用到 sum 函数时,就会通过 mCallback 成员调用回调函数了。

# 客户端实现

#define LOG_TAG "aidl_cpp"
#include <log/log.h>
#include <stdlib.h>
#include <utils/RefBase.h>
#include <utils/Log.h>
#include <binder/TextOutput.h>
#include <binder/IInterface.h>
#include <binder/IBinder.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>

#include "com/yuandaima/IHello.h"
#include "com/yuandaima/BpHello.h"
#include "com/yuandaima/ICallback.h"
#include "com/yuandaima/BnCallback.h"

using namespace android;

class MyCallback : public com::yuandaima::BnCallback {
public:
    binder::Status onCallback(const String16 &str) override {
        ALOGD("client: onCallback, receive str: %s", String8(str).string());
        return binder::Status();
    }
};

int main(int argc, char const *argv[])
{
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("IHello"));
    sp<com::yuandaima::IHello> hello = interface_cast<com::yuandaima::IHello>(binder);

    hello->hello();
    sp<MyCallback> myCallback = new MyCallback();
    hello->registerCallback(myCallback);
    int ret = 0;
    hello->sum(1, 2, &ret);

    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

    return 0;
}
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
41
42
43
44

之前的示例 (opens new window) 不同的是:

  • 实现 MyCallback 类,其父类为 BnCallback,来自生成的源码 com/yuandaima/BnCallback.h,在类中需要实现对应的回调函数,这里简单打印一些信息。
  • 主函数中需要开启线程池,因为 Client 端需要等待 Server 端的回调,所以需要开启线程池,让程序持续运转下去。

# 编译与测试

编写 Android.bp 编译文件:

cc_binary {
    name: "BinderCallbackServer",
    srcs: ["HelloServer.cpp", "IHello.cpp","ICallback.cpp"],
    shared_libs: [
        "liblog",
        "libcutils",
        "libutils",
        "libbinder",
    ],
}


cc_binary {
    name: "BinderCallbackClient",
    srcs: ["HelloClient.cpp", "IHello.cpp","ICallback.cpp"],
    shared_libs: [
        "liblog",
        "libcutils",
        "libutils",
        "libbinder",
    ],
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

接着在项目目录下执行 mm 命令,编译当前模块。接着我们把模拟器打开,然后把可执行文件 push 到模拟器上:

adb push out/target/product/rice14/system/bin/BinderCallbackServer /data/local/tmp
adb push out/target/product/rice14/system/bin/BinderCallbackClient /data/local/tmp
1
2

接着进入模拟器 shell 环境,执行可执行文件:

adb shell
cd /data/local/tmp/
#执行服务端
./BinderCallbackServer &
#执行客户端
./BinderCallbackClient &
1
2
3
4
5
6

接着查看 log:

从 log 的内容可以看出,我们的回调函数执行成功了。

# 关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,主要研究方向是 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。