一句话:通知数据走 Binder IPC,视图走 RemoteViews(Parcelable),SystemUI 拿着"包名+布局ID"去对方进程创建上下文自己 inflate。
一、整体架构:三进程协作
┌──────────┐ Binder ┌──────────────┐ Binder ┌──────────────┐
│ APP进程 │ ──────────→ │ system_server │ ──────────→ │ SystemUI进程 │
│ │ notify()│(NMS) │ onNotif()│(CarSystemUI)│
└──────────┘ └──────────────┘ └──────────────┘
↑ ↑ ↑
│ │ │
RemoteViewsStatusBarNotificationNotificationListener(Parcelable)(内部数据结构)(AIDL回调接口)
| | | | |
|---|
| | | Notification(含 RemoteViews) | |
| | | | Binder(INotificationListener) |
| | | | |
二、详细流程(逐层拆解)
第1步:APP → NMS(Binder 跨进程)
java
// APP 进程NotificationManager.notify(id, notification);
// 内部走 Binder 代理INotificationManager service =getService();// Binder Proxyservice.enqueueNotificationWithTag(pkg, id, notification,...);
传输的数据结构:
java
// Notification 实现了 Parcelable,可跨进程序列化Notification{String pkg;// 应用包名int id;NotificationChannel channel;RemoteViews contentView;// ← 关键:视图描述,不是真实 ViewRemoteViews bigContentView;PendingIntent pendingIntent;// 点击意图(也是 Parcelable)}
RemoteViews 不是 View,是"布局说明书":只包含 包名 + 布局资源ID + 文本内容,不包含任何 View 对象。
第2步:NMS 内部处理(system_server 进程)
java
// NotificationManagerService.javavoidenqueueNotificationInternal(...){// 1. 包装成 StatusBarNotificationStatusBarNotification sbn =newStatusBarNotification(...);
// 2. 加入队列(Handler 异步,保证顺序) mHandler.post(newEnqueueNotificationRunnable(sbn));
// 3. 遍历所有注册的 NotificationListenerServicefor(INotificationListener listener : mListeners){ listener.onNotificationPosted(sbn, rankingMap);// ← Binder 回调}}
关键点:
- NMS 用
Handler.post() 异步分发,避免阻塞主线程 mListeners 列表里存的是所有注册的 NotificationListenerService 的 Binder 代理- SystemUI 启动时通过
registerAsSystemService() 把自己的 NotificationListener 注册进来
第3步:NMS → SystemUI(Binder 回调)
这是跨进程传输的核心:
java
// INotificationListener.aidl(AIDL 接口定义)interfaceINotificationListener{voidonNotificationPosted(in StatusBarNotification sbn, in RankingMap rankingMap);voidonNotificationRemoved(in StatusBarNotification sbn, in RankingMap rankingMap);voidonNotificationClick(in StatusBarNotification sbn,int rank);}
java
// SystemUI 侧:NotificationListenerWithPlugins(继承 NotificationListenerService)@OverridepublicvoidonNotificationPosted(StatusBarNotification sbn,RankingMap rankingMap){ mMainHandler.post(()->{// 分发给各个处理器for(NotificationHandler handler : mNotificationHandlers){ handler.onNotificationPosted(sbn, rankingMap);}});}
Binder 回调的数据:
| |
|---|
sbn.getPackageName() | |
sbn.getNotification() | Notification 对象(含 RemoteViews) |
sbn.getPostTime() | |
rankingMap | |
第4步:SystemUI 渲染通知(最关键的"跨进程视图"问题)
核心问题:RemoteViews 只是一张"图纸",SystemUI 进程怎么把它变成真实的 View?
java
// NotificationEntryManager.javaprivatevoidbindNotification(StatusBarNotification sbn){// 异步加载 RemoteViewsAsyncLayoutInflater.getInstance(context).inflateView(sbn.getNotification().contentView, callback);}
RemoteViews.apply() 内部做了什么:
java
// RemoteViews.javapublicViewapply(Context context,ViewGroup parent){// 1. 根据包名创建对方应用的上下文Context targetContext = context.createPackageContext( packageName,Context.CONTEXT_INCLUDE_CODE |Context.CONTEXT_IGNORE_SECURITY);
// 2. 用对方的资源加载器 inflate 布局LayoutInflater inflater =(LayoutInflater) targetContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);View view = inflater.inflate(layoutId, parent,false);
// 3. 逐控件设置文本/图片等 view.setTextViewText(R.id.title, title); view.setImageViewResource(R.id.icon, iconId);
return view;}
| | |
|---|
createPackageContext(pkgName) | | |
getResources() | | |
inflate(layoutId) | | |
所以通知栏里的图标、文字,都是 SystemUI 进程"借用"APP进程的资源渲染出来的,不是 APP 进程直接画的。
三、车载场景的特殊处理
| |
|---|
| 驾驶模式 | 车速 >30km/h 时,NotificationListener 会过滤低优先级通知(IMPORTANCE_LOW 直接丢弃) |
| 紧急通知 | CarPropertyManager |
| 多用户 | 车载通常多驾驶员,NMS 用 UserHandle 隔离通知,SystemUI 只显示当前用户的 |
| 性能要求 | 车载要求响应 <16ms,所以 SystemUI 用 AsyncLayoutInflater 异步 inflate,不阻塞主线程 |
| CarSystemUI | 复用 SystemUI 通知机制,但增加了 CarNotificationListener 专门处理车控类通知(空调、座椅等) |
四、一张图总结完整链路
APP 进程 system_server SystemUI 进程
│ │ │
│ notify(id,Notification) │ │
│──────── Binder ────────────────→│ │
│ │ enqueueNotificationInternal │
│ │ → StatusBarNotification │
│ │ → mListeners.notifyPosted() │
│ │──────── Binder ─────────────→│
│ │ │ onNotificationPosted() │ │ │ → NotificationHandler │ │ │ → NotificationEntryManager │ │ │ → AsyncLayoutInflater │ │ │ → createPackageContext(pkg) │ │ │ → inflate(layoutId) ← 借用APP资源
│ │ │ → addView(statusBar) │ │ │
│ │ ← onNotificationRemoved() │
│ │←──────── Binder ────────────│
五、关键结论
| |
|---|
| Binder + Parcelable(Notification / StatusBarNotification) |
| RemoteViews 传"图纸",SystemUI 用 createPackageContext 自己 inflate |
| PendingIntent 跨进程发送,NMS 收到后调用 onNotificationClick() 回调 |
| CarSystemUI 复用这套机制 + 驾驶模式过滤 + 车辆状态联动优先级 |