Notification之----Android5.0實(shí)現(xiàn)原理(二)

概述

前文講解了Notification的構(gòu)造画饥,現(xiàn)在來講講notification的發(fā)送岭皂,以及公布前文留下的疑問(自定義view不論高度是多高见擦,最后只能顯示為64dp摘完,why?)

NotificationManager

在Notification構(gòu)造完成后姥饰,會(huì)調(diào)用NotificationManager的notify方法來發(fā)送通知,我們就來看看該方法
frameworks/base/core/java/android/app/NotificationManager.java

public void notify(String tag, int id, Notification notification)
{
    ...
    INotificationManager service = getService();
    ...
    service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
        stripped, idOut, UserHandle.myUserId());
    ...
}

可以看出NotificationManager只是一個(gè)空殼孝治,沒有做什么實(shí)際上的事情列粪,只是把notify的動(dòng)作交給了service來做栅螟。
為了主干的清晰,直接給出enqueueNotificationWithTag的實(shí)現(xiàn)在NotificationManagerService中

NotificationManagerService

frameworks/base/services/java/com/android/server/NotificationManagerService.java

public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
        Notification notification, int[] idOut, int userId) throws RemoteException {
    enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
            Binder.getCallingPid(), tag, id, notification, idOut, userId);
}

所以重要的是enqueueNotificationInternal方法

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
        final int callingPid, final String tag, final int id, final Notification notification,
        int[] idOut, int incomingUserId) {
    ...
    
    if (!isSystemNotification && !isNotificationFromListener) {
        ...
        //MAX_PACKAGE_NOTIFICATIONS = 50;
        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
            return;
        }
    }
    
    ...
        
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            synchronized (mNotificationList) {
                ...
                // blocked apps
                //如果用戶設(shè)置了該引用不顯示通知篱竭,并且不是系統(tǒng)通知的話力图,直接將該通知打分為-1000
                if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
                    if (!isSystemNotification) {
                        //JUNK_SCORE = -1000;
                        r.score = JUNK_SCORE;
                    }
                }

                //SCORE_DISPLAY_THRESHOLD = -20;
                //打分小于閾值的通知不顯示
                if (r.score < SCORE_DISPLAY_THRESHOLD) {
                    // Notification will be blocked because the score is too low.
                    return;
                }

                //垃圾通知,也不會(huì)顯示
                if (isNotificationSpam(notification, pkg)) {
                    mArchive.record(r.sbn);
                    return;
                }

                ...
                //只顯示有圖標(biāo)的通知
                if (notification.icon != 0) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(n, oldSbn);
                }
                ...
                //聲音掺逼,震動(dòng)吃媒,閃光燈的控制
                buzzBeepBlinkLocked(r);
            }
        }
    });
}

可以看到要想發(fā)出通知必須得滿足以下幾個(gè)條件

  1. 非系統(tǒng)應(yīng)用,最多只能發(fā)送50個(gè)通知消息
  2. 用戶設(shè)置了允許應(yīng)用發(fā)送通知
  3. 被系統(tǒng)判定為非垃圾通知(該功能是cm自己添加的吕喘,系統(tǒng)中會(huì)有一個(gè)數(shù)據(jù)庫赘那,然后根據(jù)通知欄的Extra信息來匹配,如果成功則判定為垃圾通知氯质,但是該功能現(xiàn)在并沒有實(shí)現(xiàn))
  4. 通知必須得有icon

檢查通過后再使用notifyPostedLocked方法做真正的發(fā)送動(dòng)作募舟。buzzBeepBlinkLocked很簡單,不浪費(fèi)篇幅敘述了闻察。

INotificationListener

notifyPostedLocked方法最后調(diào)用notifyPosted方法拱礁,我們直接來看看該方法

private void notifyPosted(final ManagedServiceInfo info,
    final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    final INotificationListener listener = (INotificationListener)info.service;
    ...
    listener.onNotificationPosted(sbnHolder, rankingUpdate);
    ...
}

