日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

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

發(fā)布時(shí)間:2023/12/20 Android 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Notification之 - Android5.0实现原理(二) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

概述

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

NotificationManager

在Notification構(gòu)造完成后,會(huì)調(diào)用NotificationManager的notify方法來(lái)發(fā)送通知,我們就來(lái)看看該方法
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());... }復(fù)制代碼

可以看出NotificationManager只是一個(gè)空殼,沒(méi)有做什么實(shí)際上的事情,只是把notify的動(dòng)作交給了service來(lái)做。
為了主干的清晰,直接給出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); }復(fù)制代碼

所以重要的是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() {@Overridepublic void run() {synchronized (mNotificationList) {...// blocked apps//如果用戶(hù)設(shè)置了該引用不顯示通知,并且不是系統(tǒng)通知的話,直接將該通知打分為-1000if (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ù)制代碼

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

  • 非系統(tǒng)應(yīng)用,最多只能發(fā)送50個(gè)通知消息
  • 用戶(hù)設(shè)置了允許應(yīng)用發(fā)送通知
  • 被系統(tǒng)判定為非垃圾通知(該功能是cm自己添加的,系統(tǒng)中會(huì)有一個(gè)數(shù)據(jù)庫(kù),然后根據(jù)通知欄的Extra信息來(lái)匹配,如果成功則判定為垃圾通知,但是該功能現(xiàn)在并沒(méi)有實(shí)現(xiàn))
  • 通知必須得有icon
  • 檢查通過(guò)后再使用notifyPostedLocked方法做真正的發(fā)送動(dòng)作。buzzBeepBlinkLocked很簡(jiǎn)單,不浪費(fèi)篇幅敘述了。

    INotificationListener

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

    private void notifyPosted(final ManagedServiceInfo info,final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {final INotificationListener listener = (INotificationListener)info.service;...listener.onNotificationPosted(sbnHolder, rankingUpdate);... }復(fù)制代碼

    這里有一個(gè)INotificationListener對(duì)象,一看到以I開(kā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() {@Overridepublic void run() {...boolean isUpdate = mNotificationData.get(sbn.getKey()) != null|| isHeadsUp(sbn.getKey());...if (isUpdate) {updateNotification(sbn, rankingMap);} else {addNotification(sbn, rankingMap);}}}); }復(fù)制代碼

    狀態(tài)欄會(huì)根據(jù)通知的唯一key值來(lái)判斷該通知是否是更新還是新增的。
    我們以新增的為例來(lái)講.addNotification是一個(gè)抽象方法,實(shí)現(xiàn)是在BaseStatusBar的子類(lèi)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);... }復(fù)制代碼

    該方法做了2個(gè)重要的事情,一個(gè)就是創(chuàng)建Entry實(shí)例,另外一個(gè)就是將Entry添加到狀態(tài)欄上,然后就顯示完成了。
    因?yàn)閏reateNotificationViews的實(shí)現(xiàn)是在父類(lèi)中,并且該方法十分重要,所以我們先跳過(guò)該方法。
    先把Entry理解成一條通知,來(lái)講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(); }復(fù)制代碼

    先直接將得到的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);}}... }復(fù)制代碼
  • 從mNotificationData對(duì)象中獲取一個(gè)list對(duì)象
  • 將mNotificationData中的每一個(gè)Entry對(duì)象的row屬性添加到List中
  • 將ExpandableNotificationRow添加到mStackScroller里面
  • 這個(gè)mStackScroller是NotificationStackScrollLayout的對(duì)象,而這個(gè)NotificationStackScrollLayout是一個(gè)繼承自ViewGroup的,也就是我們下拉狀態(tài)欄看到的整片view的根view.
    那么ExpandableNotificationRow也就是對(duì)應(yīng)著每一個(gè)通知了. ExpandableNotificationRow是繼承自FrameLayout的

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

    createNotificationViews

    這個(gè)是解答開(kāi)頭疑問(wèn)的關(guān)鍵。 該方法是BaseStatusBar類(lèi)的方法。

    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; }復(fù)制代碼

    這里首先實(shí)例化了NotificationData的內(nèi)部類(lèi)Entry。
    NotificationData是一個(gè)十分重要的類(lèi),里面有幾個(gè)比較重要的數(shù)據(jù)結(jié)構(gòu)


    ArrayMap mEntries = new ArrayMap<>(); //所有Entry的集合
    ArrayList mSortedAndFiltered = new ArrayList<>(); //排序后的Entry集合

    那這個(gè)Entry到底是個(gè)什么東西呢?先來(lái)看看這個(gè)類(lèi)的定義 public static final class Entry {...public ExpandableNotificationRow row; // the outer expanded viewpublic View expanded; // the inflated RemoteViewspublic View expandedPublic; // for insecure lockscreenspublic View expandedBig;...}復(fù)制代碼

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

    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {...//contentView和bigContentView是我們構(gòu)造Notification時(shí)傳過(guò)來(lái)的viewRemoteViews 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就是我們?cè)谙吕瓲顟B(tài)欄中看到的每一條view,這里命名為expanded 應(yīng)該是狀態(tài)欄展開(kāi),而不是通知展開(kāi)//NotificationContentView是繼承自FrameLayout的,會(huì)根據(jù)不同狀態(tài)來(lái)控制顯示哪個(gè)view(默認(rèn)通知/展開(kāi)通知)NotificationContentView expanded =(NotificationContentView) row.findViewById(R.id.expanded);...//給每一條通知設(shè)置onClick的點(diǎn)擊事件,以來(lái)相應(yīng)我們?cè)O(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)換為viewcontentViewLocal = contentView.apply(mContext, expanded,mOnClickHandler, themePackageName);if (bigContentView != null) {bigContentViewLocal = bigContentView.apply(mContext, expanded,mOnClickHandler, themePackageName);}...//因?yàn)閑xpanded 是一個(gè)FrameLayout的ViewGroup,所以往里面塞了2個(gè)viewexpanded.setContractedChild(contentViewLocal);expanded.setExpandedChild(bigContentViewLocal); }復(fù)制代碼

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

  • inflate布局文件status_bar_notification_row(這是每個(gè)通知欄的根view)
  • 給根view設(shè)置監(jiān)聽(tīng)器
  • 將在構(gòu)造通知過(guò)程中的bigContentView 和 contentView 塞到通知欄的根view里面
  • 到這里,一個(gè)通知欄從初始化到顯示的流程就講完了,但是最開(kāi)頭的疑問(wèn)不是還沒(méi)有解答嗎?來(lái)看答案

    答案

    contentView固定高度

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

    public void setContractedChild(View child) {...sanitizeContractedLayoutParams(child);addView(child);... }復(fù)制代碼private void sanitizeContractedLayoutParams(View contractedChild) {LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();lp.height = mSmallHeight;contractedChild.setLayoutParams(lp); }復(fù)制代碼

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

    bigview最大高度

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

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//mMaxNotificationHeight是systemui中配置的值,256dpint 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 setchildHeightSpec = 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);}...復(fù)制代碼

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

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

    最后

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

    類(lèi)圖

    Notification_class_diagram.jpg
    流程圖

    Notification_seq_diagram.jpg

    相關(guān)閱讀

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

    總結(jié)

    以上是生活随笔為你收集整理的Notification之 - Android5.0实现原理(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。