Android面试冲击附答案(一)————Binder机制

4 阅读8分钟

Android面试冲击附答案(一)————Binder机制


一、下边问题都能答上来吗?答不上来下滑复习。

  1. 简述Binder机制核心流程
  2. 为什么Binder机制只需要一次拷贝?一次拷贝是从哪里拷贝到哪里?拷贝的是什么?
  3. ServiceManager是什么?怎么被找到的?一个进程有多个Binder服务时怎么区别?
  4. Intent为什么不能传递对象?同一个进程里,如果通过Binder机制来传递数据,还会序列化反序列化吗?
  5. 为什么Zygote和AMS通信使用Socket不用Binder?
  6. Binder线程池大小是多少?Binder线程池在什么时候创建的?

二、Binder机制原理

为什么需要IPC?

Android基于Linux,每个进程拥有独立的虚拟地址空间,进程间内存天然隔离,无法直接访问彼此的数据。IPC(进程间通信)就是解决这个问题的机制。

传统IPC的问题

传统IPC(如管道、Socket)需要两次数据拷贝:

  • 发送方:用户空间 → 内核空间(第一次拷贝)
  • 接收方:内核空间 → 用户空间(第二次拷贝)

效率低,且无法验证调用方身份,安全性差。

Binder的核心设计

Binder基于CS架构,由四个核心角色组成:

角色说明
Client发起IPC请求的进程
Server提供服务,向ServiceManager注册
ServiceManagerBinder注册中心,handle固定为0
Binder驱动运行在内核态,负责数据转发和地址映射,是通信核心

为什么只需要一次拷贝?

Binder驱动利用mmap内存映射实现:

  1. Binder驱动在内核空间分配一块缓冲区
  2. 通过mmap将这块内存同时映射到接收方的用户空间
  3. 发送方通过copy_from_user将数据拷贝到内核缓冲区(唯一一次拷贝)
  4. 接收方直接读取映射的内存,无需再次拷贝

mmap本质:把同一块物理内存,同时映射到两个虚拟地址空间。

Binder通信核心流程

Server ──注册服务──▶ ServiceManager
                          │
Client ──查询服务──▶ ServiceManager ──返回BinderProxy──▶ Client
                          
Client ──调用方法──▶ Parcel序列化 ──▶ Binder驱动 ──copy_from_user──▶ 内核缓冲区
                                                                          │ mmap
                                                               Server用户空间 ◀──读取
                                                                          │
Client ◀──返回结果────────────────── Binder驱动 ◀──写回结果────────────────

代理模式的作用

不同进程的对象内存地址不通用,无法直接传递对象。Binder驱动在传递时做转换:

  • Server进程持有真实的Binder对象
  • Client进程拿到的是BinderProxy代理对象
  • Proxy封装了序列化、transact、反序列化的全过程,对Client透明

安全性保障

  1. 内核层自动填充调用方的UID和PID,Client无法伪造
  2. Server可通过Binder.getCallingUid()Binder.getCallingPid()验证调用方身份
  3. 系统服务根据UID判断调用方是否有权限

三、面试题与答案

1. 简述Binder机制?相比传统IPC方式有什么优点?

Binder是Android核心的IPC机制,基于CS架构。

相比传统IPC的优点:

  • 效率高:只需一次数据拷贝(传统IPC需要两次)
  • 安全性高:内核自动填充调用方UID/PID,无法伪造身份
  • 使用方便:通过AIDL封装,调用远程方法像调用本地方法一样
2. Binder机制核心成员和作用?
  1. Client:发起IPC请求
  2. Server:提供服务,启动后向ServiceManager注册
  3. ServiceManager:管理所有服务的注册与查询,handle固定为0
  4. Binder驱动:处于内核态,负责进程间数据转发和地址映射,是通信核心
3. 简述Binder机制核心流程
  1. Server启动后向ServiceManager注册服务
  2. Client通过handle=0找到ServiceManager,查询目标服务
  3. ServiceManager返回Server的代理对象(BinderProxy)给Client
  4. Client调用代理方法,将数据封装成Parcel,发起transact请求
  5. Binder驱动接收数据,copy_from_user拷贝到内核缓冲区
  6. 内核缓冲区通过mmap映射到Server用户空间,Server直接读取
  7. Server处理完后,通过Binder驱动原路返回结果

补充:ServiceManager本身也通过Binder驱动注册,驱动设置其handle=0,全局唯一。

4. 为什么Binder机制只需要一次拷贝?(mmap的原理)

传统IPC需要两次拷贝:用户空间→内核空间→用户空间。

Binder通过mmap优化:

  • Binder驱动在内核空间分配缓冲区
  • 通过mmap将这块内存映射到接收方用户空间
  • 发送方只需copy_from_user拷贝一次到内核缓冲区
  • 接收方直接读取映射内存,无需第二次拷贝

