Android 13 SystemUI 的四种通知(Notification)视图

SystemUI 中的四种通知视图

在 Android 系统中,通知可以具有不同的视图来显示信息,这些视图在不同的上下文和设备状态下有不同的显示方式。以下是 Android 13 的 SystemUI 上定义的四种主要的通知视图:

  1. 默认的折叠视图 (content view)
    • 这是最基础的通知视图
    • 它通常只显示基本信息,如应用图标、标题、内容摘要等。
    • 在屏幕上空间有限的情况下,这个视图应该提供足够的信息让用户了解通知的主要内容。
  2. 扩展视图 (expanded view)
    • 用户下拉状态栏时,可以看到的更详细的通知视图
    • 这个视图可以显示更多的内容,比如更长的文本、图片、按钮等交互元素。
    • 扩展视图提供了更多的空间来展示额外信息或提供更多的用户交互选项。
  3. 弹出视图 (heads up view)
    • 当设备正在使用时,如果通知具有较高的优先级,它可能以悬浮通知的形式出现
    • 悬浮通知会在屏幕的顶部显示一段时间,允许用户快速查看并对通知采取行动,而无需下拉状态栏。
    • 这种视图通常用于即时信息,例如来电、消息或者时间敏感的提醒。
  4. 公共视图 (public view)
    • 这是在锁屏上显示的通知视图,用于在保护用户隐私的同时展示通知。
    • 公共视图通常显示更少的详细信息,以防止敏感信息在锁屏上被他人看到。
    • 开发者可以指定一个公共视图来在用户设备锁定时显示一个更通用的通知内容。

SystemUI源码中对通知视图的标志(flag)定义

定义4种通知视图的标志(flag)

SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java 中有上述四种的通知视图的标志(flag)定义。源码如下(本文基于 android_13.0.0_r1SystemUI 源码讲解):

@Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true,
            prefix = {"FLAG_CONTENT_VIEW_"},
            value = {
                    FLAG_CONTENT_VIEW_CONTRACTED,
                    FLAG_CONTENT_VIEW_EXPANDED,
                    FLAG_CONTENT_VIEW_HEADS_UP,
                    FLAG_CONTENT_VIEW_PUBLIC,
                    FLAG_CONTENT_VIEW_ALL})
    @interface InflationFlag {}
    /**
     * The default, contracted view.  Seen when the shade is pulled down and in the lock screen
     * if there is no worry about content sensitivity.
     * 默认视图,代表折叠后的最小化视图。当通知栏被下拉时,以及在锁屏上(如果内容不敏感)时显示这个视图
     */
    int FLAG_CONTENT_VIEW_CONTRACTED = 1;
    /**
     * The expanded view.  Seen when the user expands a notification.
     * 扩展视图,当用户点击或以其他方式展开一个通知时显示
     */
    int FLAG_CONTENT_VIEW_EXPANDED = 1 << 1;
    /**
     * The heads up view.  Seen when a high priority notification peeks in from the top.
     * 弹出视图(Heads Up),用于显示高优先级通知的预览,这些通知从屏幕顶部弹出
     */
    int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2;
    /**
     * The public view.  This is a version of the contracted view that hides sensitive
     * information and is used on the lock screen if we determine that the notification's
     * content should be hidden.
     * 公共视图,是折叠视图的一个版本,它隐藏了敏感信息,在锁屏上用于显示如果我们确定通知的内容应该被隐藏
     */
    int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;

    // 这是一个特殊的标志,用来表示所有视图类型。它通过计算 (1 << 4) 得到16,然后减去1得到15,
    // 这是因为前四个标志分别是1, 2, 4, 和 8,其按位组合结果为15
    int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;

  • FLAG_CONTENT_VIEW_CONTRACTED = 1 (0001, 二进制)
  • FLAG_CONTENT_VIEW_EXPANDED = 2 (0010)
  • FLAG_CONTENT_VIEW_HEADS_UP = 4 (0100)
  • FLAG_CONTENT_VIEW_PUBLIC = 8 (1000)
  • FLAG_CONTENT_VIEW_ALL = 15 (1111,这个是特殊的标志,用于一些处理逻辑上)

通知视图相关的数据类 NotificationContentInflater # InflationProgress

SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java 的静态内部类 InflationProgress 是通知视图加载、填充(inflate)过程中会用到的数据类,它指定了需要填充的通知视图和相关的UI数据。它的成员变量中包含了这四种通知视图类型对应的的数据结构(RemoteViews)和控件(View)。

@VisibleForTesting
static class InflationProgress {
    private RemoteViews newContentView;  // 通知的默认的折叠视图布局
    private RemoteViews newHeadsUpView;  // 通知的弹出视图布局
    private RemoteViews newExpandedView; // 通知的扩展视图布局
    private RemoteViews newPublicView;   // 通知的公共视图布局

    @VisibleForTesting
    Context packageContext;

