前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SystemUI 开发之通知 Notification 的关键 API(三)

SystemUI 开发之通知 Notification 的关键 API(三)

作者头像
阳仔
发布2021-12-08 09:15:30
1.9K0
发布2021-12-08 09:15:30
举报
文章被收录于专栏:终身开发者

0x00 介绍

前文已经了解了 SystemUI 中各个组件的用途,如果没有记错的话,大概有20多个组件。今天我们来看一下其中我认为最为重要的通知组件都有哪些关键 API。了解它们是我们进一步理解 Notification 实现逻辑的入口。

回顾一下,之前我们介绍组件的用途时跟通知有关的组件有

代码语言:javascript
复制
com.android.systemui.util.NotificationChannels
用来处理通知的逻辑

com.android.systemui.status.phone.StatusBar
状态栏,也包含了通知栏和其它重要的 UI 交互,例如键盘锁等。这里也会监听通知

当然还有其它一些组件例如 PowerUI 也会发送通知,但我们更关注接收通知并处理通知相关的逻辑。

接下来我们会详细介绍这两个类中是如何处理通知的

本文是基于 Android 10 源码

0x01 NotificationChannels

NotificationChannels 类还是比较简单的

代码语言:javascript
复制
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 相关的逻辑,精简之后的代码是这样的:

代码语言:javascript
复制
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 方法

代码语言:javascript
复制
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 方法中会创建很多跟通知相关的对象,以及对相关对象之间建立关系。其中就包括了 NotificationListContainerNotificationListControllerNotificationShelfNotificationPanelStatusBarNotificationActivityStarter 等,这些都是处理通知逻辑的关键 API。 如果刚开始接触其实对这些类的印象是比较模糊的,不知道从何入手,当我们了解了这些类的用途以及它们在操作系统中的用户界面,我们就会有一个比较直观的认识,接下来会重点探索这些类的内在逻辑。

0x03 引用

•关于通知通道官方文档 https://developer.android.com/training/notify-user/channels?hl=zh-cn•在线源码阅读 https://cs.android.com/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终身开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 介绍
  • 0x01 NotificationChannels
  • 0x02 StatusBar
  • 0x03 引用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档