當前位置:
首頁 > 最新 > 從布局和實現的角度,聊聊 Notification

從布局和實現的角度,聊聊 Notification

作者簡介本文由強波原創並授權發布,未經原作者允許請勿轉載。本文從實現的角度,講解了 Android 的 Notification ,寫的非常的細緻,推薦大家看一下 ,希望大家喜歡。強波的博客地址:http://qiangbo.space/2017-07-07/AndroidAnatomy_Notification/在本文中,我們來詳細了解一下Android 上的 Notification 實現。Notification 是自 Android 發布以來就有的 API ,也是應用程序中最常用的功能的之一,開發者對其應當是相當的熟悉了。在 Android 近幾年的版本更新中,幾乎每個版本都會對系統通知界面,以及相關API做一些的改變。這些改變使得開發者可以更好的控制應用程序的通知樣式,同時也使得通知功能更易於用戶使用。本文我們來詳細看一下 Notification 方面的知識。開發者API這裡不打算對 Notification 基本的使用方式做過多講解,這方面內容對於很多開發者來說都已經是非常熟悉的了,並且網路上也很容易搜索到相關內容。下面只會說明 Notification 自 Android 5.0 以來的新增加功能。Heads-up NotificationHeads-up Notification 是 Android 5.0 上的新增功能。當設備處於使用狀態下(已經解鎖並且屏幕亮著)時,這種通知以一個小的浮動窗口的形式呈現出來,就像下面這樣:

這個樣式看起來像是對通知的一種壓縮,但是 Heads-up Notification 可以包含 Action Button 。用戶可以點擊 Action Button 進行相應的操作,也可以將這個通知界面移除掉但是不離開當前應用。這對於用戶體驗來說是一項非常好的改進,系統的來電通知就是這種形式的通知。在設備處於使用狀態下時,這種通知既不會干擾用戶當前的行為(可以直接將通知界面移除掉),又方便了用戶對於通知的處理(可以直接點擊 Action Button 來處理通知)。只要 Notification 滿足下面的兩種情況下任何一種,就會產生 Heads-up Notification:Notification 設置了 fullScreenIntent。Notification 是一個 High 優先順序的通知並且使用了鈴聲或震動。鎖屏上的Notification從 Android 5.0 開始,通知可以在鎖屏上顯示。開發者可以利用這個特性來實現媒體播放按鈕或者其他常用的操作。但同時,用戶也可以通過設置來決定是否在鎖屏界面上顯示某個應用的通知。開發者可以通過方法來控制通知顯示的詳細級別。這個方法接收三個級別的控制:VISIBILITY_PUBLIC顯示通知的全部內容。VISIBILITY_PRIVATE顯示通知的基本信息,例如通知的 icon 和 title ,但是不顯示詳細內容。

VISIBILITY_SECRET不顯示通知的任何內容。Notification直接回復從 Android 7.0 開始,用戶可以在通知界面上進行直接回復。直接回復按鈕附加在通知的下面。當用戶通過鍵盤迴復時,系統將用戶輸入的文字附在開發者指定的 Intent 上,然後發送給對應的應用。

創建一個包含直接回復按鈕的通知分為下面幾個步驟:創建一個 PendingIntent ,這個 PendingIntent 將在用戶輸入完成點擊發送按鈕之後觸發。因此我們需要為這個 PendingIntent 設置一個接受者,我們可以使用一個 BroadcastReceiver 來進行接收。創建一個 RemoteInput.Builder 對象實例,這個類的構造函數接收一個字元串作為 Key 來讓系統放入用戶輸入的文字。在接收方通過這個 key 來獲取輸入。通過方法將第1步創建的 RemoteInput 對象添加到Notification.Action 上。創建一個通知包含前面創建的 Notification.Action ,然後發送。相關代碼示例如下:

當用戶點擊回復按鈕時,系統會提示用戶輸入:

當用戶輸入完成並點擊發送按鈕之後,我們設置的被會觸發。前面我們設置了一個 BroadcastReceiver 來處理這個 Intent ,於是在 BroadcastReceiver 中可以通過下面這樣的方式來獲取用戶輸入的文本:

