SystemUI之通知图标控制

本文是基于Android 10源码分析的。

SystemUI之状态图标控制 分析了状态栏上状态图标(例如 wifi, bt)的控制流程,比较简单。本文来分析下状态栏上通知图标的控制流程,主要分析当一个新通知来临时,新通知的图标是如何一步步显示到状态上的。

通知图标控制器

从SystemUI之状态图标控制可知,状态图标是由一个叫StatusBarIconController接口控制显示的,而通知图标区域也有一个控制器,叫NotificationIconAreaController(它不是一个接口)。

NotificationIconAreaController的构造函数中会调用如下方法来创建通知图标的容器

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java

    protected void initializeNotificationAreaViews(Context context) {
        // ...

        LayoutInflater layoutInflater = LayoutInflater.from(context);
        // 实例化通知图标区域视图
        mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null);
        // 这个才是真正存放通知图标的父容器
        mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);

        // ...
    }

这里加载了notification_icon_are.xml布局,来看下这个布局


    

ID为notificationIconsNotificationIconContainer就是通知图标容器,对应于上面代码的mNotificationIcons变量。

初始化通知图标区域

既然NotificationIconAreaController自己创建了通知图标容器,那么就需要加入到状态栏视图中,这个动作在创建状态栏视图后完成的

    // packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        // ...

        FragmentHostManager.get(mStatusBarWindow)
                .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
                    CollapsedStatusBarFragment statusBarFragment =
                            (CollapsedStatusBarFragment) fragment;
                    // 把控制器中的通知容器加入到状态栏的容器中
                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);

                    // ...
                }).getFragmentManager()
                .beginTransaction()
                // CollapsedStatusBarFragment实现了状态栏的添加
                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                        CollapsedStatusBarFragment.TAG)
                .commit();                
    }

调用的就是CollapsedStatusBarFragment#initNotificationIconArea()

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java

    public void initNotificationIconArea(NotificationIconAreaController
            notificationIconAreaController) {
        // 通知图标区域
        ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
        // 这个才是通知图标的父容器
        mNotificationIconAreaInner =
                notificationIconAreaController.getNotificationInnerAreaView();
        if (mNotificationIconAreaInner.getParent() != null) {
            ((ViewGroup) mNotificationIconAreaInner.getParent())
                    .removeView(mNotificationIconAreaInner);
        }
        // 把通知图标父容器添加到通知图标区域里
        notificationIconArea.addView(mNotificationIconAreaInner);

        // 省略处理中心图标区域的代码

        // 这里其实显示了通知图标区域和中心图标区域
        showNotificationIconArea(false);
    }

监听通知的服务端

当一条新通知发送后,它会存储到通知服务端,也就是NotificationManagerService,那么SystemUI是如何知道新通知来临的。这就需要SystemUI向ActivityManagerService注册一个"服务"(一个Binder)。

这个"服务"就相当于客户端SystemUI在服务端ActivityManagerService注册的一个回调。当有通知来临的时候,就会通过这个"服务"通知SystemUI。

这个注册是在StatusBar#start()中完成的。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

    public void start() {
        // 向通知服务端注册一个"服务",用于接收通知信息的回调
        mNotificationListener =  Dependency.get(NotificationListener.class);
        mNotificationListener.registerAsSystemService();
    }

来看下这个注册的实现

    // frameworks/base/core/java/android/service/notification/NotificationListenerService.java

    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        if (mWrapper == null) {
            // 这就是要注册的Binder,也就一个回调
            mWrapper = new NotificationListenerWrapper();
        }
        INotificationManager noMan = getNotificationInterface();
        // 向通知的服务端注册回调
        noMan.registerListener(mWrapper, componentName, currentUser);
    }

这个"服务"就是NotificationListenerWrapper

注册成功后,就会回调NotificationListenerWrapper#NotificationListenerWrapper()方法,并会附带所有的通知信息。

显示通知图标

当一条新的通知来临的时候,通知的服务端会通过NotificationListenerWrapper#onNotificationPosted()进行回调,而最终会调用NotificationListener#onNotificationPosted()

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java

    public void onNotificationPosted(final StatusBarNotification sbn,
            final RankingMap rankingMap) {
        if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
            // 在主线程中进行更新
            Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                processForRemoteInput(sbn.getNotification(), mContext);
                String key = sbn.getKey();
                boolean isUpdate =
                        mEntryManager.getNotificationData().get(key) != null;
                if (isUpdate) {
                    // 更新通知操作
                    mEntryManager.updateNotification(sbn, rankingMap);
                } else {
                    // 添加新通知操作
                    mEntryManager.addNotification(sbn, rankingMap);
                }
            });
        }
    }

这里讨论添加新通知的操作,它调用的是NotificationManager#addNotification()方法,而内部是通过addNotificationInternal()实现的

    private void addNotificationInternal(StatusBarNotification notification,
            NotificationListenerService.RankingMap rankingMap) throws InflationException {
        // ...

        // NotificationEntry就代表一个通知实例
        NotificationEntry entry = new NotificationEntry(notification, ranking);

        // 异步加载视图,并绑定通知信息,由NotificationRowBinderImpl实现
        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
                REASON_CANCEL));

        // ...
    }

