Android Binder 子系统学习随笔

其实本文在 2023 年 5 月写下,一直放着没有整理,直到今天。

Binder Driver

驱动层到底干了什么,binder 框架对应的是 C/S之间的通讯,而 binder 是这中间的桥梁,将发送方的信息传递给接收方。binder 处理了这其中底层的数据交换

  • 打开 binder 设备
  • mmap 映射内存:此操作将用户控件的内存映射到内核空间中
  • 传递客户端与服务端的数据
  • 托管线程管理

flat_binder_object

这是 binder 中用来跨进程传递对象的结构体。

1
2
3
4
5
6
7
8
9
10
struct flat_binder_object {
__u32 type;
__u32 flags;

union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote object */
};
binder_uintptr_t cookie;
};

其中的 type 为

1
2
3
4
5
6
7
8
9
enum {
// 用来标记当前传递的是 binder 实体
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
// 用来标记当前传递的是 binder 在 driver 中的引用地址
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
};

当 Server 把 binder 实体传递给 Client 的时候,Server 携带的 flat_binder_object.binder 是 binder 实体在 Server 用户空间的内存,driver 会自动的将此变量存储为 driver 中保存的为 client 创建的 binder 实体引用指针,然后再将 flat_binder_object 传递给 client,client 才能将此指针填入到 binder_transation_datatarget.handle 域中,才能正确的像 Server 发起 IPC 请求。

驱动层的线程管理

驱动从最开始设计就为多线程做了支持,线程实际在 C/S 端去创建,驱动作为管理者通知 C/S 端来创建线程、退出线程等。

问题

  • 线程用完就会退出吗?
  • 驱动根据进程有无空闲的线程来决定是否创建线程吗?
  • 最大线程数就是就是一个 Server 的最大并发吗?

ServiceManager

系统中只允许曾在一个 ServiceManager 进程。ServiceManager 只将唯一的服务名称与其在 driver 中的引用号做了映射,其他 client 随用随取,所以他的工作很简单。只有 128K 的缓存空间。

问题

  • ServiceManager 的线程管理,最大线程数

debugfs

存储的位置位于 /sys/kernel/debug/binder

使用方式:

  • /sys/kernel/debug/binder/proc 查看有哪些进程使用了 binder
  • transaction_log & transactions 文件查看 binder 的通信信息

C++ 层的 Binder

Framework 层的 binder 将实现分为 native 和 proxy 两端,native 端对应的是服务端的实现,proxy 端对应的是服务的远程接口,使用服务的 client 将会拿到 proxy 端用来发起 RPC 请求。proxy 和 native 内部通过 binder 协议进行中转。client 调用 Foo.bar() 函数的时候,会将函数号、参数信息传递给 binder driver 中,binder 跨进程访问 native 实现的对应函数。实现 RPC 调用。

binder 类结构

为什么要这么设计。interface 的设计逻辑是什么。

IInterface 和 IBinder 本质上是同一个层次的抽象。IInterface 承载了服务的借口,IBinder 则标志了这是一个 binder 服务。

IInterface.h 中定义了如下的模版类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;

protected:
typedef INTERFACE BaseInterface;
virtual IBinder* onAsBinder();
};

// ----------------------------------------------------------------------

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
explicit BpInterface(const sp<IBinder>& remote);

protected:
typedef INTERFACE BaseInterface;
virtual IBinder* onAsBinder();
};

我现在认为这两个模版类的作用只是方便开发者实现 BnXXX 和 BpXXX,自动继承自不同的 BBinder 或 BpRefBase。

BnInterface & BpInterface 继承自用户实现的 IXxxInterface 为了约束 binder 的功能接口作为公共的借口提供给 BpXx 和 BnXxx,实现端与代理端从 BnInterface & BpInterface 才开始分叉。

为了满足 Binder 的实现规则,

  • 每一个 binder 服务都需要定义一个唯一的标识符,写作 descriptor,通过 getInterfaceDescriptor() 访问
  • 为了便于调用者获取到服务借口,公共 Interface 需要定义一个 android::sp<IXXX> asInterface 来返回基类对象指针。

IInterface.h 实现了如下的宏,用于开发者快速定义

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
// ----------------------------------------------------------------------