這里有一個(gè)INotificationListener對(duì)象,一看到以I開頭的就可以知道辕漂,這里肯定又是一個(gè)IPC通信呢灶。
查看源碼可以知道,onNotificationPosted的實(shí)現(xiàn)是在SystemUI進(jìn)程中钉嘹,也就是我們的狀態(tài)欄進(jìn)程鸯乃。

BaseStatusBar

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

@Override
public void onNotificationPosted(final StatusBarNotification sbn,
        final RankingMap rankingMap) {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
             ...
             boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
                            || isHeadsUp(sbn.getKey());
             ...
            if (isUpdate) {
                updateNotification(sbn, rankingMap);
            } else {
                addNotification(sbn, rankingMap);
            }
        }
    });
}

狀態(tài)欄會(huì)根據(jù)通知的唯一key值來判斷該通知是否是更新還是新增的。
我們以新增的為例來講.addNotification是一個(gè)抽象方法跋涣,實(shí)現(xiàn)是在BaseStatusBar的子類PhoneStatusBar

PhoneStatusBar

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

public void addNotification(StatusBarNotification notification, RankingMap ranking) {
    ...
    Entry shadeEntry = createNotificationViews(notification);
    if (shadeEntry == null) {
        return;
    }
    ...
    addNotificationViews(shadeEntry, ranking);
    ...
}

該方法做了2個(gè)重要的事情缨睡,一個(gè)就是創(chuàng)建Entry實(shí)例,另外一個(gè)就是將Entry添加到狀態(tài)欄上陈辱,然后就顯示完成了奖年。
因?yàn)?code>createNotificationViews的實(shí)現(xiàn)是在父類中,并且該方法十分重要性置,所以我們先跳過該方法拾并。
先把Entry理解成一條通知揍堰,來講addNotificationViews的實(shí)現(xiàn)鹏浅。

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

先直接將得到的Entry添加到mNotificationData里面
最終updateNotifications會(huì)調(diào)用PhoneStatusBar中的updateNotificationShade方法

private void updateNotificationShade() {
    ...
    ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
    ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
    ...
    for (int i=0; i<N; i++) {
       Entry ent = activeNotifications.get(i);
       ...
       toShow.add(ent.row);
    }

    for (int i=0; i<toShow.size(); i++) {
            View v = toShow.get(i);
            if (v.getParent() == null) {
                mStackScroller.addView(v);
            }
    }
    ...
}
  1. 從mNotificationData對(duì)象中獲取一個(gè)list<Entry>對(duì)象
  2. 將mNotificationData中的每一個(gè)Entry對(duì)象的row屬性添加到List<ExpandableNotificationRow>中
  3. 將ExpandableNotificationRow添加到mStackScroller里面

這個(gè)mStackScroller是NotificationStackScrollLayout的對(duì)象,而這個(gè)NotificationStackScrollLayout是一個(gè)繼承自ViewGroup的屏歹,也就是我們下拉狀態(tài)欄看到的整片view的根view.
那么ExpandableNotificationRow也就是對(duì)應(yīng)著每一個(gè)通知了. ExpandableNotificationRow是繼承自FrameLayout的

我們前面說到把Entry先理解為一條通知隐砸,看到這里,其實(shí)添加的是Entry對(duì)象里面的row屬性到界面上蝙眶,也就是ExpandableNotificationRow

createNotificationViews

這個(gè)是解答開頭疑問的關(guān)鍵季希。 該方法是BaseStatusBar類的方法褪那。

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
    ...
    // Construct the expanded view.
    NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
    if (!inflateViews(entry, mStackScroller)) {
        handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
        return null;
    }
    return entry;
}

這里首先實(shí)例化了NotificationData的內(nèi)部類Entry。
NotificationData是一個(gè)十分重要的類式塌,里面有幾個(gè)比較重要的數(shù)據(jù)結(jié)構(gòu)
<pre>
ArrayMap<String, Entry> mEntries = new ArrayMap<>(); //所有Entry的集合
ArrayList<Entry> mSortedAndFiltered = new ArrayList<>(); //排序后的Entry集合
</pre>
那這個(gè)Entry到底是個(gè)什么東西呢博敬?先來看看這個(gè)類的定義

