0x00 介绍
今天我们来看一下 SystemUI
中系统通知的实现流程,希望能解决一个问题:系统通知是如何完成监听然后显示在通知栏的?
在前面的《SystemUI开发之启动流程(一)》一文中,我们了解到 SystemUI
这个应用是由 SystemServer
启动起来的。它启动了 SystemUIService
这个 Android
服务,然后再由这个服务分别启动了 SystemUI
定义的各种服务组件 例如 SystemBars
, StatusBar
, PowerUI
, VolumeUI
等等组件。本文将此出发看看系统通知是如何实现的。这里会涉及以下一些知识点:
SystemUI
应用是如何监听系统通知的- 通知的
UI
是如何构建的
本文的代码是基于Android 10 来分析
0x01 监听系统通知的实现过程
StatusBar 的创建
在看系统通知的实现流程之前我们先来回顾一下 SystemUI
组件的构建流程。注意这些服务组件就是前文《SystemUI 开发之服务组件概览(二)》提到在 config.xml
中定义的,例如
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
<string-array name="config_systemUIServiceComponents" translatable="false">
...省略一些代码
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.SystemBars</item>
...
</string-array>
需要注意的是这些服务组件是普通 Java
类,跟 Android
四大组件服务是不一样的概念,它们都是扩展自 SystemUI
这个基类。
这里我们先关注 SystemBars
这个组件的创建
/**
* Ensure a single status bar service implementation is running at all times, using the in-process
* implementation according to the product config.
*/
public class SystemBars extends SystemUI {
//...省略一些代码
@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
createStatusBarFromConfig();
}
private void createStatusBarFromConfig() {
if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
final String clsName = mContext.getString(R.string.config_statusBarComponent);
...省略代码
Class<?> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " clsName, t);
}
try {
mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " clsName, t);
}
...省略代码
// 这里执行SystemUI的start方法
mStatusBar.start();
...省略代码
}
//...省略一些代码
}
可以看到这个类非常的简单,就是通过它创建了 StatusBar
,这个就是手机里面的状态栏。在 start()
方法里面执行了 createStatusBarFromConfig()
方法,然后读取 xml
文件中的类名配置,通过反射创建了 StatusBar
类。
有些人可能会疑问为何不直接创建 StatusBar
呢?
确实是可以的,但是这样做的好处是比较灵活,不同的设备可以配置不同的 StatusBar
实现,比如平板,车机系统等等。
接下来我们看看 StatusBar
的实现。
代码语言:javascript复制如果你看源码,会发现这个类很大,有4000多行的代码,但这里只展示与本文相关的关键流程
public class StatusBar extends SystemUI ... {
// ...省略一些代码
// 这个就是监听系统通知的接口
protected NotificationListener mNotificationListener;
// 系统通知 Binder 接口
protected IStatusBarService mBarService;
@Override
public void start() {
// ...省略一些代码
mNotificationListener = Dependency.get(NotificationListener.class);
mNotificationListener.registerAsSystemService();
// ...省略一些代码
// 这里获取系统的状态栏服务 StatusBarService
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
//...省略代码
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
mCommandQueue.addCallback(this);
RegisterStatusBarResult result = null;
try {
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
// 将StatusBar在windows显示出来
createAndAddWindows(result);
// ...省略一些代码
}
// ...省略一些代码
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
// 创建StatusBar 的View
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
mStatusBarWindowController.attach();
}
}
我们应该已经了解到所有 SystemUI
的服务组件,执行方法都是从 start()
方法开始的。
在这个方法里面通过 Dependency
组件获取到 mNotificationListener
实例,通过它进行注册系统通知的监听,这一步非常关键,这里涉及到 Binder
的通信,这里建立了SystemUI应用和系统服务的通信通道。
此外还通过了 mBarService
把 mCommandQueue
注册给了 mBarService
,用于建立系统状态栏服务与 SystemUI
的通信。不过这个状态栏不是本文的重点。
NotificationListener
代码语言:javascript复制/**
* This class handles listening to notification updates and passing them along to
* NotificationPresenter to be displayed to the user.
*/
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
//...省略代码
// 用于处理通知添加、移除、更新的操作
private final NotificationEntryManager mEntryManager =
Dependency.get(NotificationEntryManager.class);
@Override
public void onListenerConnected() {
//...省略代码
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
onSilentStatusBarIconsVisibilityChanged(noMan.shouldHideSilentStatusBarIcons());
}
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
// 通知到达,StatusBarNotification 是通知的数据
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
// 在主线程中处理
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// ...省略代码
// 更新或者添加通知,使用NotificationEntryManager来处理
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
// 移除通知
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
// 主线程操作,使用NotificationEntryManager来处理
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
mEntryManager.removeNotification(key, rankingMap, reason);
});
}
}
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
// 更新通知内容
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
// 主线程操作,使用NotificationEntryManager来处理
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
mEntryManager.updateNotificationRanking(r);
});
}
}
}
代码比较简单,它是扩展于 NotificationListenerWithPlugins
,用于监听系统通知的发送、移除、更新的回调,主要是通过 NotificationEntryManager
接口执行对通知的各种操作。
NotificationListenerWithPlugins
是继承于 NotificationListenerService
这个接口在
frameworks/base/core/java/android/service/notification/NotificationListenerService.java
它是 frameworks
提供的,如果一个应用要监听系统通知,那么就必须继承它。我们接着看它的实现。
NotificationListenerService
代码语言:javascript复制public abstract class NotificationListenerService extends Service {
/**
* Directly register this service with the Notification Manager.
*
* <p>Only system services may use this call. It will fail for non-system callers.
* Apps should ask the user to add their listener in Settings.
*
* @param context Context required for accessing resources. Since this service isn't
* launched as a real Service when using this method, a context has to be passed in.
* @param componentName the component that will consume the notification information
* @param currentUser the user to use as the stream filter
* @hide
* @removed
*/
@SystemApi
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
if (mWrapper == null) {
mWrapper = new NotificationListenerWrapper();
}
mSystemContext = context;
INotificationManager noMan = getNotificationInterface();
mHandler = new MyHandler(context.getMainLooper());
mCurrentUser = currentUser;
noMan.registerListener(mWrapper, componentName, currentUser);
}
/**
* Directly unregister this service from the Notification Manager.
*
* <p>This method will fail for listeners that were not registered
* with (@link registerAsService).
* @hide
* @removed
*/
@SystemApi
public void unregisterAsSystemService() throws RemoteException {
if (mWrapper != null) {
INotificationManager noMan = getNotificationInterface();
noMan.unregisterListener(mWrapper, mCurrentUser);
}
}
}
看源码这个代码量也很大,不过我们知道它是一个 Android
四大组件的 Service
,有 registerAsSystemService()
方法用于注册系统服务。这个方法是通过 INotificationManager
传递 NotificationListenerWrapper
来实现注册的。
我们会发现 NotificationListenerWrapper
继承自 INotificationListener.Stub
,它也是通过 Binder
来进行通信的。
到这里小结一下:在构建 StatusBar
过程中,通过 NotificationListener
完成了系统通知的监听的注册。
现在我们回来看看通知的控件是如何创建的
0x02 通知控件的创建
当收到系统通知时,在 NotificationListener
的 onNotificationPosted()
方法会执行,这时候就会在主线程中执行添加或更新的操作,这里我们关注添加的操作。
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
// 通知到达,StatusBarNotification 是通知的数据
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
// 在主线程中处理
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// ...省略代码
// 更新或者添加通知,使用NotificationEntryManager来处理
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
收到通知到达,就通过 NotificationEntryManager
来进行操作,现在来看看 NotificationEntryManager
中的实现
public class NotificationEntryManager implements ...{
// ...省略代码
// 添加通知
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
// ...省略代码
// 更新数据
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
// ...省略代码
// Construct the expanded view.
// 异步创建控件
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
// ...省略代码
}
private void bindRow(NotificationEntry entry, ExpandableNotificationRow row) {
mListContainer.bindRow(row);
mNotificationRemoteInputManager.bindRow(row);
row.setOnActivatedListener(mPresenter);
entry.setRow(row);
mNotifBindPipeline.manageRow(entry, row);
mBindRowCallback.onBindRow(row);
}
// ...省略代码
}
这里的逻辑也比较清楚, addNotification()
中直接使用 addNotificationInternal()
,此方法又通过 NotificationRowBinder
进行异步创建控件
这是一个接口,具体实现是在
代码语言:javascript复制frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
/**
* Inflates the views for the given entry (possibly asynchronously).
*/
@Override
public void inflateViews(
NotificationEntry entry,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException {
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
// 如果通知本身就存在,执行更新操作
if (entry.rowExists()) {
mIconManager.updateIcons(entry);
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
inflateContentViews(entry, row, callback);
} else {
// 首次添加通知
mIconManager.createIcons(entry);
// 通过RowInflaterTask来异步创建控件,这个控件的名称ExpandableNotificationRow
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
// Setup the controller for the view.
// 将通知数据与控件进行绑定并更新
bindRow(entry, row);
updateRow(entry, row);
});
}
}
在 inflateViews()
方法中会根据当前的通知是否存在进行更新或者添加,首次添加通知时会使用一个异步构建控件的接口来创建 ExpandableNotificationRow
实例,这个就是在通知栏中显示的具体控件。
看看 RowInflaterTask.inflate
的实现
/**
* Inflates a new notificationView. This should not be called twice on this object
*/
public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
}
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
这里使用了 AsyncLayoutInflater
这个异步创建布局的接口来实现的
这个接口是 androidx
里面的标准接口,对一些复杂的控件可以使用此接口提高创建的效率
https://developer.android.com/reference/androidx/asynclayoutinflater/view/AsyncLayoutInflater
另外我们看到一条通知的静态布局文件 status_bar_notification_row.xml
<!-- extends FrameLayout -->
<com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
>
<!-- Menu displayed behind notification added here programmatically -->
<com.android.systemui.statusbar.notification.row.NotificationBackgroundView
android:id="@ id/backgroundNormal"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.systemui.statusbar.notification.row.NotificationBackgroundView
android:id="@ id/backgroundDimmed"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@ id/expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@ id/expandedPublic"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...省略代码
</com.android.systemui.statusbar.notification.row.ExpandableNotificationRow>
它的布局结构中顶层是扩展自 FrameLayout
的 ExpandableNotificationRow
,然后是两个自定义的背景图 NotificationBackgroundView
,还有两个跟通知内容相关的 NotificationContentView
。如果需要自定义这里的布局或修改一些样式,可以关注这些类的实现。
0x03 总结
最后通过一张简单的流程来总结一下
SystemServer
启动了 SystemUIService
,然后通过反射创建在 xml
中配置的 SystemUI
服务组件列表,这其中就包括了 StatusBar
组件,这是系统状态栏。
这个状态栏会与系统的状态栏服务 StatusBarService
建立 Binder
通信。同时还会使用 NotificatioinListener
注册系统通知的监听。
当收到通知后相应的回调接口 onNotificationPosted
会被执行,然后使用 NotificationEntryManager
对相应的通知进行处理。
0x04 引用
- https://cs.android.com/android/platform/superproject/ /android-10.0.0_r10:frameworks/base/packages/SystemUI/