Android 13 NotificationChannels與Notification的加載流程

學(xué)習(xí)筆記:篇幅比較長(zhǎng)請(qǐng)耐心看愈涩,之前有寫(xiě)過(guò)部分栽渴,但不是很詳細(xì)芥备。


一、NotificationChannel 的創(chuàng)建

這部分我覺(jué)得三方應(yīng)用使用的較多坤塞,分析的時(shí)候也是源碼與三方應(yīng)用
結(jié)合分析的冯勉。

在源碼中,我看到了一個(gè)很怪的類(lèi):NotificationChannels.java摹芙。這個(gè)類(lèi)繼承了 CoreStartable灼狰。

  • 注:CoreStartable 就是 SystemUI,只是我這的源碼的命名不一樣浮禾,下面為了便于他人閱讀交胚,就以 SystemUI 來(lái)叫。

NotificationChannels.java 就百十行代碼盈电,很簡(jiǎn)單蝴簇,一起看看這個(gè)類(lèi):
NotificationChannels

// NotificationChannels.java
public class NotificationChannels extends CoreStartable {
    // ...
    // 省略代碼

    public static void createAll(Context context) {
        final NotificationManager nm = context.getSystemService(NotificationManager.class);
        // 創(chuàng)建通道
        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);
        // 創(chuàng)建通道
        final NotificationChannel alerts = new NotificationChannel(
                ALERTS,
                context.getString(R.string.notification_channel_alerts),
                NotificationManager.IMPORTANCE_HIGH);
        // 創(chuàng)建通道
        final NotificationChannel general = new NotificationChannel(
                GENERAL,
                context.getString(R.string.notification_channel_general),
                NotificationManager.IMPORTANCE_MIN);
        // 創(chuàng)建通道
        final NotificationChannel storage = new NotificationChannel(
                STORAGE,
                context.getString(R.string.notification_channel_storage),
                isTv(context)
                        ? NotificationManager.IMPORTANCE_DEFAULT
                        : NotificationManager.IMPORTANCE_LOW);
        // 創(chuàng)建通道
        final NotificationChannel hint = new NotificationChannel(
                HINTS,
                context.getString(R.string.notification_channel_hints),
                NotificationManager.IMPORTANCE_DEFAULT);
        // No need to bypass DND.
        // 注冊(cè)通道
        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.
            // 注冊(cè)通道
            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 擴(kuò)展自 SystemUI 并重寫(xiě)了 start() 方法,它執(zhí)行了 createAll() 方法匆帚,創(chuàng)建了通知通道有 batteryChannel(電池)熬词、alerts(提醒)、storage(存儲(chǔ)空間)、screenshot(屏幕截圖)互拾、hint (提示)歪今、general(常規(guī)消息)。
此外颜矿,如果是 TV 設(shè)備的話還會(huì)創(chuàng)建畫(huà)中畫(huà)通知通道寄猩。

  • 怪在哪呢:為什么在這個(gè)類(lèi)去創(chuàng)建注冊(cè)那些通知通道,而且并沒(méi)有提示消息什么的或衡,意義在哪焦影?
  • 注:下面我把該類(lèi)當(dāng)作三方應(yīng)用。

下面圍繞 NotificationChannels 一步一步的分析封断,上面調(diào)用 new NotificationChannel() 創(chuàng)建通知通道斯辰,然后 調(diào)用 nm.createNotificationChannels()方法注冊(cè)通道。
nm 其實(shí)是 NotificationManager 的對(duì)象坡疼,這樣就轉(zhuǎn)到了 NotificationManager 中彬呻。這里我作了一個(gè)流程圖:

NotificationChannel.png

從 NotificationManager.createNotificationChannel() 到 NotificationManagerService.createNotificationChannelsImpl() 都是正常流程,也好理解柄瑰。創(chuàng)建的關(guān)鍵代碼在 mPreferencesHelper.createNotificationChannel() 中闸氮,具體如下:

// PreferencesHelper.java
    @Override
    public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
            boolean fromTargetApp, boolean hasDndAccess) {
        Objects.requireNonNull(pkg);
        Objects.requireNonNull(channel);
        Objects.requireNonNull(channel.getId());
        Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
        boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false;
        synchronized (mPackagePreferences) {
            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
            if (r == null) {
                throw new IllegalArgumentException("Invalid package");
            }
            if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
                throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
            }
            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
                throw new IllegalArgumentException("Reserved id");
            }

            // 前面是各種條件檢查,下面這行是關(guān)鍵點(diǎn)教沾,先檢索這個(gè) channel 是否已經(jīng)存在蒲跨,以 channel id 為標(biāo)志位。
            NotificationChannel existing = r.channels.get(channel.getId());

            // 如果通道已經(jīng)存在就更新通道
            //  更新通道保留大部分已存在的設(shè)置授翻,只更新了 name或悲,description 等幾項(xiàng)
            if (existing != null && fromTargetApp) {

                 // 省略部分代碼......

            } else {

                 // 省略部分代碼......

                // channel 未創(chuàng)建過(guò),把用戶(hù)創(chuàng)建的 channel 加入到系統(tǒng)的 cache 里
                r.channels.put(channel.getId(), channel);
                if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
                    needsDndChange = true;
                }
                MetricsLogger.action(getChannelLog(channel, pkg).setType(
                        com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
                mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
            }
        }

        if (needsDndChange) {
            updateChannelsBypassingDnd();
        }

        return needsPolicyFileChange;
    }

