摘自郭神的文章:
從Android 8.0系統(tǒng)開始,Google引入了通知渠道這個概念。
什么是通知渠道呢?顧名思義,就是每條通知都要屬于一個對應(yīng)的渠道。每個App都可以自由地創(chuàng)建當(dāng)前App擁有哪些通知渠道,但是這些通知渠道的控制權(quán)都是掌握在用戶手上的。用戶可以自由地選擇這些通知渠道的重要程度,是否響鈴、是否振動、或者是否要關(guān)閉這個渠道的通知。
擁有了這些控制權(quán)之后,用戶就再也不用害怕那些垃圾推送消息的打擾了,因?yàn)橛脩艨梢宰灾鞯剡x擇自己關(guān)心哪些通知、不關(guān)心哪些通知。舉個具體的例子,我希望可以即時收到支付寶的收款信息,因?yàn)槲也幌脲e過任何一筆收益,但是我又不想收到支付寶給我推薦的周圍美食,因?yàn)槲覜]錢只吃得起公司食堂。這種情況,支付寶就可以創(chuàng)建兩種通知渠道,一個收支,一個推薦,而我作為用戶對推薦類的通知不感興趣,那么我就可以直接將推薦通知渠道關(guān)閉,這樣既不影響我關(guān)心的通知,又不會讓那些我不關(guān)心的通知來打擾我了。
對于每個App來說,通知渠道的劃分是非常需要仔細(xì)考究的,因?yàn)橥ㄖ酪坏﹦?chuàng)建之后就不能再修改了,因此開發(fā)者需要仔細(xì)分析自己的App一共有哪些類型的通知,然后再去創(chuàng)建相應(yīng)的通知渠道。這里我們來參考一下Twitter的通知渠道劃分:
可以看到,Twitter就是根據(jù)自己的通知類型,對通知渠道進(jìn)行了非常詳細(xì)的劃分,這樣用戶的自主選擇性就比較高了,也就大大降低了用戶不堪其垃圾通知的騷擾而將App卸載的概率。
? ?創(chuàng)建通知渠道?
首先我們使用Android Studio來新建一個項(xiàng)目,就叫它NotificationTest吧。
創(chuàng)建好項(xiàng)目之后,打開app/build.gradle文件檢查一下,確保targetSdkVersion已經(jīng)指定到了26或者更高,如下所示:
applyplugin:'com.android.application'
android {
compileSdkVersion26
defaultConfig {
applicationId"com.example.notificationtest"
minSdkVersion15
targetSdkVersion26
versionCode1
versionName"1.0"
testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner"
}
}
可以看到,這里我在創(chuàng)建新項(xiàng)目的時候默認(rèn)targetSdkVersion就是26,如果你是低于26的話,說明你的Android SDK有些老了,最好還是更新一下。當(dāng)然如果你懶得更新也沒關(guān)系,手動把它改成26就可以了。
接下來修改MainActivity中的代碼,如下所示:
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId ="chat";
String channelName ="聊天消息";
intimportance = NotificationManager.IMPORTANCE_HIGH;
createNotificationChannel(channelId, channelName, importance);
channelId ="subscribe";
channelName ="訂閱消息";
importance = NotificationManager.IMPORTANCE_DEFAULT;
createNotificationChannel(channelId, channelName, importance);
}
}
@TargetApi(Build.VERSION_CODES.O)
privatevoidcreateNotificationChannel(String channelId, String channelName,intimportance){
NotificationChannel channel =newNotificationChannel(channelId, channelName, importance);
NotificationManager notificationManager = (NotificationManager) getSystemService(
NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
}
代碼不長,我來簡單解釋下。這里我們在MainActivity中創(chuàng)建了兩個通知渠道,首先要確保的是當(dāng)前手機(jī)的系統(tǒng)版本必須是Android 8.0系統(tǒng)或者更高,因?yàn)榈桶姹镜氖謾C(jī)系統(tǒng)并沒有通知渠道這個功能,不做系統(tǒng)版本檢查的話會在低版本手機(jī)上造成崩潰。
創(chuàng)建一個通知渠道的方式非常簡單,這里我封裝了一個createNotificationChannel()方法,里面的邏輯相信大家都看得懂。需要注意的是,創(chuàng)建一個通知渠道至少需要渠道ID、渠道名稱以及重要等級這三個參數(shù),其中渠道ID可以隨便定義,只要保證全局唯一性就可以。渠道名稱是給用戶看的,需要能夠表達(dá)清楚這個渠道的用途。重要等級的不同則會決定通知的不同行為,當(dāng)然這里只是初始狀態(tài)下的重要等級,用戶可以隨時手動更改某個渠道的重要等級,App是無法干預(yù)的。
上述代碼我是模擬了這樣一個場景。想象一下我們正在開發(fā)一個類似于微信的App,其中App通知主要可以分為兩類,一類是我和別人的聊天消息,這類消息非常重要,因此重要等級我設(shè)為了IMPORTANCE_HIGH。另一類是公眾號的訂閱消息,這類消息不是那么重要,因此重要等級我設(shè)為了IMPORTANCE_DEFAULT。除此之外,重要等級還可以設(shè)置為IMPORTANCE_LOW、IMPORTANCE_MIN,分別對應(yīng)了更低的通知重要程度。
現(xiàn)在就可以運(yùn)行一下代碼了,運(yùn)行成功之后我們關(guān)閉App,進(jìn)入到設(shè)置 -> 應(yīng)用 -> 通知當(dāng)中,查看NotificationTest這個App的通知界面,如下圖所示:
剛才我們創(chuàng)建的兩個通知渠道這里已經(jīng)顯示出來了。可以看到,由于這兩個通知渠道的重要等級不同,通知的行為也是不同的,聊天消息可以發(fā)出提示音并在屏幕上彈出通知,而訂閱消息只能發(fā)出提示音。
當(dāng)然,用戶還可以點(diǎn)擊進(jìn)去對該通知渠道進(jìn)行任意的修改,比如降低聊天消息的重要等級,甚至是可以完全關(guān)閉該渠道的通知。
至于創(chuàng)建通知渠道的這部分代碼,你可以寫在MainActivity中,也可以寫在Application中,實(shí)際上可以寫在程序的任何位置,只需要保證在通知彈出之前調(diào)用就可以了。并且創(chuàng)建通知渠道的代碼只在第一次執(zhí)行的時候才會創(chuàng)建,以后每次執(zhí)行創(chuàng)建代碼系統(tǒng)會檢測到該通知渠道已經(jīng)存在了,因此不會重復(fù)創(chuàng)建,也并不會影響任何效率。
————————————————————? ? ? ?讓通知顯示出來? ? ? ?——————————————————————
觸發(fā)通知的代碼和之前版本基本是沒有任何區(qū)別的,只是在構(gòu)建通知對象的時候,需要多傳入一個通知渠道ID,表示這條通知是屬于哪個渠道的。
那么下面我們就來讓通知顯示出來。
首先修改activity_main.xml中的代碼,如下所示:
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="發(fā)送聊天消息"
android:onClick="sendChatMsg"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="發(fā)送訂閱消息"
android:onClick="sendSubscribeMsg"
/>
這里我們在布局文件中加入了兩個按鈕,很顯然,一個是用于觸發(fā)聊天消息渠道通知的,一個是用于觸發(fā)訂閱消息渠道通知的。
接下來修改MainActivity中的代碼,如下所示:
publicclassMainActivityextendsAppCompatActivity{
...
publicvoidsendChatMsg(View view){
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification =newNotificationCompat.Builder(this,"chat")
.setContentTitle("收到一條聊天消息")
.setContentText("今天中午吃什么?")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
.setAutoCancel(true)
.build();
manager.notify(1, notification);
}
publicvoidsendSubscribeMsg(View view){
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification =newNotificationCompat.Builder(this,"subscribe")
.setContentTitle("收到一條訂閱消息")
.setContentText("地鐵沿線30萬商鋪搶購中!")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
.setAutoCancel(true)
.build();
manager.notify(2, notification);
}
}
這里我們分別在sendChatMsg()和sendSubscribeMsg()方法中觸發(fā)了兩條通知,創(chuàng)建通知的代碼就不再多做解釋了,和傳統(tǒng)創(chuàng)建通知的方法沒什么兩樣,只是在NotificationCompat.Builder中需要多傳入一個通知渠道ID,那么這里我們分別傳入了chat和subscribe這兩個剛剛創(chuàng)建的渠道ID。
現(xiàn)在重新運(yùn)行一下代碼,并點(diǎn)擊發(fā)送聊天消息按鈕,效果如下圖所示:
由于這是一條重要等級高的通知,因此會使用這種屏幕彈窗的方式來通知用戶有消息到來。然后我們可以下拉展開通知欄,這里也能查看到通知的詳細(xì)信息:
用戶可以通過快速向左或者向右滑動來關(guān)閉這條通知。
接下來點(diǎn)擊發(fā)送訂閱消息按鈕,你會發(fā)現(xiàn)現(xiàn)在屏幕上不會彈出一條通知提醒了,只會在狀態(tài)欄上顯示一個小小的通知圖標(biāo):
因?yàn)橛嗛喯⑼ㄖ闹匾燃壥悄J(rèn)級別,這就是默認(rèn)級別通知的展示形式。當(dāng)然我們還是可以下拉展開通知欄,查看通知的詳細(xì)信息:
不過上面演示的都是通知欄的傳統(tǒng)功能,接下來我們看一看Android 8.0系統(tǒng)中通知欄特有的功能。
剛才提到了,快速向左或者向右滑動可以關(guān)閉一條通知,但如果你緩慢地向左或者向右滑動,就會看到這樣兩個按鈕:
其中,左邊那個時鐘圖標(biāo)的按鈕可以讓通知延遲顯示。比方說這是一條比較重要的通知,但是我暫時沒時間看,也不想讓它一直顯示在狀態(tài)欄里打擾我,我就可以讓它延遲一段后時間再顯示,這樣我就暫時能夠先將精力放在專注的事情上,等過會有時間了這條通知會再次顯示出來,我不會錯過任何信息。如下所示:
而右邊那個設(shè)置圖標(biāo)的按鈕就可以用來對通知渠道進(jìn)行屏蔽和配置了,用戶對每一個App的每一個通知渠道都有絕對的控制權(quán),可以根據(jù)自身的喜好來進(jìn)行配置和修改。如下所示:
比如說我覺得訂閱消息老是向我推薦廣告,實(shí)在是太煩了,我就可以將訂閱消息的通知渠道關(guān)閉掉。這樣我以后就不會再收到這個通知渠道下的任何消息,而聊天消息卻不會受到影響,這就是8.0系統(tǒng)通知渠道最大的特色。
另外,點(diǎn)擊上圖中的所有類別就可以進(jìn)入到當(dāng)前應(yīng)用程序通知的完整設(shè)置界面。
——————————————————————? ? ? ?管理通知渠道? ? ? ?——————————————————————————
在前面的內(nèi)容中我們已經(jīng)了解到,通知渠道一旦創(chuàng)建之后就不能再通過代碼修改了。既然不能修改的話那還怎么管理呢?為此,Android賦予了開發(fā)者讀取通知渠道配置的權(quán)限,如果我們的某個功能是必須按照指定要求來配置通知渠道才能使用的,那么就可以提示用戶去手動更改通知渠道配置。
只講概念總是不容易理解,我們還是通過具體的例子來學(xué)習(xí)一下。想一想我們開發(fā)的是一個類似于微信的App,聊天消息是至關(guān)重要的,如果用戶不小心將聊天消息的通知渠道給關(guān)閉了,那豈不是所有重要的信息全部都丟了?為此我們一定要保證用戶打開了聊天消息的通知渠道才行。
修改MainActivity中的代碼,如下所示:
publicclassMainActivityextendsAppCompatActivity{
...
publicvoidsendChatMsg(View view){
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = manager.getNotificationChannel("chat");
if(channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
Intent intent =newIntent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
startActivity(intent);
Toast.makeText(this,"請手動將通知打開", Toast.LENGTH_SHORT).show();
}
}
Notification notification =newNotificationCompat.Builder(this,"chat")
...
.build();
manager.notify(1, notification);
}
...
}
這里我們對sendChatMsg()方法進(jìn)行了修改,通過getNotificationChannel()方法獲取到了NotificationChannel對象,然后就可以讀取該通知渠道下的所有配置了。這里我們判斷如果通知渠道的importance等于IMPORTANCE_NONE,就說明用戶將該渠道的通知給關(guān)閉了,這時會跳轉(zhuǎn)到通知的設(shè)置界面提醒用戶手動打開。
現(xiàn)在重新運(yùn)行一下程序,效果如下圖所示:
可以看到,當(dāng)我們將聊天消息的通知渠道關(guān)閉后,下次再次發(fā)送聊天消息將會直接跳轉(zhuǎn)到通知設(shè)置界面,提醒用戶手動將通知打開。
除了以上管理通知渠道的方式之外,Android 8.0還賦予了我們刪除通知渠道的功能,只需使用如下代碼即可刪除:
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.deleteNotificationChannel(channelId);
但是這個功能非常不建議大家使用。因?yàn)镚oogle為了防止應(yīng)用程序隨意地創(chuàng)建垃圾通知渠道,會在通知設(shè)置界面顯示所有被刪除的通知渠道數(shù)量,如下圖所示:
這樣是非常不美觀的,所以對于開發(fā)者來說最好的做法就是仔細(xì)規(guī)劃好通知渠道,而不要輕易地使用刪除功能。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 顯示未讀角標(biāo)
前面我們提到過,蘋果是從iOS 5開始才引入了通知欄功能,那么在iOS 5之前,iPhone都是怎么進(jìn)行消息通知的呢?使用的就是未讀角標(biāo)功能,效果如下所示:
實(shí)際上Android系統(tǒng)之前是從未提供過這種類似于iOS的角標(biāo)功能的,但是由于很多國產(chǎn)手機(jī)廠商都喜歡跟風(fēng)iOS,因此各種國產(chǎn)手機(jī)ROM都紛紛推出了自己的角標(biāo)功能。
可是國產(chǎn)手機(jī)廠商雖然可以訂制ROM,但是卻沒有制定API的能力,因此長期以來都沒有一個標(biāo)準(zhǔn)的API來實(shí)現(xiàn)角標(biāo)功能,很多都是要通過向系統(tǒng)發(fā)送廣播來實(shí)現(xiàn)的,而各個手機(jī)廠商的廣播標(biāo)準(zhǔn)又不一致,經(jīng)常導(dǎo)致代碼變得極其混雜。
值得高興的是,從8.0系統(tǒng)開始,Google制定了Android系統(tǒng)上的角標(biāo)規(guī)范,也提供了標(biāo)準(zhǔn)的API,長期讓開發(fā)者頭疼的這個問題現(xiàn)在終于可以得到解決了。
那么下面我們就來學(xué)習(xí)一下如何在Android系統(tǒng)上實(shí)現(xiàn)未讀角標(biāo)的效果。修改MainActivity中的代碼,如下所示:
publicclassMainActivityextendsAppCompatActivity{
...
@TargetApi(Build.VERSION_CODES.O)
privatevoidcreateNotificationChannel(String channelId, String channelName,intimportance){
NotificationChannel channel =newNotificationChannel(channelId, channelName, importance);
channel.setShowBadge(true);
NotificationManager notificationManager = (NotificationManager) getSystemService(
NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
publicvoidsendSubscribeMsg(View view){
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification =newNotificationCompat.Builder(this,"subscribe")
...
.setNumber(2)
.build();
manager.notify(2, notification);
}
}
可以看到,這里我們主要修改了兩個地方。第一是在創(chuàng)建通知渠道的時候,調(diào)用了NotificationChannel的setShowBadge(true)方法,表示允許這個渠道下的通知顯示角標(biāo)。第二是在創(chuàng)建通知的時候,調(diào)用了setNumber()方法,并傳入未讀消息的數(shù)量。
現(xiàn)在重新運(yùn)行一下程序,并點(diǎn)擊發(fā)送訂閱消息按鈕,然后在Launcher中找到NotificationTest這個應(yīng)用程序,如下圖所示:
可以看到,在圖標(biāo)的右上角有個綠色的角標(biāo),說明我們編寫的角標(biāo)功能已經(jīng)生效了。
需要注意的是,即使我們不調(diào)用setShowBadge(true)方法,Android系統(tǒng)默認(rèn)也是會顯示角標(biāo)的,但是如果你想禁用角標(biāo)功能,那么記得一定要調(diào)用setShowBadge(false)方法。
但是未讀數(shù)量怎么沒有顯示出來呢?這個功能還需要我們對著圖標(biāo)進(jìn)行長按才行,效果如下圖所示:
這樣就能看到通知的未讀數(shù)量是2了。