Binder(一):从分层定义到多进程架构

文章内容

一、Binder 为什么是面试必问题

1.1 大厂必问

为什么面试必问 Binder:组件与系统服务都走它

Handler 和 Binder 是 Android 面试里出现频率最高的两道基础题,公司规模越大问得越细。Handler 还好,多数人写过 postDelayed、debug 过 ANR,多少有点感性认识;Binder 就不一样,工作三五年却讲不清它是什么的开发者相当多。

原因在于日常开发几乎接触不到它的存在——startActivity 一行代码调下去,中间隔着几次跨进程通信,编译器和框架把细节全藏起来了。

1.2 先弄清是什么,再谈为什么

面试官问 Binder,不是想听你背源码。这道题真正的筛选点有两个:你知不知道 Android 的组件模型是建立在跨进程通信之上的,以及你有没有在多进程架构里干过活。

这两点都要从"Binder 到底是什么"讲起。定义说不准,后面的动机也就无从谈起。

二、Binder 是什么:三个层次

Binder 的三个层次:应用层 / 驱动层 / 机制层

这个问题可以从三个角度回答,三层都说到,答案才算完整。

2.1 整体:跨进程通信机制

Binder 是 Android 的 IPC(Inter-Process Communication,进程间通信)机制。Linux 本身提供了管道、消息队列、共享内存、socket 等多种 IPC 手段,Android 在此之外自己实现了 Binder,作为整个系统跨进程调用的统一通道。

上层看到的是"调用某个服务的方法",实际是一次跨越进程边界的数据传递和方法分派。

2.2 驱动层:/dev/binder 字符设备

往下看,Binder 是一个内核态的字符设备驱动,设备节点是 /dev/binder。进程通过 open 打开它、ioctl 收发数据,再由内核完成数据在两个进程地址空间之间的搬运。

它之所以比传统 IPC 高效,关键在于用 mmap 把内核缓冲区映射到接收方进程的用户空间,一次数据拷贝就能完成传递,而管道、socket 这类需要两次(发送方到内核,内核到接收方)。

2.3 应用层:Binder.java

在 Java 层,Binder 是一个类——android.os.Binder。用 AIDL(Android Interface Definition Language,安卓接口定义语言)写过跨进程接口的人对这个结构不会陌生:编译器根据 .aidl 文件生成一个 Stub 抽象类,而 Stub 继承自 Binder。服务端实现类只要继承这个 Stub,就具备了被其他进程调用的能力。

客户端拿到的则是 Proxy,它持有一个 IBinder 引用,方法调用被打包成 Parcel 数据丢进驱动。同一套机制,在应用层表现为类的继承关系,在内核层表现为设备文件的读写。

2.4 常见误区:内存管理不是答案

有人会答"Binder 是一套内存管理机制"。Binder 驱动确实做内存管理——mmap 映射、缓冲区分配、事务数据的存放都由它负责,一次事务的数据大小还有 1MB 左右的上限。

但这是它的实现细节,不是它的定位。被问"Binder 是什么"时答内存管理,等于把手段说成了目的,面试官会立刻判断你只读过零散的源码片段,没有整体认知。

三、为什么一线企业都用多进程

3.1 根因:单进程的堆内存上限

单进程堆内存上限:拆进程让可用内存翻倍

今日头条、微信、QQ、微博这类应用全是多进程架构,中小公司的 App 则常常一个进程跑到底。这个差别不是技术品味问题,而是被内存逼出来的。

Android 虚拟机给每个进程的 Java 堆设了硬上限,跟手机装了多少物理内存没有关系。设备总内存 16GB,单个进程的堆照样卡在系统属性规定的那个值上。在连着 adb 的设备上敲一条命令就能看到:

shell
adb shell getprop dalvik.vm.heapgrowthlimit   # 查询普通应用的堆增长上限,超过它就 OOMadb shell getprop dalvik.vm.heapsize          # 查询 largeHeap=true 时能放宽到的上限

视频里演示的设备返回 384m(示例值,各厂商 ROM 会按机型自行调整,常见范围从 128m 到 512m 不等)。这意味着一个单进程 App 无论怎么优化,Java 堆能用的就这么多,突破不了。

还有一层压力来自 LMK(Low Memory Killer,低内存杀手)。系统内存吃紧时,它按优先级和内存占用挑进程回收,占得越多越容易被选中。把应用拆小,本身就是一种保活手段。

