[Android]文件描述符的binder传送
在android中,通过传送文件描述符来实现文件数据共享是个很强大的功能,binder对文件描述符的传送进行了支持。
在 Android 中,Binder 是进程间通信(IPC)的核心机制,而文件描述符(File Descriptor, FD)的跨进程传递是一个关键功能。由于文件描述符是进程内局部资源(不同进程中相同的 FD 值可能指向不同的文件),直接传递 FD 的整数值是无效的。Binder 通过内核驱动和 Parcel
机制实现了 FD 的安全跨进程传递。
1. 基本原理
Binder 传递文件描述符的核心原理是 FD 的复制与重映射:
-
发送进程将 FD 写入
Parcel
,Binder 驱动会将该 FD 转换为对内核中文件对象(struct file
)的引用。 -
接收进程从
Parcel
中读取时,Binder 驱动会在接收进程中创建一个新的 FD,并指向同一个内核文件对象。 -
内核通过引用计数管理文件对象,确保文件资源在所有进程关闭 FD 后才会释放。
2. 实现步骤
2.1 发送方(服务端)
-
创建
ParcelFileDescriptor
:将原始 FD 包装为 Android 提供的对象。java
复制
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(rawFd);
-
将 FD 写入
Parcel
:通过Parcel.writeFileDescriptor()
方法。java
复制
Parcel data = Parcel.obtain(); data.writeFileDescriptor(pfd.getFileDescriptor());
2.2 Binder 驱动处理
-
Binder 驱动解析
Parcel
中的 FD,将其转换为对内核文件对象的引用,并记录在binder_transaction_data
结构体中。 -
在接收进程的内核空间,驱动为该文件对象创建新的 FD,并映射到接收进程的 FD 表中。
2.3 接收方(客户端)
-
从
Parcel
读取 FD:使用Parcel.readFileDescriptor()
。java
复制
Parcel reply = Parcel.obtain(); ParcelFileDescriptor pfd = reply.readFileDescriptor();
-
获取原始 FD:通过
ParcelFileDescriptor
对象。java
复制
int receivedFd = pfd.getFileDescriptor();
3. 关键类与 API
-
ParcelFileDescriptor
:Android 提供的类,封装 FD 并支持跨进程传递。 -
Parcel.writeFileDescriptor()
/Parcel.readFileDescriptor()
:序列化和反序列化 FD 的核心方法。 -
FileDescriptor
:Java 层对原始 FD 的抽象。
4. 注意事项
-
资源管理:
-
必须显式关闭 FD:发送方和接收方需要各自调用
ParcelFileDescriptor.close()
,避免资源泄漏。 -
使用
try-with-resources
(Java)或use
(Kotlin)确保自动关闭。kotlin
复制
pfd.use { fd -> // 使用 fd }
-
-
权限控制:
-
接收进程需有访问文件的权限(如通过
ContentProvider
或文件路径权限授予)。 -
Binder 仅传递 FD,不检查文件路径权限,需确保逻辑安全。
-
-
跨进程稳定性:
-
ParcelFileDescriptor
实现了Parcelable
,可安全跨进程传递。 -
在 AIDL 接口中直接使用
ParcelFileDescriptor
类型:java
复制
interface IMyService { void sendFile(in ParcelFileDescriptor pfd); }
-
5. 底层机制
-
Binder 驱动:在
binder_transaction()
函数中处理 FD 数组(fd_array
),通过dup_fd_to_user()
和task_get_unused_fd_flags()
在接收进程创建新 FD。 -
内核对象引用:传递的 FD 实际指向内核的
struct file
,通过增加引用计数避免提前释放。
6. 示例代码
服务端发送 FD
java
复制
// 打开文件获取 FD FileInputStream fis = new FileInputStream("test.txt"); ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fis.getFD()); // 通过 Binder 传递 Parcel data = Parcel.obtain(); data.writeFileDescriptor(pfd.getFileDescriptor()); binder.transact(TRANSFER_FD, data, null, 0);
客户端接收 FD
java
复制
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) { if (code == TRANSFER_FD) { ParcelFileDescriptor pfd = data.readFileDescriptor(); // 使用 pfd 操作文件 return true; } return super.onTransact(code, data, reply, flags); }
总结
通过 Binder 传递文件描述符,本质是借助内核将 FD 转换为对文件对象的引用,并在接收进程重新映射为新 FD。开发者需关注 ParcelFileDescriptor
的使用、资源释放和权限控制,确保跨进程文件访问的安全性与稳定性。
hal层文件描述符
数据类型 | Android Open Source Project
hal文件中定义为handle类型,
java层使用android.os.NativeHandle
hal层使用android::hardware::hidl_handle的data[0] 即为对应的fd
句柄和内存
Android 11 引入了对 handle 和 memory 类型的 Java 支持。这两种类型分别转换为 android.os.NativeHandle 和 android.os.HidlMemory。null 句柄会被视为有效,而 null 内存则不会。
在生成的服务器代码中,接收的内存和句柄参数仅在方法调用范围内有效。如果服务器实现希望延长其生命周期,则必须使用其各自的 dup() 方法进行复制。返回的实例可在方法调用范围之外使用,且应在使用后正确关闭。
在生成的客户端代码中,作为调用的方法的输入参数发送的句柄和内存实例无需进行复制,也无需在方法返回后保持有效状态。但是,作为输出参数接收的句柄和内存实例将由自动生成的代码自动复制,且必须在复制完成后正确关闭。无论这些返回参数显示为方法的返回值(在存在单个返回值的情况下),还是使用同步回调样式(用于存在多个返回值的情况),均是如此。
如需详细了解复制与关闭,请参阅 Java 类文档