mmap本质:把同一块物理内存,同时映射到两个虚拟地址空间。

5. 一次拷贝是从哪里拷贝到哪里?拷贝的是什么?

通过copy_from_user方法,从发送方用户空间拷贝到Binder驱动在内核空间开辟的缓冲区

拷贝的内容是发送方的Parcel数据,包括:

  • 基本数据类型
  • 序列化后的对象
  • 文件描述符(fd)
6. ServiceManager是什么?怎么被找到的?

ServiceManager是Binder的注册中心,所有系统服务(AMS、WMS等)启动时都要向它注册。

怎么被找到:它的handle被Binder驱动固定设置为0,任何进程都可以直接通过handle=0找到它,不需要查询。

ServiceManager在init进程初始化时创建,运行在独立的用户空间,全局唯一。

7. 一个进程有多个Binder服务时怎么区别?

通过句柄handle区分。每个Binder服务在Binder驱动中都有唯一的handle,Client通过handle来定位具体的Server。

8. Binder对象在跨进程传递时是怎么处理的?

不直接跨进程传递,Binder驱动会做转换:

  • 真实的Binder对象存在于Server进程中
  • Client获取到的是BinderProxy代理对象
  • Client通过代理对象生成Parcel发起请求,Binder驱动转发到真实的Binder对象处理
9. 为什么Binder机制需要使用代理模式?
  1. 不同进程间内存地址不通用,无法直接传递对象
  2. Proxy封装了跨进程的序列化、transact、反序列化全过程,对Client完全透明,调用远程方法就像调用本地方法
10. 同一个进程里,通过Binder机制传递数据,还会序列化反序列化吗?

不会。asInterface()内部通过queryLocalInterface()判断IBinder是否是本地Binder对象:

  • 同进程:直接返回Stub,不走IPC,不序列化
  • 跨进程:返回BinderProxy,走IPC,需要序列化

延伸:哪些是同进程IPC场景?

  • 同APP内的bindService()
  • onServiceConnected回调里调用IMyService.Stub.asInterface(binder)

四大组件间的交互是同进程吗? 生命周期调度会绕一圈AMS,必然发生IPC。只有同进程的数据通信才能绕过IPC直接调用。

11. AIDL是什么?AIDL生成代码背后的通信过程?

AIDL(Android Interface Definition Language)是Android提供的IPC接口定义语言,底层基于Binder。

编译后生成StubProxy两个类:

  • Proxy(Client端):把调用参数写入Parcel,通过transact发送给Binder驱动
  • Stub(Server端):继承Binder,实现onTransact(),解析Parcel,执行方法,把结果写回Parcel返回

对Client来说,调用远程方法就像调用本地方法,跨进程细节完全被封装。

12. Binder线程池大小是多少?什么时候创建的?
  • 普通APP进程:默认最大15个,Binder驱动根据需要动态创建,不是一开始就满员
  • SystemServer进程:上限设为31个,因为要处理大量系统服务请求

创建时机:在ActivityThread.main()方法里启动。

13. Client如何查找到具体的Server?
  1. Server启动后向ServiceManager注册,ServiceManager维护名字→Binder对象的映射(如"activity"对应AMS)
  2. Client通过handle=0找到ServiceManager
  3. 告诉ServiceManager需要的服务名,ServiceManager返回对应的Binder对象
  4. Binder驱动在Client进程中为该对象分配一个handle
  5. Client后续通过这个handle与Server通信
14. Binder机制如何保证安全性?
  1. 内核层自动填充调用方的UID和PID,Client无法伪造
  2. Server可通过Binder.getCallingUid()Binder.getCallingPid()验证调用方身份
  3. 系统服务根据UID判断调用方是否有对应权限
15. 有哪些IPC方式?应用场景?
方式场景
BinderAndroid核心IPC,系统服务通信
AIDL基于Binder,支持并发的跨进程调用
Messenger基于AIDL,Binder+Handler,单线程串行处理
ContentProvider基于Binder,跨进程数据共享,大数据用Ashmem
Socket基于TCP/UDP,任意设备间通信,效率较低
共享内存0次拷贝,效率最高,但实现复杂
管道PipeLinux原生,单向通信,logcat底层使用
16. 为什么Zygote和AMS通信使用Socket不用Binder?

核心原因:fork与多线程不兼容

  1. Binder通信依赖多线程(Binder线程池)
  2. fork()只复制当前线程,不复制其他线程
  3. 如果fork前Binder线程池已启动,fork后子进程的线程池状态不完整,会导致死锁
  4. Socket是线程安全的,fork后可以正常使用
17. Intent传输数据上限?

一般是1MB到2MB

这个大小对应Binder驱动在内核空间开辟的Binder缓冲区大小,这块内存是整个进程共享的,不是单次传输独占的。

传输大数据应使用:文件、ContentProvider 或共享内存(Ashmem)。