至此堪唐,一個(gè)通知完整的創(chuàng)建完成巡语。

其實(shí)通過(guò)mPreferencesHelper.createNotificationChannel() 方法還能看出 NotificationChannel 一旦創(chuàng)建,那么能更改的東西就很少了(只有名字淮菠,描述男公,blocksystem,以及優(yōu)先級(jí))合陵,而 blocksystem 屬性只有在系統(tǒng)源碼里面才能使用(hide)枢赔;

NotificationChannel 不會(huì)重復(fù)創(chuàng)建。

Android官方是這么解釋這個(gè)設(shè)計(jì)的:NotificationChannel 就像是開(kāi)發(fā)者送給用戶(hù)的一個(gè)精美禮物拥知,一旦送出去糠爬,控制權(quán)就在用戶(hù)那里了。即使用戶(hù)把通知鈴聲設(shè)置成《江南style》举庶,你可以知道,但不可以更改揩抡。

二户侥、Notification 的顯示過(guò)程

這里代碼有點(diǎn)多镀琉,我制作了一個(gè)通知傳遞的方法調(diào)用流程圖:


Notification通知傳遞流程.png

上述流程圖中,我們可能更比較關(guān)注 NotificationManagerService 是怎么與 SystemUI 交互的蕊唐。

其實(shí)SystemUI向 NotificationManagerService 注冊(cè)一個(gè)"服務(wù)"(一個(gè)Binder)屋摔。這個(gè)"服務(wù)"就相當(dāng)于客戶(hù)端 SystemUI 在服務(wù)端 NotificationManagerService 注冊(cè)的一個(gè)回調(diào)。當(dāng)有通知來(lái)臨的時(shí)候替梨,就會(huì)通過(guò)這個(gè)"服務(wù)"通知SystemUI钓试,這個(gè)注冊(cè)是在StatusBar#setUpPresenter()中完成的:

// StatusBar.java
    private void setUpPresenter() {
        // 省略部分代碼......  
        // 這位置調(diào)用了NotificationsControllerImpl#initialize()的方法
        mNotificationsController.initialize(
                mPresenter,
                mNotifListContainer,
                mStackScrollerController.getNotifStackController(),
                mNotificationActivityStarter,
                mCentralSurfacesComponent.getBindRowCallback());
    }

NotificationsControllerImpl#initialize()中進(jìn)行注冊(cè):

// NotificationsControllerImpl.kt
    override fun initialize(
        presenter: NotificationPresenter,
        listContainer: NotificationListContainer,
        stackController: NotifStackController,
        notificationActivityStarter: NotificationActivityStarter,
        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
    ) {
        // 注冊(cè)回調(diào)
        notificationListener.registerAsSystemService()
    }

上述注冊(cè)了之后,每當(dāng)有通知來(lái)時(shí)就會(huì)回調(diào)到:NotificationListener#onNotificationPosted() 中副瀑,接著就會(huì)到 NotificationEntryManager中弓熏。

下面分析通知視圖的加載,這里就直接從 NotificationEntryManager#onNotificationPosted() 開(kāi)始糠睡。

// NotificationEntryManager.java
    private final NotificationHandler mNotifListener = new NotificationHandler() {
        @Override
        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
            final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
            // 通過(guò)key值進(jìn)行判斷挽鞠,通知是否已存在
            if (isUpdateToInflatedNotif) {
                updateNotification(sbn, rankingMap);
            } else {
                addNotification(sbn, rankingMap);
            }
        }
    }

