SystemUI 开发之通知 Notification 的关键 API
前文已经了解了 SystemUI
中各个组件的用途,如果没有记错的话,大概有20多个组件。今天我们来看一下其中我认为最为重要的通知组件都有哪些关键 API
。了解它们是我们进一步理解 Notification
实现逻辑的入口。
回顾一下,之前我们介绍组件的用途时跟通知有关的组件有
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
。
如果刚开始接触其实对这些类的印象是比较模糊的,不知道从何入手,当我们了解了这些类的用途以及它们在操作系统中的用户界面,我们就会有一个比较直观的认识,接下来会重点探索这些类的内在逻辑。
伪原创工具 SEO网站优化 https://www.237it.com/
作者:愤怒的代码
链接:https://juejin.cn/post/7035103572274446350