Posts 实战:使用 Binder 机制实现 C/S 架构
Post
Cancel

实战:使用 Binder 机制实现 C/S 架构

第一种:native 层实现

声明 service 业务接口

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
#ifndef ANDROID_ITESTSERVICE_H
#define ANDROID_ITESTSERVICE_H

#include <binder/IInterface.h>
#include <binder/Parcel.h>

// 命名空间为 android
namespace android {

// 声明业务接口,子类实现接口处理具体业务
class ITest : public IInterface {
public:
    // !!一定不要忘记了这里!!
    DECLARE_META_INTERFACE(Test)

    // 子类实现以下接口处理业务函数
    virtual void write();
    virtual void read();
};

// BnTest 根据请求码 code 对客户端的请求进行转发,实际工作由 ITest 子类完成,
// BnInterface 既是模板类又是多继承,其原型为:
// template<typename INTERFACE>
// class BnInterface : public INTERFACE, public BBinder {}
class BnTest : public BnInterface<ITest> {
public:
    virtual status_t onTransact(uint32_t code, const Parcel &data,
                                Parcel *reply, uint32_t flags = 0);
};
} // namespace android
#endif // ANDROID_ITESTSERVICE_H

实现 service 业务方法

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
64
65
66
67
68
69
70
// 定义 log tag, ALOGx() 中会用到
#define LOG_TAG "TestServer"

#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include "ITest.h"

namespace android {

const uint32_t CODE_WRITE = 0x10;
const uint32_t CODE_READ = 0x11;

class BpTest : public BpInterface<ITest> {
public:
    BpTest(const sp<IBinder> &impl)
            : BpInterface<ITest>(impl) {}

    // 与 ITest 中声明的业务函数同名,原因是 BpInterface 是多继承类 & 模板类,
    // 其原型为: template<typename INTERFACE>
    // class BpInterface : public INTERFACE, public BpRefBase {...}
    void write() override {
        Parcel data, reply;
        data.writeInterfaceToken(ITest::getInterfaceDescriptor());
        // 转发到 BnTest::onTransact()
        ALOGD("BpTest::write() start transact %s cmd(%d)", "WRITE", CODE_WRITE);
        remote()->transact(CODE_WRITE, data, &reply);
    }

    void read() override {
        Parcel data, reply;
        data.writeInterfaceToken(ITest::getInterfaceDescriptor());
        // 转发到 BnTest::onTransact()
        ALOGD("BpTest::read() start transact %s cmd(%d)", "READ", CODE_READ);
        remote()->transact(CODE_READ, data, &reply);
    }
};

// 实现 BnTest::onTransact()
status_t BnTest::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
    switch (code) {
        case CODE_READ:
            CHECK_INTERFACE(ITest, data, reply)
            ALOGD("BnTest::%s() read(%d), flags(%d)", __FUNCTION__, code, flags);
            // 这里调用的是 ITest::read() 函数
            read();
            return NO_ERROR;
        case CODE_WRITE:
            CHECK_INTERFACE(ITest, data, reply)
            ALOGD("BnTest::%s() write(%d), flags(%d)", __FUNCTION__, code, flags);
            // 这里调用的是 ITest::write() 函数
            write();
            return NO_ERROR;
        default:
            return BnInterface::onTransact(code, data, reply);
    }
}

// 实现 ITest::read() 接口,处理实际的业务逻辑
void ITest::read() {
    ALOGD("ITest::read() called, start do hard work");
}

// 实现 ITest::write() 接口,处理实际的业务逻辑
void ITest::write() {
    ALOGD("ITest::write() called, start do hard work");
}
// !!一定不要忘记了这里!!
IMPLEMENT_META_INTERFACE(Test, "android.test.ITest")
} // namespace android

编写 TestServer.cpp 文件实现 server 端

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
#define LOG_TAG "TestService"

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include "ITest.h"

// 使用 android 命名空间
using namespace android;