public static final class Entry {
       ...
       public ExpandableNotificationRow row; // the outer expanded view
       public View expanded; // the inflated RemoteViews
       public View expandedPublic; // for insecure lockscreens
       public View expandedBig;
       ...
 }

從定義里面可以看出,一個(gè)Entry對(duì)應(yīng)了一條通知欄的所有Data信息峰尝,其中比較重要的是row屬性偏窝,前面已經(jīng)碰到過了。最后添加界面上的也就是這個(gè)row武学。
inflateViews方法里面祭往,這個(gè)row會(huì)被賦值,我們來看看row是怎么被賦值的

private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
    ...
    //contentView和bigContentView是我們構(gòu)造Notification時(shí)傳過來的view
    RemoteViews contentView = sbn.getNotification().contentView;
    RemoteViews bigContentView = sbn.getNotification().bigContentView;
    ...
    ExpandableNotificationRow row;
    ...
    //使用指定view填充
    row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
                    parent, false);
    ...
    //這個(gè)expanded view就是我們在下拉狀態(tài)欄中看到的每一條view火窒,這里命名為expanded 應(yīng)該是狀態(tài)欄展開硼补,而不是通知展開
    //NotificationContentView是繼承自FrameLayout的,會(huì)根據(jù)不同狀態(tài)來控制顯示哪個(gè)view(默認(rèn)通知/展開通知)
    NotificationContentView expanded =
                (NotificationContentView) row.findViewById(R.id.expanded);
    ...

    //給每一條通知設(shè)置onClick的點(diǎn)擊事件熏矿,以來相應(yīng)我們設(shè)置的動(dòng)作.
    PendingIntent contentIntent = sbn.getNotification().contentIntent;
    final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
                    isHeadsUp);
    row.setOnClickListener(listener);
    ...

    ///////關(guān)鍵////////////
    View contentViewLocal = null;
    View bigContentViewLocal = null;
    //將構(gòu)造通知欄時(shí)設(shè)置的contentView & bigContentView(RemoteView)轉(zhuǎn)換為view
    contentViewLocal = contentView.apply(mContext, expanded,
                    mOnClickHandler, themePackageName);
    if (bigContentView != null) {
       bigContentViewLocal = bigContentView.apply(mContext, expanded,
                          mOnClickHandler, themePackageName);
    }
    ...
    //因?yàn)閑xpanded 是一個(gè)FrameLayout的ViewGroup已骇,所以往里面塞了2個(gè)view
    expanded.setContractedChild(contentViewLocal);
    expanded.setExpandedChild(bigContentViewLocal);
}

看完上面的代碼,先來坐個(gè)小節(jié)票编,整理下思路疾捍。在Entry.row添加到屏幕上前,做了如下的屬性賦值

  1. inflate布局文件status_bar_notification_row(這是每個(gè)通知欄的根view)
  2. 給根view設(shè)置監(jiān)聽器
  3. 將在構(gòu)造通知過程中的bigContentView 和 contentView 塞到通知欄的根view里面

到這里栏妖,一個(gè)通知欄從初始化到顯示的流程就講完了乱豆,但是最開頭的疑問不是還沒有解答嗎?來看答案

答案

contentView固定高度

expanded.setContractedChild方法前吊趾,傳遞進(jìn)來的ContentView都還是自義定的view宛裕,沒有做高度限制或者系統(tǒng)默認(rèn)的view. 最后顯示的時(shí)候卻被限制了,說明在setContractedChild方法里做了手腳

public void setContractedChild(View child) {
    ...
    sanitizeContractedLayoutParams(child);
    addView(child);
    ...
}
private void sanitizeContractedLayoutParams(View contractedChild) {
    LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
    lp.height = mSmallHeight;
    contractedChild.setLayoutParams(lp);
}

