SystemUI中的Notification流程

不管是发出一个新的通知还是对已经存在的通知进行更新,调用的都是NotificationManager.notify(int id,Notification notification)。最后走到SystemUI的时候首先调用StatusBar中的成员变量mNotificationListener的onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap)方法。成员变量mNotificationListener是一个NotificationListenerWithPlugins类的对象,NotificationListenerWithPlugins是NotificationListenerService的父类,NotificationListenerService不在SystemUI中,在android.service.notification中,暂不分析.

来看mNotificationListener对onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap)方法的实现。

public void onNotificationPosted(final StatusBarNotification sbn,
        final RankingMap rankingMap) {
    if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                processForRemoteInput(sbn.getNotification());
                String key = sbn.getKey();
                mKeysKeptForRemoteInput.remove(key);
                boolean isUpdate = mNotificationData.get(key) != null;
                ......
                    // Remove existing notification to avoid stale data.
                    if (isUpdate) {
                        removeNotification(key, rankingMap);
                    } else {
                        mNotificationData.updateRanking(rankingMap);
                    }
                    return;
                }
                try {
                    if (isUpdate) {
                        updateNotification(sbn, rankingMap);
                    } else {
                        addNotification(sbn, rankingMap);
                    }
                } catch (InflationException e) {
                    handleInflationException(sbn, e);
                }
            }
        });
    }
}

首先来看方法中的两个参数:1.StatusBarNotification sbn;2.RankingMap rankingMap。
StatusBarNotification点进去看,发现其实是由Notification组装而成,里面比较重要的属性有String pkg,int id,String key,Notification notification,保存着通知的内容,发出通知的报名信息,以及id等。StatusBarNotification 具体的组装生成过程不是在SystemUI包中进行,暂不关注。

RankingMap则是NotificationListenerService的一个静态内部类,里面保存着所有Notification相关的信息,具体的我们先往下看。

在onNotificationPosted()方法中,先对传过来的sbn进行空判断,不为空,则用mHandler发一个新的runnable来处理

String key = sbn.getKey();

mKeysKeptForRemoteInput.remove(key);

boolean isUpdate = mNotificationData.get(key) != null;

先拿到sbn里面key属性,再根据这个key去mNotificationData取对象。
mNotificationData是StatusBar的一个protected成员变量,可被子类继承,自己本身的类是NotificationData,位于SystemUI工程下的com.android.systemui.statusbar。它的get(key)方法如下:

public Entry get(String key) {
    return mEntries.get(key);
}

返回了一个Entry对象mEntries.get(key),我们来看看这个Entry是什么。Entry是NotificationData的一个内部类。其中包含的几个重要的属性的属性:

public String key;
public StatusBarNotification notification;
public NotificationChannel channel;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
public ExpandableNotificationRow row; // the outer expanded view

显然,我们的Notification先被封装成StatusBarNotification传到SystemUI,然后在SystemUI中又再一次被封装进这个Entry中。

然后我们再看看这个mEntries,这是NotificationData的一个成员变量:

private final ArrayMap mEntries = new ArrayMap<>();

暂时没看到这个map的数据是在哪里添加的。
我们回到onNotificationPosted方法,跳过一部分代码,直接来到重点。

if (isUpdate) {
    updateNotification(sbn, rankingMap);
} else {
    addNotification(sbn, rankingMap);
}

如果mNotificationData能通过sbn的key拿到的Entry不为空,说明这个通知已经存在了,isUpdate为true走更新流程,否则走添加流程。到此,onNotificationPosted方法就结束了。

我们先来看添加流程addNotification(sbn, rankingMap)。
在StatusBar中,这个方法是被这么实现的

public void addNotification(StatusBarNotification notification, RankingMap ranking)
        throws InflationException {
    String key = notification.getKey();
    mNotificationData.updateRanking(ranking);
    Entry shadeEntry = createNotificationViews(notification);
    boolean isHeadsUped = shouldPeek(shadeEntry);
    ......
    abortExistingInflation(key);
 
    mForegroundServiceController.addNotification(notification,
            mNotificationData.getImportance(key));
 
    mPendingNotifications.put(key, shadeEntry);
}

首先通过传来的StatusBarNotification notification封装构造出一个Entry对象(注意,这的notification是StabusBarNotification不是Notification,也就是onNotificationPosted传入的sbn)

Entry shadeEntry = createNotificationViews(notification);

跟过去看createNotificationViews(notification)方法,这里又跳回了StatusBar。

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
        throws InflationException {
    NotificationData.Entry entry = new NotificationData.Entry(sbn);
    Dependency.get(LeakDetector.class).trackInstance(entry);
    entry.createIcons(mContext, sbn);
    // Construct the expanded view.
    inflateViews(entry, mStackScroller);
    return entry;
}