首先为通知创建一个NotificationEntry实例,然后再通过NotificationRowBinderImpl#inflateViews()加载通知视图,绑定通知信息,并在通知栏添加通知视图,以及在状态栏添加通知图标。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java

    public void inflateViews(
            NotificationEntry entry,
            Runnable onDismissRunnable)
            throws InflationException {
        // ...

        if (entry.rowExists()) {

        } else {
            // 给通知创建图标
            entry.createIcons(mContext, sbn);
            // 异步加载通知视图
            new RowInflaterTask().inflate(mContext, parent, entry,
                    // 加载完成的回调,这里的加载指的仅仅是一个空视图
                    row -> {
                        // 绑定监听事件和回调
                        bindRow(entry, pmUser, sbn, row, onDismissRunnable);
                        // 在视图上更新通知信息
                        updateNotification(entry, pmUser, sbn, row);
                    });
        }
    }

RowInflaterTask#inflate()会使用status_bar_notification_row.xml布局创建一个通知视图,但是并没有把它加入到父容器中,更没有把把通知信息更新到视图中,这些工作都是在回调中完成的。

第一个回调bindRow(),会为视图绑定各种监听事件以及回调

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java

    private void bindRow(NotificationEntry entry, PackageManager pmUser,
            StatusBarNotification sbn, ExpandableNotificationRow row,
            Runnable onDismissRunnable) {
        // ...

        // 刚才只是创建了视图,并没有绑定数据,这里就是设置绑定数据后的回调,这个回调是由NotificationEntryManager实现
        row.setInflationCallback(mInflationCallback);

        // ...
    }

这里只列出了与本文分析相关的回调,这个回调是在视图与通知信息绑定后的回调。

第二个回调updateNotification(),用数据更新视图,更新完成后就会进行回调刚才绑定的回调事件,而这个回调是由NotificationEntryManager#onAsyncInflationFinished()实现的

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java

    public void onAsyncInflationFinished(NotificationEntry entry,
            @InflationFlag int inflatedFlags) {
        mPendingNotifications.remove(entry.key);
        if (!entry.isRowRemoved()) {
            boolean isNew = mNotificationData.get(entry.key) == null;
            if (isNew) {
                // ...

                if (mPresenter != null) {
                    // 显示视图
                    // 这个由StatusBarNotificationPresenter实现
                    mPresenter.updateNotificationViews();
                }

                // ...
            } else {

            }
        }
    }

数据已经准备完毕,那么现在就是要显示视图了,这个视图包括通知栏里的通知,以及状态栏时的通知图标。这个由StatusBarNotificationPresenter#updateNotificationViews()实现

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java

    public void updateNotificationViews() {
        // ...

        // 1\. 把通知视图添加到通知面版的通知栏中
        mViewHierarchyManager.updateNotificationViews();

        // 这里不仅仅更新了通知面版的通知视图,也更新了状态栏的通知图标
        mNotificationPanel.updateNotificationViews();

        // ...
    }

    public void updateNotificationViews() {
        // ...省略更新通知栏的相关视图的代码

        updateShowEmptyShadeView();
        // 2\. 调用mIconAreaController更新了状态栏通知图标
        // 其实就是调用 mIconAreaController.updateNotificationIcons();
        mNotificationStackScroller.updateIconAreaViews();
    }

首先是往通知栏里添加通知视图,然后再更新状态栏视图。现在只看下如何向状态栏添加通知图标的,它最终是由NotificationIconAreaController#updateIconsForLayout()实现的

// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java

    private void updateIconsForLayout(Function function,
            NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
            boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
            boolean hideCenteredIcon) {

        // toShow保存即将显示的图标
        ArrayList toShow = new ArrayList<>(
                mNotificationScrollLayout.getChildCount());

        // 过滤通知,并保存需要显示的通知图标
        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
            // 获取一个通知视图
            View view = mNotificationScrollLayout.getChildAt(i);
            if (view instanceof ExpandableNotificationRow) {
                NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
                if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
                        hideRepliedMessages, hideCurrentMedia, hideCenteredIcon)) {
                    // 获取图标
                    StatusBarIconView iconView = function.apply(ent);
                    if (iconView != null) {
                        toShow.add(iconView);
                    }
                }
            }
        }

        // ...

        // 把需要显示的图标添加到hostLayout中
        final FrameLayout.LayoutParams params = generateIconLayoutParams();
        for (int i = 0; i < toShow.size(); i++) {
            StatusBarIconView v = toShow.get(i);
            // The view might still be transiently added if it was just removed and added again
            hostLayout.removeTransientView(v);
            if (v.getParent() == null) {
                if (hideDismissed) {
                    v.setOnDismissListener(mUpdateStatusBarIcons);
                }
                hostLayout.addView(v, i, params);
            }
        }

        // ...

    }

纵观整个过程,它的原理是根据通知栏的通知视图,来获取通知图标,然后经过一系列的过滤过程,最终把图标添加到状态栏通知图标容器中。

结束

本文简要分析了通知图标的显示流程,其中穿插提到了通知栏的通知视图的添加过程。掌握了大纲,就可以对细节进行考究,甚至对SystemUI进行定制。

不管好与不好,动动你的小手为小编点个赞吧,你的点赞将是小编最大的动力。

你可能感兴趣的:(SystemUI之通知图标控制)