可以看到在sanitizeContractedLayoutParams方法里面论泛,不論傳遞進(jìn)來的contentView有多高最后的會(huì)被改成mSmallHeight的高度揩尸。這個(gè)mSmallHeight的值就是在SystemUI里面配置的,64dp

bigview最大高度

expanded.setExpandedChild的方法里面卻沒有做最大高度的限制屁奏,那么最大高度是在哪限制的呢岩榆?
這個(gè)時(shí)候就要看看ExpandableNotificationRow這個(gè)根view了
ExpandableNotificationRow繼承自ExpandableView,來看看onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //mMaxNotificationHeight是systemui中配置的值坟瓢,256dp
    int ownMaxHeight = mMaxNotificationHeight;
    ...
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        int childHeightSpec = newHeightSpec;
        ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
        if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
            if (layoutParams.height >= 0) {
                // An actual height is set
                childHeightSpec = layoutParams.height > ownMaxHeight
                    ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
                    : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
            }
            child.measure(
                    getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
                    childHeightSpec);
            int childHeight = child.getMeasuredHeight();
            maxChildHeight = Math.max(maxChildHeight, childHeight);
        } else {
            mMatchParentViews.add(child);
        }
    }
    int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
    newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
    for (View child : mMatchParentViews) {
        child.measure(getChildMeasureSpec(
                widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
                newHeightSpec);
    }
   ...

如果bigviewlayoutParams.height == ViewGroup.LayoutParams.MATCH_PARENT則高度就是newHeightSpec勇边。這個(gè)newHeightSpec要么是ownMaxHeight 要么是maxChildHeight,而這2個(gè)值的最大值就是256dp
如果bigviewlayoutParams.height 折联!= ViewGroup.LayoutParams.MATCH_PARENT粒褒,最大值也是maxChildHeight 也就是256dp

注意: 這里并沒有顯示bigview的最小高度,所以bigview的高度范圍是可以在(0,256dp ] 區(qū)間的

最后

a pic is worth a thousands words,   tow pics worth double, lol

類圖
Notification_class_diagram.jpg
流程圖
Notification_seq_diagram.jpg

相關(guān)閱讀

Notification之---NotificationListenerService5.0實(shí)現(xiàn)原理
Notification之----Android5.0實(shí)現(xiàn)原理(一)
Notification之----自定義樣式
Notification之----默認(rèn)樣式
Notification之----任務(wù)棧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诚镰,一起剝皮案震驚了整個(gè)濱河市奕坟,隨后出現(xiàn)的幾起案子祥款,更是在濱河造成了極大的恐慌,老刑警劉巖月杉,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刃跛,死亡現(xiàn)場離奇詭異,居然都是意外死亡苛萎,警方通過查閱死者的電腦和手機(jī)奠伪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來首懈,“玉大人绊率,你說我怎么就攤上這事【柯模” “怎么了滤否?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長最仑。 經(jīng)常有香客問我藐俺,道長,這世上最難降的妖魔是什么泥彤? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任欲芹,我火速辦了婚禮,結(jié)果婚禮上吟吝,老公的妹妹穿的比我還像新娘菱父。我一直安慰自己,他們只是感情好剑逃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布浙宜。 她就那樣靜靜地躺著,像睡著了一般蛹磺。 火紅的嫁衣襯著肌膚如雪粟瞬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天萤捆,我揣著相機(jī)與錄音裙品,去河邊找鬼。 笑死俗或,一個(gè)胖子當(dāng)著我的面吹牛市怎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蕴侣,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焰轻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昆雀?” 一聲冷哼從身側(cè)響起辱志,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狞膘,沒想到半個(gè)月后揩懒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挽封,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年已球,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辅愿。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡智亮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出点待,到底是詐尸還是另有隱情阔蛉,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布癞埠,位于F島的核電站状原,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏苗踪。R本人自食惡果不足惜颠区,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望通铲。 院中可真熱鬧毕莱,春花似錦、人聲如沸颅夺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碗啄。三九已至质和,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間稚字,已是汗流浹背饲宿。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胆描,地道東北人瘫想。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像昌讲,于是被迫代替她去往敵國和親国夜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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