Notification之—-Android5.0落实原理(二)

 
故事如怎么说吧?就起15年7月上马说吧。那同样年本人正好正式毕业,然后就失去矣该校领毕业证,故事便是于此时开始之。 
                 
领完毕业证的我本着将要召开的转业绝非丝毫计划,对前途相同切片迷茫,我要怎么?我会干什么?我无懂得!本人拟的科班是机电一体化,工作是好找,但有关这些行业自我衷心是从心眼儿里拧的,为什么吧?在实习中我与几乎个高校校友去了平家机械厂,里面什么动静就是非多描述了,因为凡实习期薪资不高,我们吧未是坏注意,能解决温饱就推行,当时便是这么想的,后来工厂里比缺人,于是我们虽替各种职务,中午换班休息一个丁要是干三只人之在,说实话真的吓累呦。后来当那里愣神了3个月便愣住不停止了,马上临近过年我哪怕去职回家了,他们同自身同一走了几乎独,剩下的怀念再次坚持转,我从未劝他们什么的,人各有志,或许喜欢安静平静的活方法吧,但本身就是是为不了那种三点一线的生活,感觉自己便是机器人一样,也非知晓他们现在安了。 
                                               
在家过了年,又要考虑找工作之从业了,但还要休晓得会做呀,思前想后呢从不在获得,我大看不下去了,就吃自家错过自己姑姑哪里举行事情,一个月份2000,我姑是我们县里的一个品牌的经销商,就这么以我姑那里愣神了大体上年多,因为凡自己亲姑嘛,我之薪资每个月份都见面多500,而且人家还免了解,每次发我工钱的时还是他人还下班了走了,我姑姑把我留到最后,伸手叫住自己,“来,博儿”,然后手里拿在雷同由钞票给自己,并且还嘱咐一句“别都叫你妈,自己留点花”,因为是凭着罢都于夫人,所以工资必须上缴,没得协商,后来同我妈协商了每个月留500片零钱,我妈是未亮堂2500的行之,这样自己每个月还出1000片的零用钱,每天早起9点交地方,露个面子,意思是自家来了,然后骑车上自家那吱吱呀呀的电动车跑市场,县城能产生差不多好?再说自己个人于特殊,嗯(⊙_⊙)就是新鲜,所以自己的职责特别容易,一上午尽管ok了,中午吃个饭,然后同下午泡在网吧了打lol,dnf,5沾同样到一直回家,第一只月我特别卖力干活的,证明自己是行得通之,每天回去报个到,后来调皮了,一到5沾一直的走向回家的路,回家累打^O^。嘻滋滋又欢乐的生,但自己晓得就毫不自己怀念如果之生存,这样的生存平静安逸跟之前在工厂里实际不比不多,虽然本人弗掌握自家想如果的,但自掌握一个男孩终究要成为丈夫的,终究要自主,顶天立地的,既然早晚的从,不如提前好了。

概述

前文任课了Notification的布局,现在来讲说notification的出殡,以及发布前文留下的问号(自定义view不论高度是基本上高,最后只能显示也64dp,why?)

NotificationManager

在Notification构造就后,会调用NotificationManager的notify道来发送通知,我们便来看望该措施
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只是一个空壳,没有开啊实际的事务,只是将notify的动作交给了service来举行。
为主干的清,直接叫出enqueueNotificationWithTag的贯彻在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
                //如果用户设置了该引用不显示通知,并且不是系统通知的话,直接将该通知打分为-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;
                }

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

                ...
                //只显示有图标的通知
                if (notification.icon != 0) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(n, oldSbn);
                }
                ...
                //声音,震动,闪光灯的控制
                buzzBeepBlinkLocked(r);
            }
        }
    });
}

好望而想发通报必须得饱以下几独规范

  1. 非系统以,最多只能发送50单通知消息
  2. 用户设置了兴利用发送通知
  3. 被系统判定为非垃圾通知(该功能是cm自己长的,系统受见面起一个数据库,然后根据通知栏的Extra信息来配合,如果成功则判定为垃圾通知,但是该意义现在并没落实)
  4. 照会必须得有icon

反省通过后再次使用notifyPostedLocked办法做确实的出殡动作。buzzBeepBlinkLocked很简短,不浪费篇幅讲述了。

INotificationListener

notifyPostedLocked方法最后调用notifyPosted方法,我们一直来看望该措施

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

此地发出一个INotificationListener对象,一看到坐I初步的即足以了解,这里一定还要是一个IPC通信。
查阅源码可以知道,onNotificationPosted的落实是在SystemUI进程被,也尽管是我们的状态栏进程。

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);
            }
        }
    });
}