通過(guò)上述源碼可以知道,通知到后狈孔,首先會(huì)進(jìn)行判斷該通知是否存在信认,存在則刷新,不存在則添加均抽;這里以添加為例去分析嫁赏。

先看兩張圖,可以知道下面分析的方向:
SystemUI組件思維導(dǎo)圖:

SystemUI組件思維導(dǎo)圖.png

SystemUI 關(guān)鍵布局圖:
SystemUI 關(guān)鍵布局圖.png

根布局:super_status_bar.xml,
頂上狀態(tài)欄: status_bar.xml, 通過(guò)CollapsedStatusBarFragment.java加載油挥;PhoneStatusBarView(FrameLayout,)是里面的父控件; 對(duì)應(yīng) R.id.status_bar_container 潦蝇。
下拉狀態(tài)欄:(包括通知為status_bar_expanded.xml),最外層布局NotificationPanelView;qs_frame.xml 為下拉后的狀態(tài)欄部分(用QSFragment管理喘漏,布局控件為QSContainerImpl)护蝶,其高度更新在QSContainerImpl.java中;
NotificationStackScrollLayout 用于下拉的通知的相關(guān)問(wèn)題(占滿(mǎn)全屏翩迈,包括導(dǎo)航欄持灰,會(huì)處理點(diǎn)擊狀態(tài)欄空白區(qū)的邏輯)。

NotificationStackScrollLayout:是一個(gè)滑動(dòng)布局负饲,里面嵌套著 ExpandableNotificationRow 堤魁,即通知。

接著上面分析:上面我們只關(guān)注 addNotification(sbn, rankingMap) 返十,而它內(nèi)部時(shí)調(diào)用 addNotificationInternal() 方法實(shí)現(xiàn)的妥泉。
NotificationEntryManager#addNotificationInternal()

// NotificationEntryManager.java
   private void addNotificationInternal(
            StatusBarNotification notification,
            RankingMap rankingMap) throws InflationException {
       
        // 省略部分代碼 ...

        NotificationEntry entry = mPendingNotifications.get(key);

        // 省略部分代碼 ...

        // 構(gòu)造視圖
        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
            // NotificationRowBinderImpl 為 NotificationRowBinder 的實(shí)現(xiàn)類(lèi)
            mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
        }
        // 省略部分代碼 ...
    }

首先為通知?jiǎng)?chuàng)建一個(gè) NotificationEntry 通知實(shí)例,然后再通過(guò) NotificationRowBinderImpl 中的 inflateViews() 加載通知視圖洞坑,綁定通知信息盲链,并在通知欄添加通知視圖,以及在狀態(tài)欄添加通知圖標(biāo)。
NotificationRowBinderImpl#inflateViews()

// NotificationRowBinderImpl.java
    @Override
    public void inflateViews(
            NotificationEntry entry,
            NotifInflater.Params params,
            NotificationRowContentBinder.InflationCallback callback)
            throws InflationException {
        if (params == null) {
            // weak assert that the params should always be passed in the new pipeline
            mNotifPipelineFlags.checkLegacyPipelineEnabled();
        }
        // 獲取查看父布局
        ViewGroup parent = mListContainer.getViewParentForNotification(entry);
        // 通知是否存在
        if (entry.rowExists()) {
            mIconManager.updateIcons(entry);
            ExpandableNotificationRow row = entry.getRow();
            row.reset();
            updateRow(entry, row);
            inflateContentViews(entry, params, row, callback);
        } else {
            // 創(chuàng)建圖標(biāo)
            mIconManager.createIcons(entry);
            mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
                    row -> {
                        // 為視圖設(shè)置控制器.
                        ExpandableNotificationRowComponent component =
                                mExpandableNotificationRowComponentBuilder
                                        .expandableNotificationRow(row)
                                        .notificationEntry(entry)
                                        .onExpandClickListener(mPresenter)
                                        .listContainer(mListContainer)
                                        .build();
                        ExpandableNotificationRowController rowController =
                                component.getExpandableNotificationRowController();
                        rowController.init(entry);
                        entry.setRowController(rowController);
                        bindRow(entry, row);
                        updateRow(entry, row);
                        inflateContentViews(entry, params, row, callback);
                    });
        }
    }