int main(int count, char **argv) {
    // count 为参数个数,argv[0] 表示可执行程序本身
    ALOGI("sample service start count: %d, argv[0]: %s", count, argv[0]);

    // 创建单例
    sp<ProcessState> proc(ProcessState::self());

    // 获取 service manager
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("defaultServiceManager(): %p", sm.get());

    // 注意 BnTest 的继承关系如下:
    //              IBinder
    //                ^^
    //                ||
    //              BBinder
    //                ^^
    //                ||
    //           BnInterface
    //                ^^
    //                ||
    //              BnTest
    // 即实际上 BnTest 就是一个 IBinder
    // 注册服务
    sm->addService(String16("sample.service"), new BnTest()/*binder*/);
    ALOGI("add sample.service to service manager");

    // 开启工作线程
    ProcessState::self()->startThreadPool();
    // 加入到主线程
    IPCThreadState::self()->joinThreadPool();
    return 0;
}

编写 Android.bp 文件并编译出 service 端可执行程序

// 编译二进制可执行程序:sample_service
cc_binary {
    // 可执行程序的名字
    name: "sample_service",
    // 使用哪些源文件来进行编译
    srcs: [
        "ITest.cpp",
        "TestServer.cpp",
    ],
    // 包含的头文件所在的目录
    include_dirs: [
        "vendor/grandstream/test-service/native"
    ],
    // 使用到哪些共享库
    shared_libs: [
        "libbinder",
        "libcutils",
        "liblog",
        "libutils",
    ],
    // c flags
    cflags: [
        "-Werror",
        "-Wno-error=deprecated-declarations",
        "-Wall",
    ],
}

至此,程序源代码目录结构如下:

1
2
3
4
├── Android.bp
├── ITest.cpp
├── ITest.h
└── TestServer.cpp

然后在源码环境下进入目录,执行 mm 命令进行编译生成 sample_server 程序: out/target/product/xxx/system/bin/sample_server,最后将该程序 push 到 设备中:

1
adb push out/target/product/xxx/system/bin/sample_service /system/bin/sample_service

编写 TestClient.cpp 文件实现 client 端

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
#define LOG_TAG "TestClient"

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include "ITest.h"

// 使用 android 命名空间
using namespace android;

int main(int count, char **argv) {
    ALOGI("sample client start count: %d, argv[0]: %s", count, argv[0]);

    // 获取 service manager
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("defaultServiceManager(): %p", sm.get());

    // 获取注册的 sample.service 服务
    sp<IBinder> binder = sm->getService(String16("sample.service"));
    if (binder == NULL) {
        ALOGE("sample service binder is null, abort...");
        return -1;
    }
    ALOGI("sample service binder is %p", binder.get());

    // 通过 interface_cast 还原 ITest 接口
    sp<ITest> service = interface_cast<ITest>(binder);
    ALOGI("sample service is %p", service.get());

    // 使用服务提供的业务接口
    service->write();
    service->read();
    return 0;
}

修改 Android.bp 文件并编译出 client 可执行程序

1、修改 Android.bp 文件追加如下内容:

// 编译二进制可执行程序:sample_client
cc_binary {
    name: "sample_client",
    srcs: [
        "ITest.cpp",
        "TestClient.cpp",
    ],
    include_dirs: [
        "vendor/grandstream/test-service/native"
    ],
    shared_libs: [
        "libbinder",
        "libcutils",
        "liblog",
        "libutils",
    ],
    cflags: [
        "-Werror",
        "-Wno-error=deprecated-declarations",
        "-Wall",
    ],
}

2、编译出 sample_client 可执行程序并且 push 到设备中

然后在源码环境下进入目录,执行 mm 命令进行编译生成 sample_client 程序: out/target/product/xxx/system/bin/sample_client,最后将该程序 push 到设备中:

1
adb push out/target/product/xxx/system/bin/sample_client /system/bin/sample_client

3、运行程序观察调用过程

进入串口先执行 sample_service(或/system/bin/sample_service) 启动 service, 在执行 sample_client (或/system/bin/sample_client) 向 server 发送请求,在 logcat 中可观察到如下日志输出:

I/TestService: sample service start count: 1, argv[0]: sample_service
I/TestService: defaultServiceManager(): 0x729e23d180
I/TestService: add sample.service to service manager
I/TestClient: sample client start count: 1, argv[0]: system/bin/sample_client
I/TestClient: defaultServiceManager(): 0x76f023d180
I/TestClient: sample service binder is 0x76f024b140
I/TestClient: sample service is 0x76f023d1c0
D/TestServer: BpTest::write() start transact WRITE cmd(16)
D/TestServer: BnTest::onTransact() write(16), flags(16)
D/TestServer: ITest::write() called, start do hard work
D/TestServer: BpTest::read() start transact READ cmd(17)
D/TestServer: BnTest::onTransact() read(17), flags(16)
D/TestServer: ITest::read() called, start do hard work