状态栏会根据通报之绝无仅有key值来判断该通报是否是翻新还是新增的。
咱为新增的呢例来讲.addNotification是一个泛方法,实现是在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个主要之政工,一个即是创建Entry实例,另外一个就是用Entry添加到状态栏上,然后就亮就了。
因为createNotificationViews的实现是在父类中,并且该法充分最主要,所以我们事先跳了该措施。
先行把Entry理解成一漫长通知,来讲addNotificationViews的兑现。

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会调用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对象中赢得一个list<Entry>对象
  2. 用mNotificationData中的各国一个Entry对象的row属性添加到List<ExpandableNotificationRow>中
  3. 将ExpandableNotificationRow添加到mStackScroller里面

这mStackScroller是NotificationStackScrollLayout的目标,而此NotificationStackScrollLayout是一个继续自ViewGroup的,也就是我们下拉状态栏看到底整片view的根view.
那么ExpandableNotificationRow也就算是指向承诺正在各级一个通了.
ExpandableNotificationRow是后续自FrameLayout的

咱前说到把Entry先理解啊同样漫长通知,看到此间,其实添加的是Entry对象中的row属性到界面上,也就算是ExpandableNotificationRow

createNotificationViews

是是解答开头疑问的根本。 该措施是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;
}

此处首先实例化了NotificationData的内类Entry。
NotificationData是一个怪生死攸关之近乎,里面来几单比根本的数据结构
<pre>
ArrayMap<String, Entry> mEntries = new ArrayMap<>();
//所有Entry的集合
ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
//排序后的Entry集合
</pre>
这就是说是Entry到底是单什么事物吧?先来看望这仿佛的概念

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;
       ...
 }

从概念里面可以看到,一个Entry对诺了一致漫漫通知栏的所有Data信息,其中比较根本的凡row属性,前面早已遇到了了。最后补充加界面上的也罢便是这个row。
inflateViews措施中,这个row会被赋值,我们来看看row是怎么让赋值的

private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
    ...
    //contentView和bigContentView是我们构造Notification时传过来的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);
    ...
    //这个expanded view就是我们在下拉状态栏中看到的每一条view,这里命名为expanded 应该是状态栏展开,而不是通知展开
    //NotificationContentView是继承自FrameLayout的,会根据不同状态来控制显示哪个view(默认通知/展开通知)
    NotificationContentView expanded =
                (NotificationContentView) row.findViewById(R.id.expanded);
    ...

    //给每一条通知设置onClick的点击事件,以来相应我们设置的动作.
    PendingIntent contentIntent = sbn.getNotification().contentIntent;
    final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
                    isHeadsUp);
    row.setOnClickListener(listener);
    ...

    ///////关键////////////
    View contentViewLocal = null;
    View bigContentViewLocal = null;
    //将构造通知栏时设置的contentView & bigContentView(RemoteView)转换为view
    contentViewLocal = contentView.apply(mContext, expanded,
                    mOnClickHandler, themePackageName);
    if (bigContentView != null) {
       bigContentViewLocal = bigContentView.apply(mContext, expanded,
                          mOnClickHandler, themePackageName);
    }
    ...
    //因为expanded 是一个FrameLayout的ViewGroup,所以往里面塞了2个view
    expanded.setContractedChild(contentViewLocal);
    expanded.setExpandedChild(bigContentViewLocal);
}

扣押了上面的代码,先来以个小节,整理下思路。在Entry.row添加到屏幕上前面,做了如下的性赋值

  1. inflate布局文件status_bar_notification_row(这是每个通知栏的根view)
  2. 受根view设置监听器
  3. 以于构造通知过程被之bigContentView 和 contentView
    塞到通知栏的根view里面

到这边,一个通知栏从初始化到展示的流程便提得了了,但是最好初步的问题不是尚从来不解答吗?来拘禁答案

答案

contentView固定高度www.4688.com

expanded.setContractedChild道前,传递进入的ContentView都还是自义定的view,没有举行高度限制或者系统默认的view.
最后显示的时光却为限定了,说明当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主意中,不论传递进入的contentView有多胜最后之会受改变化mSmallHeight的冲天。这个mSmallHeight的价就是是于SystemUI里面配置的,64dp

bigview最深高度

expanded.setExpandedChild的计中却并未举行最特别惊人的限量,那么最充分高度是当啊限制的啊?
本条时段将要看看ExpandableNotificationRow这个根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。这个newHeightSpec要么是ownMaxHeight
要么是maxChildHeight,而立2单价值的绝充分价值就是是256dp
如果bigviewlayoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT,最可怜价值吗是maxChildHeight
也即是256dp

只顾:
这里连从未显示bigview的卓绝小高度,所以bigview的冲天范围是可以以(0,256dp
] 区间的

最后

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

类图

www.4688.com 1

Notification_class_diagram.jpg

流程图

www.4688.com 2

Notification_seq_diagram.jpg

有关阅读

Notification之—NotificationListenerService5.0实现原理
Notification之—-Android5.0兑现原理(一)
Notification之—-自定义样式
Notification之—-默认样式
Notification之—-任务栈

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website