上面無(wú)論走哪個(gè)分支刽沾,最后進(jìn)入到inflateContentViews(entry, row, callback);這是一個(gè)回調(diào):
NotificationRowBinderImpl#inflateContentViews()

// NotificationRowBinderImpl.java
    
    // 加載該行的基本內(nèi)容視圖
    private void inflateContentViews(
            NotificationEntry entry,
            NotifInflater.Params inflaterParams,
            ExpandableNotificationRow row,
            @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
        
         // 省略部分代碼......

        params.rebindAllContentViews();
        mRowContentBindStage.requestRebind(entry, en -> {
            row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
            row.setIsLowPriority(isLowPriority);
            if (inflationCallback != null) {
                inflationCallback.onAsyncInflationFinished(en);
            }
        });
    }

inflationCallback 是 NotificationRowContentBinder 的一個(gè)內(nèi)部接口本慕;在 NotificationEntryManager 中被實(shí)現(xiàn),所以將回調(diào)到 NotificationEntryManager#onAsyncInflationFinished() 中侧漓。

// NotificationEntryManager.java

        @Override
        public void onAsyncInflationFinished(NotificationEntry entry) {
            Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished");
            mPendingNotifications.remove(entry.getKey());
            // If there was an async task started after the removal, we don't want to add it back to
            // the list, otherwise we might get leaks.
            if (!entry.isRowRemoved()) {
                boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;

                    // 省略部分代碼......

                if (isNew) {
                    // 省略部分代碼......

                    // 添加一個(gè)notification會(huì)走到這里锅尘、
                    // 包括一開(kāi)機(jī)就顯示出來(lái)的那些notification
                    addActiveNotification(entry);
                    // 更新視圖
                    updateNotifications("onAsyncInflationFinished");

                    // 省略部分代碼......

                } else {
                    // 省略部分代碼......
                }
            }
            Trace.endSection();
        }

這里直接看 updateNotifications("onAsyncInflationFinished") 方法;
NotificationEntryManager#updateNotification()

// NotificationEntryManager.java
    public void updateNotifications(String reason) {

        // 省略部分代碼......

        if (mPresenter != null) {
            // 更新視圖
            mPresenter.updateNotificationViews(reason);
        }
        // 省略部分代碼......
    }

mPresenter 的實(shí)現(xiàn)類(lèi)是 StatusBarNotificationPresenter布蔗,所以接著看其里面的 updateNotificationViews() 方法藤违。
StatusBarNotificationPresenter#updateNotificationViews()

// StatusBarNotificationPresenter.java
    @Override
    public void updateNotificationViews(final String reason) {
        if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
            return;
        }
        // The function updateRowStates depends on both of these being non-null, so check them here.
        // We may be called before they are set from DeviceProvisionedController's callback.
        if (mScrimController == null) return;

        // 不要在折疊期間修改通知。.
        if (isCollapsing()) {
            mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
            return;
        }
        // 把通知視圖添加到通知面版的通知欄中
        mViewHierarchyManager.updateNotificationViews();
        // 這里不僅僅更新了通知面版的通知視圖纵揍,也更新了狀態(tài)欄的通知圖標(biāo)
        mNotificationPanel.updateNotificationViews(reason);
    }

我們這里看通知面板更新顿乒,即 mNotificationPanel.updateNotificationViews(reason) 方法。mNotificationPanel 為 NotificationPanelViewController 的對(duì)象骡男。
NotificationPanelViewController#updateNotificationViews(reason)

// NotificationPanelViewController.java
    // 更新通知視圖的部分和狀態(tài)欄圖標(biāo)淆游。每當(dāng)顯示的基礎(chǔ)通知數(shù)據(jù)發(fā)生更改時(shí),
    // 這由 NotificationPresenter 觸發(fā)隔盛。
    public void updateNotificationViews(String reason) {
        // 更新NotificationStackScrollLayout 這個(gè)視圖類(lèi)的各種信息
        // updateSectionBoundaries() 這個(gè)方法還沒(méi)弄明白犹菱,但我估計(jì)是添加/刪除視圖后布局重新定位,以及一個(gè)
        mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
        // Footer 其實(shí)就是通知面板底部的兩個(gè)按鈕:“管理”吮炕、“全部清除”腊脱。
        mNotificationStackScrollLayoutController.updateFooter();
        // 更新?tīng)顟B(tài)欄的通知圖標(biāo)
        mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
    }

