SystemUI之通知圖標控制

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

SystemUI之狀態(tài)圖標控制 分析了狀態(tài)欄上狀態(tài)圖標(例如 wifi, bt)的控制流程,比較簡單。本文來分析下狀態(tài)欄上通知圖標的控制流程,主要分析當一個新通知來臨時幕帆,新通知的圖標是如何一步步顯示到狀態(tài)上的。

通知圖標控制器

SystemUI之狀態(tài)圖標控制可知赖条,狀態(tài)圖標是由一個叫StatusBarIconController接口控制顯示的失乾,而通知圖標區(qū)域也有一個控制器,叫NotificationIconAreaController(它不是一個接口)纬乍。

NotificationIconAreaController的構(gòu)造函數(shù)中會調(diào)用如下方法來創(chuàng)建通知圖標的容器

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

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

        LayoutInflater layoutInflater = LayoutInflater.from(context);
        // 實例化通知圖標區(qū)域視圖
        mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null);
        // 這個才是真正存放通知圖標的父容器
        mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);

        // ...
    }

這里加載了notification_icon_are.xml布局碱茁,來看下這個布局

<com.android.keyguard.AlphaOptimizedLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/notification_icon_area_inner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false">
    <com.android.systemui.statusbar.phone.NotificationIconContainer
        android:id="@+id/notificationIcons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:clipChildren="false"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>

ID為notificationIconsNotificationIconContainer就是通知圖標容器,對應(yīng)于上面代碼的mNotificationIcons變量仿贬。

初始化通知圖標區(qū)域

既然NotificationIconAreaController自己創(chuàng)建了通知圖標容器纽竣,那么就需要加入到狀態(tài)欄視圖中,這個動作在創(chuàng)建狀態(tài)欄視圖后完成的

    // 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;
                    // 把控制器中的通知容器加入到狀態(tài)欄的容器中
                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);

                    // ...
                }).getFragmentManager()
                .beginTransaction()
                // CollapsedStatusBarFragment實現(xiàn)了狀態(tài)欄的添加
                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                        CollapsedStatusBarFragment.TAG)
                .commit();                
    }

調(diào)用的就是CollapsedStatusBarFragment#initNotificationIconArea()

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

    public void initNotificationIconArea(NotificationIconAreaController
            notificationIconAreaController) {
        // 通知圖標區(qū)域
        ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
        // 這個才是通知圖標的父容器
        mNotificationIconAreaInner =
                notificationIconAreaController.getNotificationInnerAreaView();
        if (mNotificationIconAreaInner.getParent() != null) {
            ((ViewGroup) mNotificationIconAreaInner.getParent())
                    .removeView(mNotificationIconAreaInner);
        }
        // 把通知圖標父容器添加到通知圖標區(qū)域里
        notificationIconArea.addView(mNotificationIconAreaInner);

        // 省略處理中心圖標區(qū)域的代碼

        // 這里其實顯示了通知圖標區(qū)域和中心圖標區(qū)域
        showNotificationIconArea(false);
    }

監(jiān)聽通知的服務(wù)端

當一條新通知發(fā)送后茧泪,它會存儲到通知服務(wù)端蜓氨,也就是NotificationManagerService,那么SystemUI是如何知道新通知來臨的队伟。這就需要SystemUI向ActivityManagerService注冊一個"服務(wù)"(一個Binder)穴吹。

這個"服務(wù)"就相當于客戶端SystemUI在服務(wù)端ActivityManagerService注冊的一個回調(diào)。當有通知來臨的時候嗜侮,就會通過這個"服務(wù)"通知SystemUI港令。

這個注冊是在StatusBar#start()中完成的。

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

    public void start() {
        // 向通知服務(wù)端注冊一個"服務(wù)"棘钞,用于接收通知信息的回調(diào)
        mNotificationListener =  Dependency.get(NotificationListener.class);
        mNotificationListener.registerAsSystemService();
    }

來看下這個注冊的實現(xiàn)

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

    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        if (mWrapper == null) {
            // 這就是要注冊的Binder缠借,也就一個回調(diào)
            mWrapper = new NotificationListenerWrapper();
        }
        INotificationManager noMan = getNotificationInterface();
        // 向通知的服務(wù)端注冊回調(diào)
        noMan.registerListener(mWrapper, componentName, currentUser);
    }

這個"服務(wù)"就是NotificationListenerWrapper

注冊成功后宜猜,就會回調(diào)NotificationListenerWrapper#NotificationListenerWrapper()方法,并會附帶所有的通知信息硝逢。

顯示通知圖標

當一條新的通知來臨的時候姨拥,通知的服務(wù)端會通過NotificationListenerWrapper#onNotificationPosted()進行回調(diào),而最終會調(diào)用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);
                }
            });
        }
    }

這里討論添加新通知的操作渠鸽,它調(diào)用的是NotificationManager#addNotification()方法叫乌,而內(nèi)部是通過addNotificationInternal()實現(xiàn)的

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

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

        // 異步加載視圖,并綁定通知信息徽缚,由NotificationRowBinderImpl實現(xiàn)
        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
                REASON_CANCEL));

        // ...
    }