总结:

从以上日志输出可以看出,当对某一 service 中的业务函数发起调用时,首先调用 BpXxx(即 BpInterface),接着调用 BnXxx(即 BnInterface),最终调用 IXxx(即 IInterface)

本例源码:binder server(native)


第二种:java 层实现

实现 server 端

1、编写 AIDL 文件 DataStruct.aidlITest.aidl

1.1、 ITest.aidl 文件

package com.sleticalboy.sample.service;
// aidl 文件中,即使文件是在同一个 package 下,也要显示导入
import com.sleticalboy.sample.service.DataStruct;

interface ITest {
    void doWrite(in DataStruct data);
    void doRead(in String name, in boolean notify);
}

1.2、 ITest.aidl 文件

package com.sleticalboy.sample.service;
// 切记,当 java 类实现 Parcelable 接口时,在 aidl 中要用 parcelable 关键字
parcelable DataStruct;

可以使用 aidl 命令先生成对应的 java 文件以方便代码编写

1
aidl -Iaidl/ -osrc/ aidl/com/sleticalboy/sample/service/ITest.aidl

2、编写 server 端

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
package com.sleticalboy.sample.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class SampleService extends Service {

    private static final String TAG = "SampleService";

    private final IBinder mBinder = new ITest.Stub() {
        @Override
        public void doWrite(DataStruct data) throws RemoteException {
            Log.d(TAG, "doWrite() data = [" + data + "]");
        }

        @Override
        public void doRead(String name, boolean notify) throws RemoteException {
            Log.d(TAG, "doRead() name = [" + name + "], notify = [" + notify + "]");
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

3、编写 Android.bp 文件并编译出 SampleService apk

3.1、Android.bp 文件内容如下:

// 编译 Android apk
android_app {
    // apk 名字
    name: "SampleService",
    // 源文件: .java/.aidl/.logtags/.proto
    srcs: [
        "aidl/com/sleticalboy/sample/service/ITest.aidl",
        "src/com/sleticalboy/sample/service/**/*.java",
    ],
    aidl: {
        // 本地目录(与 Android.bp 文件同级的目录)
        local_include_dirs: ["aidl"],
        // 猜测与日志输出相关
        generate_traces: true,
    },
    // 签名文件,此处表示与平台使用相同的签名文件
    certificate: "platform",
    // 清单文件
    manifest: "AndroidManifest.xml",
    // 静态库,
    static_libs: [
        "android-common",
    ],
    // 优化开关
    optimize: {
        enabled: false,
    },
}

3.2、执行 mmm 命令进行编译

1
mmm vendor/sleticalboy/sample_service/java/

3.3、将编译出的 service apk 安装到设备中

1
adb install -r -d -t out/target/product/xxx/system/priv-app/SampleService/SampleService.apk

4、server 端目录结构如下:

├── aidl
│   └── com
│       └── sleticalboy
│           └── sample
│               └── service
│                   ├── DataStruct.aidl
│                   └── ITest.aidl
├── Android.bp
├── AndroidManifest.xml
├── build_manul.sh
└── src
    └── com
        └── sleticalboy
            └── sample
                └── service
                    ├── DataStruct.java
                    └── SampleService.java

本例源码:binder server(java)

实现 client 端

1、直接在 Activity 中访问服务端

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.sleticalboy.sample.client;

import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.widget.Toast;

import com.sleticalboy.sample.client.R;

import com.sleticalboy.sample.service.DataStruct;
import com.sleticalboy.sample.service.ITest;

public class SampleClient extends Activity {

    private static final String TAG = "SampleClient";

    private ITest mService;
    private final ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.d(TAG, "onServiceConnected() name: " + name + ", binder: " + binder);
            mService = ITest.Stub.asInterface(binder);
            SampleClient.this.onServiceConnected();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };

    private void onServiceConnected() {
        if (mService != null) {
            Toast.makeText(this, R.string.service_bind_ok, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.sample_client);

        doBindeService();

        initViews();
    }

    private void initViews() {
        findViewById(R.id.btn_write).setOnClickListener(v -> {
            if (mService == null) {
                return;
            }
            try {
                DataStruct data = new DataStruct();
                data.mName = "data to server";
                data.mNotify = true;
                mService.doWrite(data);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        });
        findViewById(R.id.btn_read).setOnClickListener(v -> {
            if (mService == null) {
                return;
            }
            try {
                mService.doRead("client", false);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        });
        findViewById(R.id.btn_native).setOnClickListener(v -> {
            // 只要有权限,是能查询到 ServiceManager 中注册的 service
            IBinder service = ServiceManager.getService("sample.service");
            Log.w(TAG, "sample.service: " + service);
        });
    }

    private void doBindeService() {
        Intent intent = new Intent();
        intent.setClassName("com.sleticalboy.sample.service",
                "com.sleticalboy.sample.service.SampleService");
        boolean bound = bindService(intent, mConn, BIND_AUTO_CREATE);
        Log.d(TAG, "bind service: " + bound);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConn);
    }
}

2、编写 Android.bp 文件并编译出 SampleClient apk

2.1、Android.bp 文件内容如下

android_app {
    name: "SampleClient",
    srcs: [
        "src/com/sleticalboy/sample/client/**/*.java",
        "../sample_service/java/src/com/sleticalboy/sample/service/**/*.java",
        "../sample_service/java/aidl/com/sleticalboy/sample/service/ITest.aidl",
    ],
    aidl: {
        local_include_dirs: ["../sample_service/java/aidl"],
        generate_traces: true,
    },
    certificate: "platform",
    manifest: "AndroidManifest.xml",
    static_libs: [
        "android-common",
    ],
    optimize: {
        enabled: false,
    },
}

2.2、执行 mmm 命令进行编译

1
mmm vendor/sleticalboy/sample_client/

2.3、将编译出的 client apk 安装到设备中

1
adb install -r -d -t out/target/product/xxx/system/priv-app/SampleClient/SampleClient.apk

3、client 端目录结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
├── Android.bp
├── AndroidManifest.xml
├── res
│   ├── layout
│   │   └── sample_client.xml
│   └── values
│       └── strings.xml
└── src
    └── com
        └── sleticalboy
            └── sample
                └── client
                    └── SampleClient.java

client 访问 service 运行输出如下:

// 1、客户端发起绑定服务
sample.client D/SampleClient: bind service: true
// 2、服务端开始创建
sample.service D/SampleService: onCreate() called
// 3、服务端收到客户端绑定请求
sample.service D/SampleService: onBind() called
// 4、客户端绑定服务成功
sample.client D/SampleClient: onServiceConnected() name: ComponentInfo{}, binder: android.os.BinderProxy@1ff5453
// 5、服务端收到客户端的 write 调用
sample.service D/SampleService: doWrite() data = [DataStruct{mName='data to server', mNotify=true}]
// 6、服务端收到客户端的 read 调用
sample.service D/SampleService: doRead() name = [client], notify = [false]

本例源码:binder client


参考资料

aidl 使用文档

aidl 工具的源码在 ${ANDROID_ROOT}/system/tools/aidl 目录下,分为 java 和 cpp 两类,分别可以通过输入 aidl 文件生成 java 文件和 cpp 文件

INPUT required
usage: aidl OPTIONS INPUT [OUTPUT]
       aidl --preprocess OUTPUT INPUT...

OPTIONS:
   -I<DIR>    search path for import statements.可以去哪个目录下寻找import文件
   -d<FILE>   generate dependency file.
   -a         generate dependency file next to the output file with the name based on the input file.
   -ninja     generate dependency file in a format ninja understands.
   -p<FILE>   file created by --preprocess to import.
   -o<FOLDER> base output folder for generated files. 生成的java文件输出目录
   -b         fail when trying to compile a parcelable.
   -t         include tracing code for systrace. Note that if either the client or server code is not auto-generated by this tool, that part will not be traced.

INPUT:
   An aidl interface file.

OUTPUT:
   The generated interface files.
   If omitted and the -o option is not used, the input filename is used, with the .aidl extension changed to a .java extension.
   If the -o option is used, the generated files will be placed in the base output folder, under their package folder

androidbp 文件格式

编译源码时会自动生成相关的文档,文档具体路径在: ${ANDROID_ROOT}/out/soong/docs/soong_build.html

This post is licensed under CC BY 4.0 by the author.