今天我们来看一下 SystemUI
中系统通知的实现流程,希望能解决一个问题:系统通知是如何完成监听然后显示在通知栏的?
在前面的《SystemUI开发之启动流程(一)》一文中,我们了解到 SystemUI
这个应用是由 SystemServer
启动起来的。它启动了 SystemUIService
这个 Android
服务,然后再由这个服务分别启动了 SystemUI
定义的各种服务组件 例如 SystemBars
, StatusBar
, PowerUI
, VolumeUI
等等组件。本文将此出发看看系统通知是如何实现的。这里会涉及以下一些知识点:
SystemUI
应用是如何监听系统通知的UI
是如何构建的本文的代码是基于Android 10 来分析
在看系统通知的实现流程之前我们先来回顾一下 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
的实现。
如果你看源码,会发现这个类很大,有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
的通信。不过这个状态栏不是本文的重点。
/**
* 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
提供的,如果一个应用要监听系统通知,那么就必须继承它。我们接着看它的实现。
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
完成了系统通知的监听的注册。
现在我们回来看看通知的控件是如何创建的
当收到系统通知时,在 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
进行异步创建控件
这是一个接口,具体实现是在
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
。如果需要自定义这里的布局或修改一些样式,可以关注这些类的实现。
最后通过一张简单的流程来总结一下
SystemServer
启动了 SystemUIService
,然后通过反射创建在 xml
中配置的 SystemUI
服务组件列表,这其中就包括了 StatusBar
组件,这是系统状态栏。
这个状态栏会与系统的状态栏服务 StatusBarService
建立 Binder
通信。同时还会使用 NotificatioinListener
注册系统通知的监听。
当收到通知后相应的回调接口 onNotificationPosted
会被执行,然后使用 NotificationEntryManager
对相应的通知进行处理。