至此通知面板的視圖完成添加、更新龙亲。

下面接著看下?tīng)顟B(tài)欄的通知圖標(biāo)更新:
NotificationIconAreaController#updateNotificationIcons()

// NotificationIconAreaController.java
    public void updateNotificationIcons(List<ListEntry> entries) {
        mNotificationEntries = entries;
        updateNotificationIcons();
    }

    private void updateNotificationIcons() {
        Trace.beginSection("NotificationIconAreaController.updateNotificationIcons");
         // 更新?tīng)顟B(tài)欄圖標(biāo)
        updateStatusBarIcons();
        updateShelfIcons();
        // 更新 Aod 通知圖標(biāo)
        updateAodNotificationIcons();
        // 應(yīng)用通知圖標(biāo)色調(diào)
        applyNotificationIconsTint();
        Trace.endSection();
    }

下面都是調(diào)用 update XXX Icons() 這種類(lèi)似的方法陕凹,接著調(diào)用 updateIconsForLayout() 方法,我們直接分析 NotificationIconAreaController#updateIconsForLayout()

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

        // toShow保存即將顯示的圖標(biāo)
        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
                mNotificationScrollLayout.getChildCount());

        // 過(guò)濾通知鳄炉,并保存需要顯示的通知圖標(biāo)
        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
            // 獲取一個(gè)通知視圖
            View view = mNotificationScrollLayout.getChildAt(i);
            if (view instanceof ExpandableNotificationRow) {
                NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
                if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
                        hideRepliedMessages, hideCurrentMedia, hideCenteredIcon)) {
                    // 獲取圖標(biāo)
                    StatusBarIconView iconView = function.apply(ent);
                    if (iconView != null) {
                        toShow.add(iconView);
                    }
                }
            }
        }

        // ...

        // 把需要顯示的圖標(biāo)添加到hostLayout中
        final FrameLayout.LayoutParams params = generateIconLayoutParams();
        for (int i = 0; i < toShow.size(); i++) {
            StatusBarIconView v = toShow.get(i);
            // 如果剛剛刪除并再次添加杜耙,視圖可能仍會(huì)暫時(shí)添加
            hostLayout.removeTransientView(v);
            if (v.getParent() == null) {
                if (hideDismissed) {
                    v.setOnDismissListener(mUpdateStatusBarIcons);
                }
                // 執(zhí)行到最后是 NotificationIconContainer.addView 添加視圖
                // NotificationIconContainer本身沒(méi)有addView、removeView方法拂盯,
                // 最終走的是其多層下去的父類(lèi)ViewGroup的方法
                hostLayout.addView(v, i, params);
            }
        }

        // ...

    }

到這里整個(gè) Notification 流程分析完畢佑女。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者谈竿。
  • 序言:七十年代末团驱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子空凸,更是在濱河造成了極大的恐慌嚎花,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀洲,死亡現(xiàn)場(chǎng)離奇詭異紊选,居然都是意外死亡啼止,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)丛楚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)族壳,“玉大人,你說(shuō)我怎么就攤上這事趣些。” “怎么了贰您?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵坏平,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我锦亦,道長(zhǎng)舶替,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任杠园,我火速辦了婚禮顾瞪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抛蚁。我一直安慰自己陈醒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布瞧甩。 她就那樣靜靜地躺著钉跷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肚逸。 梳的紋絲不亂的頭發(fā)上爷辙,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音朦促,去河邊找鬼膝晾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛务冕,可吹牛的內(nèi)容都是我干的血当。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼洒疚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼歹颓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起油湖,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巍扛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后乏德,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撤奸,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吠昭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胧瓜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矢棚。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖府喳,靈堂內(nèi)的尸體忽然破棺而出蒲肋,到底是詐尸還是另有隱情,我是刑警寧澤钝满,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布兜粘,位于F島的核電站,受9級(jí)特大地震影響弯蚜,放射性物質(zhì)發(fā)生泄漏孔轴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一碎捺、第九天 我趴在偏房一處隱蔽的房頂上張望路鹰。 院中可真熱鬧,春花似錦收厨、人聲如沸晋柱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)趣斤。三九已至,卻和暖如春黎休,著一層夾襖步出監(jiān)牢的瞬間浓领,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工势腮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留联贩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓捎拯,卻偏偏與公主長(zhǎng)得像泪幌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子署照,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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