这里面的重点是inflateViews(entry, mStackScroller)。第二个参数mStackScroller,就是SystemUI中的下拉通知栏里面所有通知以及一些其他view的父view,是StatusBar中一个成员变量。
跟过去看方法细节

protected void inflateViews(Entry entry, ViewGroup parent) {
    PackageManager pmUser = getPackageManagerForUser(mContext,
            entry.notification.getUser().getIdentifier());
 
    final StatusBarNotification sbn = entry.notification;
    if (entry.row != null) {
        entry.reset();
        updateNotification(entry, pmUser, sbn, entry.row);
    } else {
        new RowInflaterTask().inflate(mContext, parent, entry,
                row -> {
                    bindRow(entry, pmUser, sbn, row);
                    updateNotification(entry, pmUser, sbn, row);
                });
    }
 
}

看RowInflaterTask().inflate方法,该方法在RowInflaterTask中

public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
        RowInflationFinishedListener listener) {
    mListener = listener;
    AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
    mEntry = entry;
    entry.setInflationTask(this);
    inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}

这个row(ExpandableNotificationRow)就是最终添加到通知栏上的通知对应的view,它的布局文件是R.layout.status_bar_notification_row。

这里重点说一下AsyncLayoutInflater这个类,它是NotificationInflater的静态内部类,其中有方法onAsyncInflationFinished如下

public void onAsyncInflationFinished(NotificationData.Entry entry) {
    mRow.getEntry().onInflationTaskFinished();
    mRow.onNotificationUpdated();
    mCallback.onAsyncInflationFinished(mRow.getEntry());
}
onAsyncInflationFinished的实现在StatusBar中,如下
public void onAsyncInflationFinished(Entry entry) {
    mPendingNotifications.remove(entry.key);
    // 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.
    boolean isNew = mNotificationData.get(entry.key) == null;
    if (isNew && !entry.row.isRemoved()) {
        addEntry(entry);//重点
    } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
        mVisualStabilityManager.onLowPriorityUpdated(entry);
        updateNotificationShade();
    }
    entry.row.setLowPriorityStateUpdated(false);
}

addEntry是重点,如下

private void addEntry(Entry shadeEntry) {
    boolean isHeadsUped = shouldPeek(shadeEntry);
    if (isHeadsUped) {
        mHeadsUpManager.showNotification(shadeEntry);
        // Mark as seen immediately
        setNotificationShown(shadeEntry.notification);
    }
    addNotificationViews(shadeEntry);//重点
    // Recalculate the position of the sliding windows and the titles.
    setAreThereNotifications();
}

addNotificationViews的内容如下

protected void addNotificationViews(Entry entry) {
    if (entry == null) {
        return;
    }
    // Add the expanded view and icon.
    mNotificationData.add(entry);
    updateNotifications();
}

mNotificationData.add(entry);对应了前面分析的boolean isUpdate = mNotificationData.get(key) != null;

进入updateNotifications()方法,内容如下

protected void updateNotifications() {
    mNotificationData.filterAndSort();
 
    updateNotificationShade();
}

重点在updateNotificationShade()(实际上,我就是在这个方法里进行了一些修改达到我想要的需求)

private void updateNotificationShade() {
    if (mStackScroller == null) return;
 
    // Do not modify the notifications during collapse.
    if (isCollapsing()) {
        addPostCollapseAction(new Runnable() {
            @Override
            public void run() {
                updateNotificationShade();
            }
        });
        return;
    }
 
    ArrayList activeNotifications = mNotificationData.getActiveNotifications();
    ArrayList toShow = new ArrayList<>(activeNotifications.size());
    final int N = activeNotifications.size();
    for (int i=0; i orderedChildren =
                    mTmpChildOrderMap.get(summary);
            if (orderedChildren == null) {
                orderedChildren = new ArrayList<>();
                mTmpChildOrderMap.put(summary, orderedChildren);
            }
            orderedChildren.add(ent.row);
        } else {
            toShow.add(ent.row);
        }
 
    }
 
    ArrayList toRemove = new ArrayList<>();
    for (int i=0; i< mStackScroller.getChildCount(); i++) {
        View child = mStackScroller.getChildAt(i);
        if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
            toRemove.add((ExpandableNotificationRow) child);
        }
    }
 
    for (ExpandableNotificationRow remove : toRemove) {
        if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
            // we are only transfering this notification to its parent, don't generate an animation
            mStackScroller.setChildTransferInProgress(true);
        }
        if (remove.isSummaryWithChildren()) {
            remove.removeAllChildren();
        }
        mStackScroller.removeView(remove);
        mStackScroller.setChildTransferInProgress(false);
    }
 
    removeNotificationChildren();
 
    for (int i=0; i

显示做mStackScroller的空判断,然后是通知栏动画状态的判断。一切OK,就:
1.mNotificationData获取数据Entry集合,构造一个大小和这个Entry集合一样的ExpandableNotificationRow集合toShow
2.遍历entry,把entry.row添加到toshow里面
3.原有的通知,但是toshow里没有的则移除,然后toshow里没添加上的添加上去

至此我们的通知就成功地添加到通知栏了。

看bindRow(entry, pmUser, sbn, row)方法

private void bindRow(Entry entry, PackageManager pmUser,
        StatusBarNotification sbn, ExpandableNotificationRow row) {
    row.setExpansionLogger(this, entry.notification.getKey());
    row.setGroupManager(mGroupManager);
    row.setHeadsUpManager(mHeadsUpManager);
    row.setAboveShelfChangedListener(mAboveShelfObserver);
    row.setRemoteInputController(mRemoteInputController);
    row.setOnExpandClickListener(this);
    row.setRemoteViewClickHandler(mOnClickHandler);
    row.setInflationCallback(this);
    row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
    final String pkg = sbn.getPackageName();
    String appname = pkg;
    try {
        final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
                PackageManager.MATCH_UNINSTALLED_PACKAGES
                        | PackageManager.MATCH_DISABLED_COMPONENTS);
        if (info != null) {
            appname = String.valueOf(pmUser.getApplicationLabel(info));
        }
    } catch (NameNotFoundException e) {
        // Do nothing
    }
    row.setAppName(appname);
    row.setOnDismissRunnable(() ->
            performRemoveNotification(row.getStatusBarNotification()));
    row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
//该句实现了焦点的策略,row(也就是我们的通知)有子控件需要焦点则把焦点交给子控件,否则给row。
    if (ENABLE_REMOTE_INPUT) {
        row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    }
}

