0x00 介绍
前文已经了解了 SystemUI
中各个组件的用途,如果没有记错的话,大概有20多个组件。今天我们来看一下其中我认为最为重要的通知组件都有哪些关键 API
。了解它们是我们进一步理解 Notification
实现逻辑的入口。
回顾一下,之前我们介绍组件的用途时跟通知有关的组件有
代码语言:javascript复制com.android.systemui.util.NotificationChannels
用来处理通知的逻辑
com.android.systemui.status.phone.StatusBar
状态栏,也包含了通知栏和其它重要的 UI 交互,例如键盘锁等。这里也会监听通知
当然还有其它一些组件例如 PowerUI 也会发送通知,但我们更关注接收通知并处理通知相关的逻辑。
接下来我们会详细介绍这两个类中是如何处理通知的
本文是基于 Android 10 源码
0x01 NotificationChannels
NotificationChannels
类还是比较简单的
public class NotificationChannels extends SystemUI {
// ...
// 省略代码
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
context.getString(R.string.notification_channel_battery),
NotificationManager.IMPORTANCE_MAX);
final String soundPath = Settings.Global.getString(context.getContentResolver(),
Settings.Global.LOW_BATTERY_SOUND);
batteryChannel.setSound(Uri.parse("file://" soundPath), new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
.build());
batteryChannel.setBlockable(true);
final NotificationChannel alerts = new NotificationChannel(
ALERTS,
context.getString(R.string.notification_channel_alerts),
NotificationManager.IMPORTANCE_HIGH);
final NotificationChannel general = new NotificationChannel(
GENERAL,
context.getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_MIN);
final NotificationChannel storage = new NotificationChannel(
STORAGE,
context.getString(R.string.notification_channel_storage),
isTv(context)
? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_LOW);
final NotificationChannel hint = new NotificationChannel(
HINTS,
context.getString(R.string.notification_channel_hints),
NotificationManager.IMPORTANCE_DEFAULT);
// No need to bypass DND.
nm.createNotificationChannels(Arrays.asList(
alerts,
general,
storage,
createScreenshotChannel(
context.getString(R.string.notification_channel_screenshot),
nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
batteryChannel,
hint
));
// Delete older SS channel if present.
// Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
// This line can be deleted in Q.
nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
if (isTv(context)) {
// TV specific notification channel for TV PIP controls.
// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
// priority, so it can be shown in all times.
nm.createNotificationChannel(new NotificationChannel(
TVPIP,
context.getString(R.string.notification_channel_tv_pip),
NotificationManager.IMPORTANCE_MAX));
}
}
/**
* Set up screenshot channel, respecting any previously committed user settings on legacy
* channel.
* @return
*/
@VisibleForTesting static NotificationChannel createScreenshotChannel(
String name, NotificationChannel legacySS) {
NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
screenshotChannel.setSound(null, // silent
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
screenshotChannel.setBlockable(true);
if (legacySS != null) {
// Respect any user modified fields from the old channel.
int userlock = legacySS.getUserLockedFields();
if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) {
screenshotChannel.setImportance(legacySS.getImportance());
}
if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) {
screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes());
}
if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) {
screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern());
}
if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) {
screenshotChannel.setLightColor(legacySS.getLightColor());
}
// skip show_badge, irrelevant for system channel
}
return screenshotChannel;
}
@Override
public void start() {
createAll(mContext);
}
private static boolean isTv(Context context) {
PackageManager packageManager = context.getPackageManager();
return packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
}
NotificationChannels
扩展自 SystemUI
并重写了 start
方法,它执行了 createAll
方法,创建了通知通道有 batteryChannel
(电池)、alerts
(提醒)、storage
(存储空间)、screenshot
(屏幕截图)、hint
(提示)、general
(常规消息)。
此外,如果是 TV
设备的话还会创建画中画通知通道。
那什么是 NotificationChannel
呢?在 Android 8.0
使用通知时必须指定 NotificationChannel
,这样其实是为了避免过分地打扰用户,用户有能力可以对一些指定的通知进行关闭,而不影响其它用户关心的通知。例如一个应用里面会提示很多类型通知,但是用户只关心其中某个通知,那么用户就可以通过设置进行配置。
关于更多的使用信息可以参考官方文档
0x02 StatusBar
在用户界面上 StatusBar
多数情况下是会一直显示在屏幕顶部(全屏应用会隐藏),它是 SystemUI
中一个非常核心的功能,有将近 5000 行的代码也可以从另一个方面知晓它的重要程度。首先看一下构造函数,竟然有很多参数,我几乎看不过来。所以先放弃构造函数的入口。然后我们知道它也是继承自 SystemUI
类,所以我们可以关注它的 start
方法,看它做了哪些初始化的工作。
不过 start
方法也不简单,有将近190行的代码。但我们这里暂时只关心与 Notification
相关的逻辑,精简之后的代码是这样的:
public void start() {
// ...
// 省略代码
// 创建windows
createAndAddWindows(result);
// Make sure we always have the most current wallpaper info.
IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
mWallpaperChangedReceiver.onReceive(mContext, null);
// 这里似乎跟通知有关
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
// 设置 systemui 可见性(navigationbar 和statusbar)
setSystemUiVisibility(mDisplayId, result.mSystemUiVisibility,
result.mFullscreenStackSysUiVisibility, result.mDockedStackSysUiVisibility,
0xffffffff, result.mFullscreenStackBounds, result.mDockedStackBounds,
result.mNavbarColorManagedByIme);
// StatusBarManagerService has a back up of IME token and it's restored here.
setImeWindowStatus(mDisplayId, result.mImeToken, result.mImeWindowVis,
result.mImeBackDisposition, result.mShowImeSwitcher);
// ...
// 省略代码
}
跟进到 setUpPresenter
方法
private void setUpPresenter() {
// Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(
mStatusBarWindow, this, mNotificationPanel,
(NotificationListContainer) mStackScroller);
final NotificationRowBinderImpl rowBinder =
new NotificationRowBinderImpl(
mContext,
SystemUIFactory.getInstance().provideAllowNotificationLongPress());
// mNotificationPanel 是通知面板
// mStackScroller 是 NotificationStackScrollLayout 的实例,它是通知列表
// mStatusBarWindow 整合StatusBar 和 NotificationPanel的window
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager,
mNotificationAlertingManager, rowBinder);
// 通知列表的Controller,它内部持有 NotificationListContainer 引用(其实就是NotificationStackScrollLayout)
mNotificationListController =
new NotificationListController(
mEntryManager,
(NotificationListContainer) mStackScroller,
mForegroundServiceController,
mDeviceProvisionedController);
mAppOpsController.addCallback(APP_OPS, this);
mNotificationShelf.setOnActivatedListener(mPresenter);
mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback =
(StatusBarRemoteInputCallback) Dependency.get(
NotificationRemoteInputManager.Callback.class);
mShadeController = Dependency.get(ShadeController.class);
final ActivityStarter activityStarter = Dependency.get(ActivityStarter.class);
// 用于处理通知相关的各种交互,例如点击通知后跳转个某个应用等交互
mNotificationActivityStarter = new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManager, mNotificationPanel, mPresenter, mEntryManager,
mHeadsUpManager, activityStarter, mActivityLaunchAnimator,
mBarService, mStatusBarStateController, mKeyguardManager, mDreamManager,
mRemoteInputManager, mStatusBarRemoteInputCallback, mGroupManager,
mLockscreenUserManager, mShadeController, mKeyguardMonitor,
mNotificationInterruptionStateProvider, mMetricsLogger,
new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER),
Dependency.get(BG_HANDLER), mActivityIntentHelper, mBubbleController);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mEntryManager.setRowBinder(rowBinder);
rowBinder.setNotificationClicker(new NotificationClicker(
this, Dependency.get(BubbleController.class), mNotificationActivityStarter));
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
}
在 setUpPresenter
方法中会创建很多跟通知相关的对象,以及对相关对象之间建立关系。其中就包括了 NotificationListContainer
、NotificationListController
、NotificationShelf
、NotificationPanel
和StatusBarNotificationActivityStarter
等,这些都是处理通知逻辑的关键 API
。
如果刚开始接触其实对这些类的印象是比较模糊的,不知道从何入手,当我们了解了这些类的用途以及它们在操作系统中的用户界面,我们就会有一个比较直观的认识,接下来会重点探索这些类的内在逻辑。
0x03 引用
•关于通知通道官方文档 https://developer.android.com/training/notify-user/channels?hl=zh-cn•在线源码阅读 https://cs.android.com/