前言:如果说StatusBar
是代码最多最重要的一个,那Notification
是使用最多,最广泛的一个。这节主要讲通知栏相关的服务和组件控制器是如何构建的。
- 源码浅析,分支为android-12.1.0_r8
NotificationBar的创建过程
在SystemUI学习篇(2)
中,已经了解过StatusBar
的启动过程等。其中也提到了,通知栏,导航栏以及锁屏界面都在StatusBar里有相关的启动操作。
关于NotificationBar
的初始化
- 在
StatusBar
中有一个启动过程 start() -> createAndAddWindows() -> makeStatusBarView() -> inflateStatusBarWindow(),在inflateStatusBarWindow()方法中就涉及到了通知栏的一些视图的创建。 - 在inflateStatusBarWindow()中完成一些变量数据初始化后,回到makeStatusBarView()执行一些设置服务、视图填充和Controller绑定操作。然后继续在createAndAddWindows()中执行
NotificationShadeWindowController
的attach()方法,以及start()方法中的setUpPresenter()。
部分与通知栏相关代码:
public class StatusBar extends SystemUI implements /*代码省略*/ {
@Override
public void start() {
//...代码省略...
createAndAddWindows(result);
//...代码省略...
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
}
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);//调用后续方法初始化
mNotificationShadeWindowController.attach();//初始化完成后执行
//...代码省略...
}
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
//...代码省略...
inflateStatusBarWindow();
mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
mWallpaperController.setRootView(mNotificationShadeWindowView);
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
NotificationListContainer notifListContainer =
mStackScrollerController.getNotificationListContainer();
mNotificationLogger.setUpWithContainer(notifListContainer);
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
mUserSwitcherController.init(mNotificationShadeWindowView);
//...代码省略...
mStatusBarWindowController.getFragmentHostManager()
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
//...代码省略
// Ensure we re-propagate panel expansion values to the panel controller and
// any listeners it may have, such as PanelBar. This will also ensure we
// re-display the notification panel if necessary (for example, if
// a heads-up notification was being displayed and should continue being
// displayed).
mNotificationPanelViewController.updatePanelExpansionAndVisibility();
})
./*代码省略*/
//...代码省略...
mNotificationPanelViewController.initDependencies(
this,
this::makeExpandedInvisible,
mNotificationShelfController);
}
private void inflateStatusBarWindow() {
mStatusBarComponent = mStatusBarComponentFactory.create();
mFragmentService.addFragmentInstantiationProvider(mStatusBarComponent);
mNotificationShadeWindowView = mStatusBarComponent.getNotificationShadeWindowView();
mNotificationShadeWindowViewController = mStatusBarComponent
.getNotificationShadeWindowViewController();
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mNotificationPanelViewController = mStatusBarComponent.getNotificationPanelViewController();
//...代码省略...
}
}
代码虽然多,但是方法名都有对应相应方法的作用。NotificationShadeWindowView
通知栏的阴影块背景视图、NotificationPanelView
通知面板视图,用来填充各个通知。
private void setUpPresenter() {
// Set up the initial notification state.
mActivityLaunchAnimator.setCallback(mKeyguardHandler);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),
mHeadsUpManager
);//动画相关
// TODO: inject this.
mPresenter = new StatusBarNotificationPresenter(
mContext,
mNotificationPanelViewController,
mHeadsUpManager,
mNotificationShadeWindowView,
mStackScrollerController,
mDozeScrimController,
mScrimController,
mNotificationShadeWindowController,
mDynamicPrivacyController,
mKeyguardStateController,
mKeyguardIndicationController,
mFeatureFlags,
this /* statusBar */,
mShadeController,
mLockscreenShadeTransitionController,
mCommandQueue,
mViewHierarchyManager,
mLockscreenUserManager,
mStatusBarStateController,
mNotifShadeEventSource,
mEntryManager,
mMediaManager,
mGutsManager,
mKeyguardUpdateMonitor,
mLockscreenGestureLogger,
mInitController,
mNotificationInterruptStateProvider,
mRemoteInputManager,
mConfigurationController);
mNotificationShelfController.setOnActivatedListener(mPresenter);
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mNotificationActivityStarter =
mStatusBarNotificationActivityStarterBuilder
.setStatusBar(this)
.setActivityLaunchAnimator(mActivityLaunchAnimator)
.setNotificationAnimatorControllerProvider(mNotificationAnimationProvider)
.setNotificationPresenter(mPresenter)
.setNotificationPanelViewController(mNotificationPanelViewController)
.build();
mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mNotificationsController.initialize(
this,
mBubblesOptional,
mPresenter,
mStackScrollerController.getNotificationListContainer(),
mNotificationActivityStarter,
mPresenter);
}
这个方法作用就是完成NotificationsController
的initialize()方法。
NotificationShelf
是通知栏主体框架。
执行完这些后,Notification相关的View的
Controller
都被启动起来,在Controller里有各个视图填充相应Framelayout的逻辑。这一块的代码多且杂,就不做深究。下节就分析一下,app或者系统发送通知后,SystemUI是如何将通知显示,以及重要级是如何区分的。即通知的实现过程
Notification的关键API
NotificationChannels
什么是 NotificationChannel
?在 Android 8.0
以及之后使用通知时必须指定 NotificationChannel
,并且在指定NotificationChannel
时需要设置通知的重要程度等级,以便用户可以在设置里根据个人喜好操作不同的NotificationChannel
。
源码路径: frameworks/base/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
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)),
batteryChannel,
hint
));
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 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);
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
设备的话还会创建画中画通知通道。