    private View inflatedContentView;		// 已填充的默认的折叠视图
    private View inflatedHeadsUpView;		// 已填充的弹出视图
    private View inflatedExpandedView;             // 已填充的扩展视图
    private View inflatedPublicView;		// 已填充的公共视图
    private CharSequence headsUpStatusBarText;
    private CharSequence headsUpStatusBarTextPublic;

    private InflatedSmartReplyState inflatedSmartReplyState;
    private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
    private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
}

静态内部类 InflationProgress 中的成员变量 RemoteViews 和 View 的区别是什么?

RemoteViewsView 两者都与通知视图相关,但它们在通知内容填充(inflation)过程中扮演着不同的角色:

  1. RemoteViews:
    • 它表示的是通知的视图的布局模板。RemoteViews 是一个轻量级的对象,可以跨进程传递,它描述了视图的结构和要填充的数据,而不是实际的视图对象。
    • RemoteViews 仅包含布局资源的信息和需要绑定到这些布局的数据,但它不是一个实际的 View 实例。
    • 它是在通知要被展示之前准备的,用于定义通知在状态栏或者锁屏上的外观。
  2. View:
    • 它表示的是已经从 RemoteViews 填充(inflate)后创建出来的实际视图。填充过程涉及将 RemoteViews 对象中定义的布局和数据实例化成一个可交互的 View 对象。
    • inflatedContentView 是在通知内容被加载到内存并准备显示给用户时生成的,也就是说,它是实际添加到通知栏中的视图,用户与之互动。
    • 它通常由系统在后台执行填充操作时创建,避免在主线程中执行耗时的布局操作,从而不影响用户界面的响应性。

简而言之,RemoteViews 是定义通知外观的布局模板(RemoteViews),而 View 是根据这个模板创建的实际可交互的控件。

分析 SystemUI 如何绑定通知视图

1. 类 RowContentBindStage 的方法 executeStage 解析了哪些通知视图需要绑定或者解绑。

@Override
protected void executeStage(
        @NonNull NotificationEntry entry,
        @NonNull ExpandableNotificationRow row,
        @NonNull StageCallback callback) {
    RowContentBindParams params = getStageParams(entry);
    ...

    // Resolve content to bind/unbind.
    @InflationFlag int inflationFlags = params.getContentViews();
    @InflationFlag int invalidatedFlags = params.getDirtyContentViews();

    @InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
    @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;

    // Bind/unbind with parameters
    mBinder.unbindContent(entry, row, contentToUnbind);

    ...
    
    mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback);
}

  • inflationFlags: 表示当前要显示的视图。如 inflationFlags = 1101(二进制),这表示折叠视图、弹出视图和公共视图要显示。
  • invalidatedFlags:表示需要更新的视图。如 invalidatedFlags = 1011(二进制),这表示折叠视图、扩展视图和公共视图要更新。
  • contentToBind :只有需要更新并且要显示的视图才是要绑定的视图集合。 如 contentToBind 经过下面的相与后得到 1001,表示折叠视图和公共视图要绑定。
inflationFlags:    1101
invalidatedFlags:  1011
                   ----
contentToBind:     1001

  • contentToUnbind: 表示需要解绑的视图。FLAG_CONTENT_VIEW_ALL = 1111 代表所有视图的集合。inflationFlagsFLAG_CONTENT_VIEW_ALL 的异或得到的结果是不需要显示的视图,即需要解绑的的视图。
inflationFlags:           0110 (展开和头部弹出的视图需要显示)
FLAG_CONTENT_VIEW_ALL:    1111 (所有视图)
                          ----
contentToUnbind:          1001

2. 我们继续跟进 mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback); 这段代码。类 NotificationContentInflater 的方法 bindContentmBinder.bindContent() 的具体实现

@Override
public void bindContent(
        NotificationEntry entry,
        ExpandableNotificationRow row,
        @InflationFlag int contentToBind,
        BindParams bindParams,
        boolean forceInflate,
        @Nullable InflationCallback callback) {
    
    ...

    // Cancel any pending frees on any view we're trying to bind since we should be bound after.
    cancelContentViewFrees(row, contentToBind);

    AsyncInflationTask task = new AsyncInflationTask(
            mBgExecutor,
            mInflateSynchronously,
            contentToBind,
            mRemoteViewCache,
            entry,
            mConversationProcessor,
            row,
            bindParams.isLowPriority,
            bindParams.usesIncreasedHeight,
            bindParams.usesIncreasedHeadsUpHeight,
            callback,
            mRemoteInputManager.getRemoteViewsOnClickHandler(),
            mIsMediaInQS,
            mSmartReplyStateInflater);
    
    if (mInflateSynchronously) {
        task.onPostExecute(task.doInBackground());
     } else {
         task.executeOnExecutor(mBgExecutor);
     }
}

  • contentToBind 传入到 类 NotificationContentInflater 的静态内部类 AsyncInflationTask 的构造方法里,被赋值给类 AsyncInflationTask 中的成员变量 mReInflateFlags
