Android 13 SystemUI 的四种通知(Notifi

2024-02-26  本文已影响0人  BlueSocks

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;

通知视图相关的数据类 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:    1101
invalidatedFlags:  1011
                   ----
contentToBind:     1001

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);
     }
}

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;
    }
}

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;
}

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 的加载对应通知视图类型的部分过程。

上一篇 下一篇

猜你喜欢

热点阅读