看updateNotification(entry,pmUser,sbn,entry.row)方法

private void updateNotification(Entry entry, PackageManager pmUser,
        StatusBarNotification sbn, ExpandableNotificationRow row) {
    row.setNeedsRedaction(needsRedaction(entry));
    boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
    boolean isUpdate = mNotificationData.get(entry.key) != null;
    boolean wasLowPriority = row.isLowPriority();
    row.setIsLowPriority(isLowPriority);
    row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
    // bind the click event to the content area
    mNotificationClicker.register(row, sbn);//给通知栏注册点击事件
 
    // Extract target SDK version.
    try {
        ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
        entry.targetSdk = info.targetSdkVersion;
    } catch (NameNotFoundException ex) {
        Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
    }
    row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
            && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
 
    entry.row = row;
    entry.row.setOnActivatedListener(this);
 
    boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
            mNotificationData.getImportance(sbn.getKey()));
    boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
    row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
    row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
    row.updateNotification(entry);
}

mNotificationClicker.register(row, sbn);mNotificationClicker是StatusBar的一个私有成员,对应的类是NotificationClicker,是一个StatusBar的内部类,实现了View.OnClickListener接口,它就是row的监听类,实现的功能是row被点击时,启动相应的pendingIntent.注册操作代码如下

public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
    Notification notification = sbn.getNotification();
    if (notification.contentIntent != null || notification.fullScreenIntent != null) {
        row.setOnClickListener(this);
    } else {
        row.setOnClickListener(null);
    }
}

进入ExpandableNotificationRow的updateNotification方法

public void updateNotification(NotificationData.Entry entry) {
    mEntry = entry;
    mStatusBarNotification = entry.notification;
    mNotificationInflater.inflateNotificationViews();
}

至此inflateViews()结束,Entry生成完毕,Entry中的row生成完毕。

Notification的更新

接着回到最开始的updateNotification(StatusBarNotification notification, RankingMap ranking)方法中

public void updateNotification(StatusBarNotification notification, RankingMap ranking)
        throws InflationException {
    if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
 
    final String key = notification.getKey();
    abortExistingInflation(key);
    Entry entry = mNotificationData.get(key);
    if (entry == null) {
        return;
    }
    mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
    mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
    if (key.equals(mKeyToRemoveOnGutsClosed)) {
        mKeyToRemoveOnGutsClosed = null;
        Log.w(TAG, "Notification that was kept for guts was updated. " + key);
    }
 
    Notification n = notification.getNotification();
    mNotificationData.updateRanking(ranking);
 
    final StatusBarNotification oldNotification = entry.notification;
    entry.notification = notification;
    mGroupManager.onEntryUpdated(entry, oldNotification);
 
    entry.updateIcons(mContext, notification);
    inflateViews(entry, mStackScroller);
 
    mForegroundServiceController.updateNotification(notification,
            mNotificationData.getImportance(key));
 
    boolean shouldPeek = shouldPeek(entry, notification);
    boolean alertAgain = alertAgain(entry, n);
 
    updateHeadsUp(key, entry, shouldPeek, alertAgain);
    updateNotifications();
 
    if (!notification.isClearable()) {
        // The user may have performed a dismiss action on the notification, since it's
        // not clearable we should snap it back.
        mStackScroller.snapViewIfNeeded(entry.row);
    }
 
    if (DEBUG) {
        // Is this for you?
        boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
        Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
    }
 
    setAreThereNotifications();
}