這裡還有兩點需要開發者注意的:用戶點擊完發送按鈕之後,該按鈕會變成一個旋轉的樣式表示這個動作還在進行中。開發者需要重新發送一條新的通知來更新這個狀態。通過 BroadcastReceiver 來處理這個發送事件的同時,請注意將 BroadcastReceiver 在 AndroidManifest.xml 中的配置設為:android:exported=」false」 。否則任何應用都可以發送一條 Intent 來觸發你的 BroadcastReceiver ,這可能對你應用造成危害。Bundling Notifications從 Android 7.0 開始,系統提供一個新的方式來展示連續的通知: Bundling notifications。這種展示方式特別適用於即時通訊類應用,因為這類應用會持續不斷的收到新的消息並發送通知。這種展示方式是以一種層次性的結構來組織通知。頂部是顯示組內概覽信息的消息,當用戶進一步展開組的時候,系統顯示組內的更多信息。如下圖所示:

Notification.Build 類中提供了相應的API來進行這種通知樣式的管理:通過 groupKey 將通知歸為一個組當 isGroupSummary = true 時表示將該條通知設為組內的 Summary 通知系統將根據這裡設置的 sortKey 進行排序Notification 消息樣式從 Android 7.0 開始,系統提供了 MessagingStyle API 來自定義通知的樣式。開發者可以自定義通知的各種 Label,包括:對話 Title ,附加消息以及通知的 Content view 等。下面是一段代碼示例:

這條通知顯示出來是下面這個樣子:

通知欄與通知窗口外部界面通知欄位於狀態欄中,在狀態欄的左側通過一系列應用的 Icon 來顯示通知:

用戶可以通過從屏幕上側下滑的方法展開通知窗口,通知窗口的上方是 Quick Settings 區域,下方是通知列表。用戶可以展開 Quick Settings 區域。

內部實現在了解了通知界面的外觀之後,我們就來看一下系統是如何實現這個界面的。在 SystemUI 的實現中,通過 XML 布局文件以及一系列自定義 Layout 類來管理通知界面。整個 Status Bar 通過 super_status_bar.xml 文件來進行布局,這個布局文件的根元素是一個自定義的 FrameLayout ,類名是 StatusBarWindowView 。這個布局文件的結構如下圖所示:

在這裡,我們重點要關注的就是選中的兩行:super_status_bar.xml 中 include 了一個名稱為status_bar的布局文件。super_status_bar.xml 中 include 了一個名稱為status_bar_expanded的布局文件。這裡的 status_bar 便是系統狀態欄的布局文件, status_bar_expanded 便是下拉的通知窗口的布局文件。status_bar.xml布局文件結構如下圖所示。這個布局文件的根元素是名稱為 PhoneStatusBarView 的自定義 FrameLayout 類。對照這個布局文件和手機上的狀態欄,我相信讀者應該很容易理解了:notification_icon_area正是系統顯示通知 icon 的區域system_icon_area是顯示系統圖標的區域,例如:Wifi,電話信息以及電池等clock是狀態欄上顯示時間的區域

下面我們再來看一下 status_bar_expanded.xml 這個布局文件的結構,這個布局文件的根元素是一個名稱為 NotificationPanelView 的類,這個類同樣是一個自定義的 FrameLayout 。

在這個布局文件中:頂部是一個名稱為keyguard_status_view的元素。這個便是該界面上的狀態欄布局。這個狀態欄顯示的內容和通常的狀態欄的內容是有所區別的,讀者可以回到上面相應的截圖對比一下不同場景下狀態欄顯示的內容。qs_auto_reinflate_container 是顯示 Quick Settings 的區域。這個區域其實是 include 了一個另外布局文件:qs_panel.xml。

notification_stack_scroller 便是真正顯示通知列表的地方,這是一個 NotificationStackScrollLayout 類型的元素。從名稱上我們就可以看出,這個元素是可以滾動的,因為通知的列表可能是很長的。