首先為通知創(chuàng)建一個NotificationEntry實例憨奸,然后再通過NotificationRowBinderImpl#inflateViews()加載通知視圖,綁定通知信息凿试,并在通知欄添加通知視圖排宰,以及在狀態(tài)欄添加通知圖標似芝。

    // 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 {
            // 給通知創(chuàng)建圖標
            entry.createIcons(mContext, sbn);
            // 異步加載通知視圖
            new RowInflaterTask().inflate(mContext, parent, entry,
                    // 加載完成的回調(diào),這里的加載指的僅僅是一個空視圖
                    row -> {
                        // 綁定監(jiān)聽事件和回調(diào)
                        bindRow(entry, pmUser, sbn, row, onDismissRunnable);
                        // 在視圖上更新通知信息
                        updateNotification(entry, pmUser, sbn, row);
                    });
        }
    }

RowInflaterTask#inflate()會使用status_bar_notification_row.xml布局創(chuàng)建一個通知視圖板甘,但是并沒有把它加入到父容器中党瓮,更沒有把把通知信息更新到視圖中,這些工作都是在回調(diào)中完成的盐类。

第一個回調(diào)bindRow()寞奸,會為視圖綁定各種監(jiān)聽事件以及回調(diào)

    // 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) {
        // ...

        // 剛才只是創(chuàng)建了視圖,并沒有綁定數(shù)據(jù)在跳,這里就是設(shè)置綁定數(shù)據(jù)后的回調(diào)枪萄,這個回調(diào)是由NotificationEntryManager實現(xiàn)
        row.setInflationCallback(mInflationCallback);

        // ...
    }

這里只列出了與本文分析相關(guān)的回調(diào),這個回調(diào)是在視圖與通知信息綁定后的回調(diào)猫妙。

第二個回調(diào)updateNotification()呻引,用數(shù)據(jù)更新視圖,更新完成后就會進行回調(diào)剛才綁定的回調(diào)事件吐咳,而這個回調(diào)是由NotificationEntryManager#onAsyncInflationFinished()實現(xiàn)的

    // 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實現(xiàn)
                    mPresenter.updateNotificationViews();
                }

                // ...
            } else {

            }
        }
    }

數(shù)據(jù)已經(jīng)準備完畢逻悠,那么現(xiàn)在就是要顯示視圖了,這個視圖包括通知欄里的通知韭脊,以及狀態(tài)欄時的通知圖標童谒。這個由StatusBarNotificationPresenter#updateNotificationViews()實現(xiàn)

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

    public void updateNotificationViews() {
        // ...

        // 1\. 把通知視圖添加到通知面版的通知欄中
        mViewHierarchyManager.updateNotificationViews();

        // 這里不僅僅更新了通知面版的通知視圖,也更新了狀態(tài)欄的通知圖標
        mNotificationPanel.updateNotificationViews();

        // ...
    }

    public void updateNotificationViews() {
        // ...省略更新通知欄的相關(guān)視圖的代碼

        updateShowEmptyShadeView();
        // 2\. 調(diào)用mIconAreaController更新了狀態(tài)欄通知圖標
        // 其實就是調(diào)用 mIconAreaController.updateNotificationIcons();
        mNotificationStackScroller.updateIconAreaViews();
    }

首先是往通知欄里添加通知視圖沪羔,然后再更新狀態(tài)欄視圖〖⒁粒現(xiàn)在只看下如何向狀態(tài)欄添加通知圖標的,它最終是由NotificationIconAreaController#updateIconsForLayout()實現(xiàn)的

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

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

        // toShow保存即將顯示的圖標
        ArrayList<StatusBarIconView> 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);
            }
        }

        // ...

    }

縱觀整個過程琅豆,它的原理是根據(jù)通知欄的通知視圖,來獲取通知圖標篓吁,然后經(jīng)過一系列的過濾過程茫因,最終把圖標添加到狀態(tài)欄通知圖標容器中。

結(jié)束

本文簡要分析了通知圖標的顯示流程杖剪,其中穿插提到了通知欄的通知視圖的添加過程冻押。掌握了大綱,就可以對細節(jié)進行考究盛嘿,甚至對SystemUI進行定制洛巢。

不管好與不好,動動你的小手為小編點個贊吧次兆,你的點贊將是小編最大的動力稿茉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子漓库,更是在濱河造成了極大的恐慌恃慧,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米苹,死亡現(xiàn)場離奇詭異糕伐,居然都是意外死亡,警方通過查閱死者的電腦和手機蘸嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門良瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人训唱,你說我怎么就攤上這事褥蚯。” “怎么了况增?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵赞庶,是天一觀的道長。 經(jīng)常有香客問我澳骤,道長歧强,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任为肮,我火速辦了婚禮摊册,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颊艳。我一直安慰自己茅特,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布棋枕。 她就那樣靜靜地躺著白修,像睡著了一般。 火紅的嫁衣襯著肌膚如雪重斑。 梳的紋絲不亂的頭發(fā)上兵睛,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音绸狐,去河邊找鬼卤恳。 笑死,一個胖子當著我的面吹牛寒矿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播若债,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼符相,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起啊终,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤镜豹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蓝牲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趟脂,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年例衍,在試婚紗的時候發(fā)現(xiàn)自己被綠了昔期。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡佛玄,死狀恐怖硼一,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梦抢,我是刑警寧澤般贼,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站奥吩,受9級特大地震影響哼蛆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霞赫,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一腮介、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绩脆,春花似錦萤厅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玉锌,卻和暖如春名挥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背主守。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工禀倔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人参淫。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓救湖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涎才。 傳聞我的和親對象是個殘疾皇子鞋既,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容