3.2 场景一:内存大户单独开进程

多进程三大场景:内存大户 / 推送稳定 / WebView 隔离

图库浏览、大图解码、视频编辑这类功能,峰值内存动辄一两百兆。放在主进程里,一次相册加载就可能把整个应用推到 OOM 边缘。

拆出去单独跑一个进程,主进程和图库进程各自享有独立的 384MB 额度,加起来接近 768MB 可用堆——这是绕开单进程上限唯一有效的办法。功能用完进程退出,占用的内存立刻还给系统。

3.3 场景二:推送等常驻功能

推送 SDK 基本都要求跑在独立进程里,原因是稳定性。主进程的生命周期跟着用户操作走,用户一划退出,进程随时可能被回收,推送通道跟着断掉。

独立进程可以维持较低的内存占用和较高的存活优先级,主进程被杀了它照样在线。消息到达率是推送服务的核心指标,这个拆分几乎没有替代方案。

3.4 场景三:WebView 与风险隔离

WebView 是出了名的内存泄漏重灾区,某些系统版本上销毁后仍然持有 Activity 引用。把它关进独立进程,页面用完直接结束进程,泄漏跟着一起消失。微信就有一个专门的 tools 进程处理 WebView 相关的页面。

再往下推一层,任何不稳定的代码都值得隔离——第三方 SDK、native 库、复杂的解析逻辑。多进程的效果类似装了多个独立 App,其中一个崩溃,其余的照常运行,用户看到的只是某个页面打不开,而不是整个应用闪退。

3.5 怎么开:android:process

拆进程不需要写额外的启动代码,在 AndroidManifest.xml 里给组件加一个属性就行:

xml
<application>    <service        android:name=".push.PushService"        android:process=":push" />          <!-- 冒号开头表示应用私有进程,最终名为 包名:push -->     <activity        android:name=".web.WebActivity"        android:process=":tools" />         <!-- 另一个私有进程,专门承载 WebView 页面 -->     <service        android:name=".core.SharedService"        android:process="com.example.remote" />  <!-- 不带冒号是全局进程,同签名的其他应用可共享 --></application>

属性加上之后,这个 Service 或 Activity 就跑在另一个进程里。随之而来的是:主进程和它之间再也不能直接传对象、共享静态变量,所有交互只能走跨进程通信——也就是 Binder。多进程架构和 Binder 是绑在一起的,选了前者就必须懂后者。

四、你以为没用过,其实天天在用

App 与系统服务的跨进程通信:AMS/PMS/WMS 都在 system_server

4.1 四大组件与系统服务

即使一个进程写到底,也躲不开 Binder。AMS(ActivityManagerService,活动管理服务)跑在 system_server 进程里,和你的应用进程分属两边。每次 startActivity、bindService、sendBroadcast,请求都要跨进程送到 AMS,AMS 处理完再反向调回应用进程执行生命周期。

PMS(PackageManagerService)管理应用安装与权限查询,WMS(WindowManagerService)负责窗口的添加和布局协商,它们同样住在 system_server 里。查一次 getPackageInfo、加一个悬浮窗,中间都是 Binder 事务。

4.2 系统功能同样跨进程

拨打电话交给 telephony 服务,设置闹钟交给 AlarmManagerService,剪贴板、通知栏、蓝牙、定位,全都是同一套路径:应用侧拿到一个 Proxy,方法调用被序列化成 Parcel,经 /dev/binder 送到对应的系统服务进程。

写在中小公司单进程 App 里的每一行组件代码,背后都在用 Binder,只是框架把它包得太严实,让人以为自己从没碰过。

五、总结

5.1 答题路径

被问到 Binder,按这个顺序说:

  • 整体上它是 Android 的跨进程通信机制;
  • 驱动层它是字符设备 /dev/binder,靠 mmap 做到一次拷贝;
  • 应用层它是 Binder 类,AIDL 生成的 Stub 继承它,服务端继承 Stub 就获得了被跨进程调用的能力。
    总之别把内存管理当定义,那是实现细节。

5.2 后续

Binder 往下还有很多内容:一次完整通信的数据流向、驱动如何做一次拷贝、ServiceManager 的角色、AIDL 生成代码里 transact 和 onTransact 的对应关系,这些留到后面几篇展开。


本文由 AgentPlanFlow 生成

觉得不错?给作者点个赞吧

分类:Android系统

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Binder(一):从分层定义到多进程架构 · Yinchen 的博客