Notification從發送到顯示Notification的發送有了上面通知界面布局的知識之後,我們再看一下,應用程序中發送的通知是如何最終顯示到系統的通知界面上的。開發者通過創建 Notification 對象來發送通知。該對象中記錄了一條通知的所有詳細信息,Notification 類圖如下所示:

這裡的很多欄位相信開發者都很熟悉,因為這些欄位都是我們發送通知時要設置的。這裡需要說明的是這個欄位。Bundle 以鍵值對的形式存儲了可以通過 IPC 傳遞的一系列數據。當我們通過 Notification.buidler 構建 Notification 對象時,有一些自定義樣式的值都是存在這個 extras 欄位中的,例如下面這些:

Notification 類是一個 Parcelable 類,這意味著它可以通過Binder 被跨進程傳遞。我們通常不會手動創建 Notification ,而是通過Notification.Builder 類中的 setXXX 方法(上面已經列出了一些)來創建 Notification。很顯然,這個 Notification.Builde r類使用的是典型的 Builder 設計模式,通過這個類,簡化了我們創建 Notification 的過程,下圖是 Notification.Builde r類的類圖:

這個類提供了非常多的 setXXX 方法讓我們設置 Notification 的屬性,並且這些方法會返回 Builder 對象本身以便我們可以連續調用。最終,我們通過一個方法獲取到構造好的 Notification 對象。NotificationManagerService在構造好了 Notification 對象之後,我們通過NotificationManager 的(及其重載)方法真正將通知發送出去。我相信讀者自然能想到,這個 NotificationManager 一定也是通過 Binder 實現的。確實沒錯,真正實現通知發送的服務叫做 NotificationManagerService ,這個 Service 同樣位於system_server 進程中。NotificationManager 代表了服務的客戶端被應用程序所使用,而 NotificationManagerService 位於系統進程中接收和處理請求。Android 系統中大量的系統服務都是這樣的實現套路。notify 介面最終會調用到 NotificationManager 中的另一個叫做 notifyAsUser 的介面來發送通知,其實現如下:

這段代碼說明如下:通過 getService 方法獲取 NotificationManagerService 的遠程服務介面, getService 方法的實現其實就是通過ServiceManager 拿到 NotificationManagerService 的 Binder 對象。通過 mContext 為 Notification 添加一些附加屬性,這裡的 mContext 代表了調用發送通知介面的 Context ,系統服務中會通過這個 Context 來確定是誰在使用服務。在 LOLLIPOP_MR1 之上的版本(API Level 22)上,發送通知必須設置 Small Icon ,否則直接拋出異常。調用 NotificationManagerService 的遠程介面來真正進行通知的發送。接下來我們要關注的自然是NotificationManagerService.enqueueNotificationWithTag方法的實現。NotificationManagerService相關代碼位於以下路徑:/frameworks/base/services/core/java/com/android/server/notification/在NotificationManagerService.enqueueNotificationWithTag方法中,會將用戶發送過來的 Notification 對象包裝在一個 StatusBarNotification 對象中:

然後又將 StatusBarNotification 包裝在 NotificationRecord 對象中:

StatusBarNotification 構造函數中的其他參數,描述了發送通知的調用者的身份,包括:包名,調用者的 uid,pid 等等。這個身份的作用是:系統可以針對調用者身份的不同做不同的處理。例如:用戶可能關閉了某些應用的通知顯示,系統通過調用者的身份便可以確定這個應用的通知是否需要顯示在通知界面上。

而看到 NotificationRecord,讀者應該很自然能想到ActivityManagerService 中的 ActivityRecord,ProcessRecord 等結構。這些都是系統服務中用來描述應用程序中對象的對應結構。

下圖描述了上面三種結構的包含關係:

系統在創建 NotificationRecord 對象之後,會 Post一個Runnable 的 Task 進行通知的發送:

在 EnqueueNotificationRunnable 中,需要做下面幾件事情:處理通知的分組。檢查該通知是否已經被阻止(通過調用者的身份:包名及 uid )。對通知進行排序。判斷對已有通知更新,還是發送一條新的通知。調用 NotificationListeners.notifyPostedLocked。如果需要:處理聲音和震動。這裡只有需要說明一下。一條通知發送到系統之後,系統中可能會有很多模塊會對其感興趣(最基本的,會有模塊要將這個通知顯示在通知界面上)。發送通知是一個事件,處理通知是一個響應,當事件的響應者可能不止一個的時候,為了達到解耦這兩者之間的關係,很自然的會使用我們常見的監聽器模型(或者叫做:Observer 設計模式)。系統中,對於通知感興趣的監聽器通過 NotificationListenerService 類來表達。而這裡的便是對所有的 NotificationListenerService 進行回調通知。這其中有一個最重要的 NotificationListenerService 就是 BaseStatusBar 。因為它就是負責將通知顯示在通知界面上的監聽器。Notification的顯示BaseStatusBar 中對於通知發送的回調邏輯如下:

這段代碼的說明如下:每個 StatusBarNotification 對象都有一個 Key 值,這個值根據調用者的身份以及調用者設置的通知 id 生成。當應用程序通過同一個通知 id 發送了多次通知,這些通知的 Key 值是一樣的,由此可以對通知進行更新。mNotificationData(類型為 NotificationDat a)中記錄了系統所有的通知列表。如果是一個已經存在的通知需要更新,則先將存在的通知刪除。addNotification是一個抽象方法,由子類實現。在手機設備上,這個方法自然是由PhoneStatusBar 來實現。在方法中,會調用方法來最終將通知顯示在通知界面上,其代碼如下所示:

這裡的updateNotificationShade方法便是將通知的顯示內容添加到通知面板的顯示區域:NotificationStackScrollLayout中。而mIconController.updateNotificationIcons(mNotificationData)則是在notification_icon_area區域添加通知Icon。updateNotificationShade代碼比較長,但是邏輯是比較好理解的。主體邏輯就是對每一個需要顯示的通知創建一個ExpandableNotificationRow,然後設置對應的內容並添加到NotificationStackScrollLayout(mStackScroller 對象)中。瀏覽一下這段代碼便可以看到我們在 API 部分講解的一些 API 在系統服務中的實現:這裡了處理通知的分組,visibility 等相關信息。

至此,一條新發送的通知就真正顯示出來了。下面這幅圖描述了一條 Notification 從發送到顯示出來的流程:

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 承香墨影 的精彩文章:

工作中,AS和Git更配哦!

TAG:承香墨影 |

您可能感興趣

從源碼角度分析 Kotlin by lazy 的實現
專訪Catherine Otto—從BMJ子刊Heart主編的角度看文章發表
Nike SB Zoom Blazer Mid從別樣的角度詮釋花卉元素
Illustrator簡單的製作角度漸變效果
陳建州多角度曬 MADNESS x Converse Chuck Taylor 實物圖
價格分析的三個角度:超越競爭對手 is the key point!
從孩子的角度看51talk與vipkid哪個好?
photoshop中怎麼調整照片的透視角度
Kubernetes v1.10,從運維和開發者角度審視新功能!
從職業玩家角度看,4K顯示器和iMac的Retina屏哪個畫質會更好?
低配iPhone屏幕如何 光譜角度實測iPhone XR屏幕
Android圖片載入框架最全解析二,從源碼的角度理解Glide的執行流程
GloMo 從graph的角度看數據並應用在遷移學習中
從技術角度分析刀劍神域外傳 Gun Gale Online
從職業玩家角度看,Windows 哪些用戶體驗細節處理比 macOS 好?
從職業玩家角度看,如何評價微軟 Surface Go?
從抓包的角度分析connect函數的連接過程
重磅三方聯名!Timberland x BAPE x UNDFTD 多角度細節近賞
多角度,全方位賞析三星 Galaxy S9/S9 Plus
從AI角度出發,深度對比榮耀V10、OPPO find X和vivo NEX