#define DECLARE_META_INTERFACE(INTERFACE) \
public: \
static const ::android::String16 descriptor; \
static ::android::sp<I##INTERFACE> asInterface(const ::android::sp<::android::IBinder>& obj); \
virtual const ::android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
static bool setDefaultImpl(::android::sp<I##INTERFACE> impl); \
static const ::android::sp<I##INTERFACE>& getDefaultImpl(); \
\
private: \
static ::android::sp<I##INTERFACE> default_impl; \
\
public:

#define __IINTF_CONCAT(x, y) (x ## y)

#ifndef DO_NOT_CHECK_MANUAL_BINDER_INTERFACES

#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
static_assert(internal::allowedManualInterface(NAME), \
"b/64223827: Manually written binder interfaces are " \
"considered error prone and frequently have bugs. " \
"The preferred way to add interfaces is to define " \
"an .aidl file to auto-generate the interface. If " \
"an interface must be manually written, add its " \
"name to the whitelist."); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \

#else

#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \

#endif

// Macro to be used by both IMPLEMENT_META_INTERFACE and IMPLEMENT_META_NESTED_INTERFACE
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(ITYPE, INAME, BPTYPE) \
const ::android::String16& ITYPE::getInterfaceDescriptor() const { return ITYPE::descriptor; } \
::android::sp<ITYPE> ITYPE::asInterface(const ::android::sp<::android::IBinder>& obj) { \
::android::sp<ITYPE> intr; \
if (obj != nullptr) { \
intr = ::android::sp<ITYPE>::cast(obj->queryLocalInterface(ITYPE::descriptor)); \
if (intr == nullptr) { \
intr = ::android::sp<BPTYPE>::make(obj); \
} \
} \
return intr; \
} \
::android::sp<ITYPE> ITYPE::default_impl; \
bool ITYPE::setDefaultImpl(::android::sp<ITYPE> impl) { \
/* Only one user of this interface can use this function */ \
/* at a time. This is a heuristic to detect if two different */ \
/* users in the same process use this function. */ \
assert(!ITYPE::default_impl); \
if (impl) { \
ITYPE::default_impl = std::move(impl); \
return true; \
} \
return false; \
} \
const ::android::sp<ITYPE>& ITYPE::getDefaultImpl() { return ITYPE::default_impl; } \
ITYPE::INAME() {} \
ITYPE::~INAME() {}

// Macro for an interface type.
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const ::android::StaticString16 I##INTERFACE##_descriptor_static_str16( \
__IINTF_CONCAT(u, NAME)); \
const ::android::String16 I##INTERFACE::descriptor(I##INTERFACE##_descriptor_static_str16); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(I##INTERFACE, I##INTERFACE, Bp##INTERFACE)

// Macro for "nested" interface type.
// For example,
// class Parent .. { class INested .. { }; };
// DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(Parent, Nested, "Parent.INested")
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(PARENT, INTERFACE, NAME) \
const ::android::String16 PARENT::I##INTERFACE::descriptor(NAME); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(PARENT::I##INTERFACE, I##INTERFACE, \
PARENT::Bp##INTERFACE)

#define CHECK_INTERFACE(interface, data, reply) \
do { \
if (!(data).checkInterface(this)) { return PERMISSION_DENIED; } \
} while (false) \


// ----------------------------------------------------------------------

有了这两个宏之后,开发者只要在接口基类(IXXX)头文件中,使用 DECLARE_META_INTERFACE 宏便完成了需要的组件的声明。然后在 cpp 文件中使用 IMPLEMENT_META_INTERFACE 便完成了这些组件的实现。

Binder 的初始化

所有使用到 Binder 的进程在开始使用 Binder 进行 IPC 之前都需要打开 binder 设备并且 mmap 映射内存。这部分逻辑因为是进程共有的,所以定义到了 framework 的 ProcessState 类中。

ProcessState 实例为单例,进程唯一,进程只需要打开一次 binder 设备与 mmap 映射。

在 Android Fragment 中集成 React Native

这里想介绍的是如果我们想在一个已有的项目里独立的集成 React Native,可能我只需要一个特定的简单页面通过 React Native 实现动态化,并不想像文档那样接入 React Native 的 gradle 插件来实现。

先增加 ReactNative 的依赖到 app 模块的 build.gradle

1
2
3
4
dependencies {
implementation("com.facebook.react:react-android:0.75.2")
implementation("com.facebook.react:hermes-android:0.75.2")
}

Sync 项目之后可以实现 ReactNative 的入口 Fragment 文件。

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
class ReactNativeFragment : Fragment() {

private lateinit var rootView: ReactRootView
private lateinit var reactInstanceManager: ReactInstanceManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 初始化 RN 的 so 库
SoLoader.init(requireContext(), false)

rootView = ReactRootView(requireContext())
reactInstanceManager = ReactInstanceManager.builder()
// 传入 Application 实例
.setApplication(globalApp)
.setCurrentActivity(requireActivity())
// 传入 asset 文件中 RN bundle 的文件名,也可以传入本地文件
.setBundleAssetName("index.android.bundle")
.setUseDeveloperSupport(false)
// 这里可以通过传入自定义的 package 来支持 RN 原生模块和原生 View 组件
.addPackages(listOf(MainReactPackage()))
.setInitialLifecycleState(LifecycleState.RESUMED)
.setJSMainModulePath("index")
.setDefaultHardwareBackBtnHandler {
Log.d(TAG, "onCreate: defaultHardwareBackHandler handle")
}
.build()

// 启动 RN 应用
rootView.startReactApplication(
reactInstanceManager,
// 传入 RN 中通过 AppRegistry.registerComponent() 注册的组件名
"react_native_cli_init",
// 在这里通过 Bundle 给 RN 组件传递参数,RN 组件会通过 props 接收参数
arguments
)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View = rootView // 返回创建的 ReactRootView 实例
}

这样就实现了基本的 React Native 组件嵌入 Android Fragment 中。

HarmonyOS 鸿蒙上使用 Compose imePadding 无效的问题

先说结论:

Manifest 中 Activity 的 android:windowSoftInputMode 标记避免使用 adjustNothing,而是使用 adjustResize 等其他 “adjust” 值。


使用 Compose 构建的登录页面在鸿蒙系统上遇到了给 Composable 组件设置 imePadding 无效的问题,在网络上没有找到相关的博客,大家都在关注使用 KMP 构建纯血鸿蒙的方案。

想到了 处理输入法可见性,才发现我项目中设置的是 adjustNothing,改成 adjustResize 就可以了。