private AsyncInflationTask(
        Executor bgExecutor,
        boolean inflateSynchronously,
        @InflationFlag int reInflateFlags,
        NotifRemoteViewCache cache,
        NotificationEntry entry,
        ConversationNotificationProcessor conversationProcessor,
        ExpandableNotificationRow row,
        boolean isLowPriority,
        boolean usesIncreasedHeight,
        boolean usesIncreasedHeadsUpHeight,
        InflationCallback callback,
        RemoteViews.InteractionHandler remoteViewClickHandler,
        boolean isMediaFlagEnabled,
        SmartReplyStateInflater smartRepliesInflater) {
    ...
    mReInflateFlags = reInflateFlags;
    ...
}

3. 再看一下 task.doInBackground() 这段代码,它里面用到了 内部类 AsyncInflationTask 的成员变量 mReInflateFlagsmReInflateFlags 传入到 方法 createRemoteViewsinflateSmartReplyViews 中作为参数。

@Override
protected InflationProgress doInBackground(Void... params) {
    try {
        ...
        
        InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                mUsesIncreasedHeadsUpHeight, packageContext);
        InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
        return inflateSmartReplyViews(
                inflationProgress,
                mReInflateFlags,
                mEntry,
                mContext,
                packageContext,
                previousSmartReplyState,
                mSmartRepliesInflater);
    } catch (Exception e) {
        mError = e;
        return null;
    }
}

  • 方法 createRemoteViews根据给定的标志创建通知的 RemoteViews 对象,这些对象代表了通知在状态栏、锁屏等地方显示的不同视图(例如,折叠视图、展开视图、弹出视图和公共视图)。

    • 通过参数 reInflateFlags 判断是否包含某些通知视图,如果包含,则构建相应的通知视图的数据结构(RemoteViews), 并赋值给 InflationProgress 实例的对应的通知视图的RemoteView成员变量。
private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
        Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
        boolean usesIncreasedHeadsUpHeight, Context packageContext) {
    InflationProgress result = new InflationProgress();

    if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
        result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
    }

    if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
        result.newExpandedView = createExpandedView(builder, isLowPriority);
    }

    if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
        result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
    }

    if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
        result.newPublicView = builder.makePublicContentView(isLowPriority);
    }

    result.packageContext = packageContext;
    result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
    result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
            true /* showingPublic */);
    return result;
}

  • inflateSmartReplyViews 方法是 Android 系统 UI 组件中处理通知行(ExpandableNotificationRow)智能回复视图填充(inflate)的一个辅助方法。这个方法的目的是为了根据通知的内容,异步地构建并且填充与智能回复相关的视图。
private static InflationProgress inflateSmartReplyViews(
        InflationProgress result,
        @InflationFlag int reInflateFlags,
        NotificationEntry entry,
        Context context,
        Context packageContext,
        InflatedSmartReplyState previousSmartReplyState,
        SmartReplyStateInflater inflater) {
    boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0
            && result.newContentView != null;
    boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0
            && result.newExpandedView != null;
    boolean inflateHeadsUp = (reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0
            && result.newHeadsUpView != null;
    if (inflateContracted || inflateExpanded || inflateHeadsUp) {
        result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry);
    }
    if (inflateExpanded) {
        result.expandedInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
                context, packageContext, entry, previousSmartReplyState,
                result.inflatedSmartReplyState);
    }
    if (inflateHeadsUp) {
        result.headsUpInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
                context, packageContext, entry, previousSmartReplyState,
                result.inflatedSmartReplyState);
    }
    return result;
}

以上就是关于 SystemUI 的加载对应通知视图类型的部分过程。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,如果想要自定义下拉通知栏的颜色,可以通过修改SystemUI的相关设置来实现。 首先,为了修改SystemUI的颜色,需要获取相应的权限。我们可以在AndroidManifest.xml文件中添加如下代码: ```xml <uses-permission android:name="android.permission.STATUS_BAR"/> ``` 接下来,在我们的项目中创建一个名为values的文件夹,并在其中创建一个名为colors.xml的文件。在这个文件中,我们可以定义我们想要使用的颜色。例如,我们可以定义一个名为notification_background的颜色,用于设置下拉通知栏的背景颜色。代码如下: ```xml <resources> <color name="notification_background">#FF0000</color> </resources> ``` 然后,我们需要修改SystemUI的源代码,以更新背景颜色。具体来说,我们需要找到StatusBar类中的updateResources方法,并在该方法中添加以下代码: ```java Context context = mContext.createPackageContext("com.example.notificationtest", Context.CONTEXT_IGNORE_SECURITY); // 替换为自己的包名 int color = context.getResources().getColor(R.color.notification_background); mBackgroundView.setBackgroundColor(color); ``` 最后,我们需要重新编译并安装我们的应用程序。一旦安装完成,我们就可以看到下拉通知栏的背景颜色已经根据我们在colors.xml中定义的颜色进行了自定义。 以上是通过修改SystemUI的方式来自定义下拉通知栏的颜色。请注意,这种方式需要具备系统级权限,因此只适用于特定的Android设备。在实际开发中,请确保在使用这种方式之前了解并遵守相关的法规和政策,以避免违规行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值