🚀 0x00. 引言:SystemUI 的核心职责与通信必要性
在 Android 的世界里,SystemUI 扮演着"系统门面"的角色。无论是顶部的状态栏 (显示时间、电量、信号)、底部的导航栏 (手势或按键),还是下拉的通知中心 与锁屏界面,都是 SystemUI 的一部分。
然而,在 AOSP(Android Open Source Project)的架构中,SystemUI 并不是系统内核的一部分,而是一个运行在独立进程(com.android.systemui)中的应用程序。
这就带来了一个巨大的挑战:
SystemUI 只是一个 UI 展示层,但它展示的数据(如电量、通知、正在运行的任务)和执行的操作(如杀掉进程、切换应用)都掌握在系统核心进程(system_server)或桌面启动器(Launcher)手中。
如果把 Android 系统比作一家大公司:
- SystemUI 是前台接待员(负责展示和简单交互)。
- Framework (system_server) 是总经理和各个职能部门(掌握核心权力和数据)。
- Launcher 是档案室管理员(管理应用列表和任务栈)。
前台接待员要完成工作,就必须时刻保持与总经理和档案管理员的通话。这种"通话"机制,就是我们今天要深挖的 IPC(进程间通信) ,而在 Android 中,其基石便是 Binder。
🌉 0x01. 核心通信机制(一):与 Launcher/Recents 的通信(多任务管理)
背景
在 Android 9.0 (Pie) 之前,"最近任务" (Recents) 界面通常由 SystemUI 自己绘制。但为了实现手势操作中"从应用平滑过渡到桌面"的动画效果,Google 工程师将 Recents 的 UI 逻辑移交给了 Launcher 进程(即 QuickStep)。
这就出现了一个典型的跨进程协作场景:用户在 SystemUI 的导航栏区域上滑 -> SystemUI 捕获手势 -> 通知 Launcher 显示 Recents 界面并跟随手指运动。
技术解析:IOverviewProxy 的桥梁作用
SystemUI 和 Launcher 之间的通信主要依赖于一个名为 IOverviewProxy 的 AIDL 接口。
-
建立连接:
- SystemUI 启动时,会通过
OverviewProxyService监听 Launcher 服务的状态。 - 当 Launcher 启动后,它会实现
IOverviewProxy接口,并将其 Binder 对象传递给 SystemUI。 - 通俗理解: Launcher 上班了,把自己的"专线电话号码"告诉了 SystemUI。
- SystemUI 启动时,会通过
-
数据流向与交互:
- 手势触发: 当你在导航条上滑动时,SystemUI 的导航栏组件捕获触摸事件。
- Binder 调用: SystemUI 通过持有的
IOverviewProxy代理对象,调用 Launcher 中的方法(如onOverviewShown()或onMotionDown())。 - UI 响应: Launcher 接收到指令,开始绘制 Recents 的缩略图,并根据手指位置更新动画。
(OverviewProxyService) participant BinderDriver as Binder驱动
(Kernel) participant Launcher as Launcher进程
(TouchInteractionService) %% --- 阶段一:建立连接 (Binding) --- rect rgb(240, 248, 255) note right of SysUI: 阶段一:建立连接 (Binding) SysUI->>SysUI: 系统启动,初始化服务 SysUI->>BinderDriver: bindService(Intent="android.intent.action.QUICKSTEP_SERVICE") BinderDriver->>Launcher: 唤醒 Launcher 进程 Launcher->>Launcher: onCreate() -> onBind() note right of Launcher: 创建 IOverviewProxy.Stub 实体 Launcher-->>BinderDriver: 返回 Binder 对象 BinderDriver-->>SysUI: onServiceConnected(IBinder) note right of SysUI: 将 IBinder 转换为 IOverviewProxy 对象 end %% --- 阶段二:双向握手 (Handshake) --- rect rgb(255, 250, 240) note right of SysUI: 阶段二:双向握手 (Handshake) note over SysUI, Launcher: SystemUI 也需要把自己的接口给 Launcher SysUI->>SysUI: 创建 ISystemUiProxy (实现者) SysUI->>Launcher: 调用 IOverviewProxy.onInitialize(ISystemUiProxy) Launcher->>Launcher: 保存 SystemUI 的代理对象 note right of Launcher: 至此,双方建立了双向通信通道 end %% --- 阶段三:运行时通信 (Runtime IPC) --- rect rgb(240, 255, 240) note right of SysUI: 阶段三:手势交互 (Runtime) participant User as 用户手指 User->>SysUI: 在导航栏上滑 (Touch Event) SysUI->>SysUI: InputMonitor 捕获事件 note over SysUI, Launcher: 跨进程调用开始 SysUI->>BinderDriver: mOverviewProxy.onMotionDown() BinderDriver->>Launcher: 传输数据 (Parcel) Launcher->>Launcher: Stub.onMotionDown() Launcher->>Launcher: 绘制 Recents 动画 (跟随手指) Launcher-->>User: 界面更新 (UI Feedback) end
A. 请求连接 (Connection)
- SystemUI 端 :使用
Context.bindService()绑定 Launcher 的Service (TouchInteractionService)。 - Launcher 端 :在
onBind()中返回mTISBinder。这个 Binder 对象继承自IOverviewProxy.Stub。
SystemUI 端在 OverviewProxyService 与 Launcher 建立连接并把SystemUI的代理对象传给Launcher,其代码片段为
Java
// SystemUI 进程建立连接的方法
public void startConnectionToCurrentUser() {
if (mHandler.getLooper() != Looper.myLooper()) {
mHandler.post(mConnectionRunnable);
} else {
internalConnectToCurrentUser();
}
}
// 实际发起连接的逻辑
private void internalConnectToCurrentUser() {
//...
// 查询 Launcher 的服务Action实际上就是Launcher 进程中 Manifest中定义的
// "android.intent.action.QUICKSTEP_SERVICE"
Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
try {
// 1.绑定服务,SystemUI 请求服务,将获取 Launcher 中的 IOverviewProxy 代理对象
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.of(getCurrentUserId()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
//...
}
// 服务连接监听
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//...
try {
// 2. 监听连接链路的生命周期,如果服务死亡则重新发起连接
service.linkToDeath(mOverviewServiceDeathRcpt, 0);
} catch (RemoteException e) {
//...
}
mCurrentBoundedUserId = getCurrentUserId();
// 3. 获取 Launcher 得代理对象 IOverviewProxy
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
// 4. 把 SystemUI 的代理对象 mSysUiProxy 通过 Bundle 进行传输
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
try {
// 4. 建立双向通行,把 SystemUI 的代理对象 告知 Launcher
// 至此 SystemUI 与 Launcher 都获得了对方的代理对象
// 这是一个两个进程双向通行、保活机制的标准实现方式,实际项目中请直接照抄此模式!
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
// ...
}
//... 省略代码
};
Launcher 端在 TouchInteractionService 实现了IOverviewProxy.Stub 接口,代码片段为
Java
public class TISBinder extends IOverviewProxy.Stub {
@BinderThread
public void onInitialize(Bundle bundle) {
// 这里直接从Bundle 中获取 SystemUI 的代理对象 并转成 ISystemUiProxy
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
...
});
sIsInitialized = true;
}
...
B. 数据传输 (Transaction)
当在 SystemUI 调用 mOverviewProxy.onOverviewShown() 时,底层发生了什么?
- 序列化 (Marshalling) :SystemUI 的 IOverviewProxy 将方法 ID(如
TRANSACTION_onOverviewShown)和参数写入一个Parcel对象。 - 陷入内核 (Kernel Trap) :Proxy 调用
transact(),线程挂起,进入 Linux 内核态。 - 驱动搬运:Binder 驱动找到 Launcher 进程,将数据从 SystemUI 的内存空间拷贝(映射)到 Launcher 的内存空间。
- 分发 (Dispatch) :Launcher 进程中的 Binder 线程池被唤醒,收到数据。
- 执行 (Execution) :Launcher 的 Stub 调用
onTransact(),解析出是哪个方法,然后调用TouchInteractionService中具体的onOverviewShown()实现逻辑。
C. 双向通信 (Bi-directional)
注意图中 阶段二 的"握手"。
- Launcher 不仅仅是被控制,它有时也需要控制 SystemUI(例如:在多任务界面点击"分屏",Launcher 需要通知 SystemUI 进入分屏模式)。
- 因此,SystemUI 连接上 Launcher 后,立刻调用
onInitialize(ISystemUiProxy proxy),把自己的 Binder 传给 Launcher。 - 这样,双方手中都握着对方的"遥控器"(Binder 代理对象) ,都可以给对方发送消息。
📞 0x02. 核心通信机制(二):与 Framework 的通信(CommandQueue)
背景
Framework 层(主要是 system_server 进程)包含着 Android 的大脑,如 WindowManagerService (WMS) 和 StatusBarManagerService。当系统状态发生改变时------例如电话来了需要全屏通知,或者应用请求隐藏状态栏(沉浸模式)------Framework 需要"命令" SystemUI 更新界面。
CommandQueue 深度解析
CommandQueue 是 SystemUI 中极其关键的一个类,它是 Framework 向 SystemUI 发号施令的"总线"。
1. 定义与原理
- 本质:
CommandQueue继承自IStatusBar.Stub。这意味着它本质上是一个 Binder 本地对象。 - 位置: 它运行在 SystemUI 进程中。
- 作用: 它是 SystemUI 在 Binder 通信中的"接收端"。
在 SystemUI 端 CommandQueue 监听到 Framework 的通知后通过 Handler 进行分发,其代码片段为
Java
public class CommandQueue extends IStatusBar.Stub {
// ...
public void animateCollapsePanels(int flags, boolean force) {
synchronized (mLock) {
mHandler.removeMessages(MSG_COLLAPSE_PANELS);
mHandler.obtainMessage(MSG_COLLAPSE_PANELS, flags, force ? 1 : 0).sendToTarget();
}
}
public void togglePanel() {
synchronized (mLock) {
mHandler.removeMessages(MSG_TOGGLE_PANEL);
mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget();
}
}
// ...
}
CommandQueue 作为 SystemUI 的服务端接口,那它是如何告知 Framework 中的 system_server 进程的呢?
答案是在 StatusBar 或者高版本的 CentralSurfacesImpl 的 start() 中,通过系统服务 StatusBarService 进行传递的。
Java
public void start() {
...
// 这里通过 ServiceManager 获取到 StatusBarService 的对象
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
...
RegisterStatusBarResult result = null;
try {
// 然后通过 registerStatusBar 方法把 CommandQueue 对象传输到
// system_server 进程,这样system_server 就可以与 SystemUI 进行通信了
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
createAndAddWindows(result);
...
}
2. 数据流向
-
发送指令 (Framework):
- 当 App 调用
setSystemUiVisibility()请求全屏时,WMS 会处理该请求。 - WMS 通过
StatusBarManagerService找到 SystemUI 注册过来的 Binder 代理(即IStatusBar接口)。 - 调用
bar.setSystemUiVisibility(...)。
- 当 App 调用
-
跨进程传输 (Binder):
- 指令通过 Binder 驱动从
system_server传输到 SystemUI 进程。
- 指令通过 Binder 驱动从
-
接收与分发 (SystemUI):
CommandQueue接收到 Binder 调用。- 关键点: Binder 调用通常发生在 Binder 线程池中,不能直接更新 UI。因此,
CommandQueue内部维护了一个Handler,将这些指令封装成 Message,发送到 SystemUI 的主线程 (Main Thread) 队列中排队执行。 - 最终,SystemUI 的各个组件(如
StatusBar类)通过回调接口(Callbacks)响应这些变化,更新图标或隐藏导航栏。
它们三者的关系时序图为
(CommandQueue) participant SBMS as SystemServer
(StatusBarManagerService) participant BinderDriver as Binder驱动/ServiceManager Note over SysUI: SystemUI 进程启动 %% --- 阶段一:CommandQueue 对象创建与初始化 --- SysUI->>SysUI: 创建 CommandQueue 实例 Note right of SysUI: CommandQueue 继承 IStatusBar.Stub
(SystemUI 的 Binder 实体) %% --- 阶段二:向 SystemServer 注册服务 --- Note over SysUI, SBMS: SystemUI 将自身 Stub 对象注册到 Framework SysUI->>SBMS: getSystemService(STATUS_BAR_SERVICE)
(获取 SBMS 代理) SysUI->>SBMS: registerStatusBar(IStatusBar.Stub) %% 3. Binder 传输 Note right of SBMS: SBMS 接收 Stub 对象
并转换为 IStatusBar 代理 SBMS->>SBMS: 保存 IStatusBar 代理引用 (mCallbacks) Note over SBMS, SysUI: 连接建立完成:
SystemServer 现持有 SystemUI 的"遥控器" %% --- 阶段三:后续指令准备就绪 --- SBMS->>SysUI: (连接确认,但无返回值) loop 后续指令发送 SBMS->>SysUI: IStatusBar.disable(StateFlags)
(通过代理对象调用) SysUI->>SysUI: CommandQueue 接收并处理指令 end
应用实例:沉浸模式
当你在看视频时,App 请求隐藏状态栏:
- App ->
View.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN) - WMS (Framework) 计算出新的标志位。
- Binder 调用
IStatusBar.setSystemUiVisibility。 - SystemUI 的
CommandQueue收到调用 -> 切回主线程 -> 通知状态栏 View 播放淡出动画。
总结:
本文讲解 SystemUI 中两种比较常见的 Binder 的业务:
- 与系统层 Framework 通信(CommandQueue机制)
- 与App层 Launcher 通信(双向通信)
掌握 SystemUI 的 IPC 机制,是理解 Android 系统交互逻辑的钥匙,也是进行深度系统定制的必经之路。希望本文能为您揭开这层神秘的面纱。
下一篇将剖析更为复杂精妙的全面屏手势导航的机制,这里不仅有进程间通信还有各种事件的在不同应用的分发传递与协调。