更新Notification走的还是inflateViews方法和updateNotifications方法

移除通知

首先还是StatusBar中的mNotificationListener但是和notification的添加/更新不同的是,走的不再是onNotificationPosted方法,而是onNotificationRemoved

public void onNotificationRemoved(StatusBarNotification sbn,
        final RankingMap rankingMap) {
    if (true/**DEBUG*/) Log.d(TAG, "onNotificationRemoved: " + sbn);
    if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
        final String key = sbn.getKey();
        mHandler.post(() -> removeNotification(key, rankingMap));
    }
}

这个方法就是很简单地拿个key,然后走removeNotification(key, rankingMap)方法.

public void removeNotification(String key, RankingMap ranking) {
    boolean deferRemoval = false;
    abortExistingInflation(key);
    if (mHeadsUpManager.isHeadsUp(key)) {
        // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
        // sending look longer than it takes.
        // Also we should not defer the removal if reordering isn't allowed since otherwise
        // some notifications can't disappear before the panel is closed.
        boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key)
                && !FORCE_REMOTE_INPUT_HISTORY
                || !mVisualStabilityManager.isReorderingAllowed();
        deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
    }
    if (key.equals(mMediaNotificationKey)) {
        clearCurrentMediaNotification();
        updateMediaMetaData(true, true);
    }
    if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
        Entry entry = mNotificationData.get(key);
        StatusBarNotification sbn = entry.notification;
 
        Notification.Builder b = Notification.Builder
                .recoverBuilder(mContext, sbn.getNotification().clone());
        CharSequence[] oldHistory = sbn.getNotification().extras
                .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
        CharSequence[] newHistory;
        if (oldHistory == null) {
            newHistory = new CharSequence[1];
        } else {
            newHistory = new CharSequence[oldHistory.length + 1];
            for (int i = 0; i < oldHistory.length; i++) {
                newHistory[i + 1] = oldHistory[i];
            }
        }
        newHistory[0] = String.valueOf(entry.remoteInputText);
        b.setRemoteInputHistory(newHistory);
 
        Notification newNotification = b.build();
 
        // Undo any compatibility view inflation
        newNotification.contentView = sbn.getNotification().contentView;
        newNotification.bigContentView = sbn.getNotification().bigContentView;
        newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
 
        StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
                sbn.getOpPkg(),
                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
                newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
        boolean updated = false;
        try {
            updateNotification(newSbn, null);
            updated = true;
        } catch (InflationException e) {
            deferRemoval = false;
        }
        if (updated) {
            mKeysKeptForRemoteInput.add(entry.key);
            return;
        }
    }
    if (deferRemoval) {
        mLatestRankingMap = ranking;
        mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
        return;
    }
    Entry entry = mNotificationData.get(key);
 
    if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
            && (entry.row != null && !entry.row.isDismissed())) {
        mLatestRankingMap = ranking;
        mRemoteInputEntriesToRemoveOnCollapse.add(entry);
        return;
    }
    if (entry != null && mNotificationGutsExposed != null
            && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
            && !entry.row.getGuts().isLeavebehind()) {
        Log.w(TAG, "Keeping notification because it's showing guts. " + key);
        mLatestRankingMap = ranking;
        mKeyToRemoveOnGutsClosed = key;
        return;
    }
 
    if (entry != null) {
        mForegroundServiceController.removeNotification(entry.notification);
    }
 
    if (entry != null && entry.row != null) {
        entry.row.setRemoved();
        mStackScroller.cleanUpViewState(entry.row);
    }
    // Let's remove the children if this was a summary
    handleGroupSummaryRemoved(key, ranking);
    StatusBarNotification old = removeNotificationViews(key, ranking);
    /// M: Enable this log for unusual case debug.
    if (true/**SPEW*/) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
    if (old != null) {
        if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
                && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
            if (mState == StatusBarState.SHADE) {
                animateCollapsePanels();
            } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) {
                goToKeyguard();
            }
        }
    }
    setAreThereNotifications();
}

这里面的重点是removeNotificationViews(key, ranking)方法,这个方法是在StatusBar中定义的

protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
    NotificationData.Entry entry = mNotificationData.remove(key, ranking);
    if (entry == null) {
        Log.w(TAG, "removeNotification for unknown key: " + key);
        return null;
    }
    updateNotifications();
    Dependency.get(LeakDetector.class).trackGarbage(entry);
    return entry.notification;
}

里面逻辑也很简单,根据key,从mNotificationData移除entry,然后就是走回我们熟悉的updateNotifications()刷新UI。

你可能感兴趣的:(SystemUI中的Notification流程)