Notification 是 Android 系統(tǒng)中一個(gè)特別特別重要的機(jī)制,它可以讓你在不打開 app 的情況下就可以便捷的查看消息、新聞秸歧、通知等等。
我個(gè)人認(rèn)為 Android 比 iOS 好用的一個(gè)很重要的地方就是通知思杯,嗯,一定是這樣的。
比如下圖:
當(dāng)一個(gè)app收到推送通知后色乾,會在狀態(tài)欄顯示該 app 的 logo誊册,這樣當(dāng)你過段時(shí)間查看手機(jī)的時(shí)候不會遺漏重要的消息。
- 微信:別人給你發(fā)消息暖璧,你不用打開微信就可以看到案怯,還可以直接回復(fù)(當(dāng)然了,微信沒有去實(shí)現(xiàn)這個(gè)功能)澎办;
- 網(wǎng)易新聞:通知欄可以直接看到新聞標(biāo)題和一部分內(nèi)容嘲碱,如果你不關(guān)心詳情的話,看看通知欄就可以了局蚀;
- 豆瓣:會給你推送覺得你感興趣的東西麦锯,如果你想看直接左滑或右滑刪除,想看的話點(diǎn)擊打開就可以琅绅;
- 網(wǎng)易云音樂:常駐通知欄扶欣,你可以看到當(dāng)前正在播放的歌曲,可以點(diǎn)擊暫停千扶、下一首料祠、上一首等等。
Notification 主要樣式
樣式 | 描述 |
---|---|
Normal | 標(biāo)準(zhǔn)通知澎羞,顯示標(biāo)題和單行內(nèi)容 |
BigText | 多行文字髓绽,顯示標(biāo)題和多行內(nèi)容 |
BigPicture | 顯示文字和圖片,顯示標(biāo)題妆绞、內(nèi)容和圖片 |
Inbox | 郵件顺呕,顯示標(biāo)題和多行郵件內(nèi)容 |
Messaging | 消息,顯示和朋友的對話內(nèi)容 |
Media | 音樂播放器括饶,顯示常駐通知欄塘匣,可以執(zhí)行各種操作 |
Custom | 自定義,自定義 layout 的通知欄 |
Normal Notification
圖中的1是 small icon,通過下列方法來設(shè)置:
builder.setSmallIcon(R.drawable.small_icon);
圖中的2是 app name,通過下列方法來設(shè)置:
builder.setTicker(context.getString(R.string.app_name));
圖中的3是 show time,通過下列方法來設(shè)置:
builder.setWhen(System.currentTimeMillis());
圖中的4是 content title,通過下列方法來設(shè)置:
builder.setContentTitle("標(biāo)題");
圖中的5是 content text,通過下列方法來設(shè)置:
builder.setContentText("內(nèi)容");
圖中的6是 large icon,通過下列方法來設(shè)置:
builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.large_icon));
<center></center>
下面是創(chuàng)建一個(gè)標(biāo)準(zhǔn)的通知欄的代碼:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClass(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
PendingIntent pendingIntent = PendingIntent.getActivity(context
, (int) SystemClock.uptimeMillis()
, intent
, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
//設(shè)置通知欄大圖標(biāo)巷帝,上圖中右邊的大圖
builder.setLargeIcon(BitmapFactory.decodeResource(
context.getResources(), R.drawable.large_icon))
// 設(shè)置狀態(tài)欄和通知欄小圖標(biāo)
.setSmallIcon(R.drawable.small_icon)
// 設(shè)置通知欄應(yīng)用名稱
.setTicker(context.getString(R.string.app_name))
// 設(shè)置通知欄顯示時(shí)間
.setWhen(System.currentTimeMillis())
// 設(shè)置通知欄標(biāo)題
.setContentTitle("標(biāo)題")
// 設(shè)置通知欄內(nèi)容
.setContentText("內(nèi)容")
// 設(shè)置通知欄點(diǎn)擊后是否清除,設(shè)置為true扫夜,當(dāng)點(diǎn)擊此通知欄后楞泼,它會自動消失
.setAutoCancel(true)
// 將Ongoing設(shè)為true 那么左滑右滑將不能刪除通知欄
//.setOngoing(true);
// 設(shè)置通知欄點(diǎn)擊意圖
.setContentIntent(pendingIntent);
// 鈴聲、閃光笤闯、震動均系統(tǒng)默認(rèn)
.setDefaults(Notification.DEFAULT_ALL);
// 設(shè)置為public后堕阔,通知欄將在鎖屏界面顯示
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
// 從Android4.1開始,可以通過以下方法颗味,設(shè)置通知欄的優(yōu)先級超陆,優(yōu)先級越高的通知排的越靠前,
// 優(yōu)先級低的,不會在手機(jī)最頂部的狀態(tài)欄顯示圖標(biāo)
// 設(shè)置優(yōu)先級為PRIORITY_MAX时呀,將會在手機(jī)頂部顯示通知欄
.setPriority(NotificationCompat.PRIORITY_MAX);
NotificationManager noti = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
noti.notify((int) System.currentTimeMillis(), builder.build());
點(diǎn)擊消失
如果你想點(diǎn)擊通知后张漂,該通知自動消失,那么你就需要調(diào)用 setAutoCancel(boolean b) 這個(gè)方法谨娜,并將其設(shè)置為 true航攒;
設(shè)置鈴聲、震動趴梢、閃光效果
setDefaults(Notification.DEFAULT_ALL) 表示系統(tǒng)系統(tǒng)默認(rèn)的鈴聲漠畜、震動和閃光效果。舉例:
自定義通知鈴聲:
- 注釋 setDefaults(Notification.DEFAULT_ALL) 方法坞靶;
- 調(diào)用 builder.setSound(Uri sound);
- 你還可以通過 setVibrate 和 setLights 設(shè)置震動和閃光效果
鎖屏顯示
如上圖所示憔狞,如果你想在鎖屏情況下顯示通知,則需要調(diào)用 setVisibility(int visibility)彰阴,并將其設(shè)置為NotificationCompat.VISIBILITY_PUBLIC
頭部顯示
如上圖所示瘾敢,如果你想通知顯示在手機(jī)頭部,比如來電時(shí)的通知硝枉,那么你就需要調(diào)用 setPriority(int pri)廉丽,并將其設(shè)置為NotificationCompat.PRIORITY_MAX
禁止刪除
如果你想讓你的通知欄常駐,用戶無法滑動刪除妻味,也不能通過手機(jī)的清除鍵刪除正压,類似于網(wǎng)易云音樂等 app 的通知欄,那么你可以設(shè)置 setOngoing 方法责球,也設(shè)為 true焦履,這樣,通知欄只能通過代碼調(diào)用 cancel 方法才能消失雏逾;
通知點(diǎn)擊事件
使用 build.setContentIntent(PendingIntent intent) 設(shè)置點(diǎn)擊事件嘉裤。
先看官方的代碼吧:
/**
* Supply a {@link PendingIntent} to send when the notification is clicked.
* If you do not supply an intent, you can now add PendingIntents to individual
* views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
* RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to
* read {@link Notification#contentIntent Notification.contentIntent} for
* how to correctly use this.
*/
public Builder setContentIntent(PendingIntent intent) {
mContentIntent = intent;
return this;
}
從代碼注釋可以看出,當(dāng)通知被點(diǎn)擊的時(shí)候栖博,系統(tǒng)會發(fā)送一個(gè) PendingIntent屑宠。
從字面上看,PendingIntent:等待的仇让,未決定的Intent典奉。
要獲取 PendingIntent 對象,有以下靜態(tài)方法:
- getActivity(Context context, int requestCode, Intent intent, int flags)
- getActivities(Context context, int requestCode, Intent[] intents, int flags)
- getBroadcast(Context context, int requestCode, Intent intent, int flags)
- getService(Context context, int requestCode, Intent intent, int flags)
分別對應(yīng)著 Intent 的3個(gè)行為丧叽,跳轉(zhuǎn)到一個(gè)或幾個(gè) activity 組件卫玖、打開一個(gè)廣播組件和打開一個(gè)服務(wù)組件。
PendingIntent 是一種特殊的 Intent踊淳。主要的區(qū)別在于 Intent 的執(zhí)行立刻的假瞬,而 PendingIntent 的執(zhí)行不是立刻的。
PendingIntent 執(zhí)行的操作實(shí)質(zhì)上是參數(shù)傳進(jìn)來的 Intent 的操作,但是使用 PendingIntent 的目的在于它所包含的 Intent 的操作的執(zhí)行是需要滿足某些條件的脱茉。
一般情況下剪芥,點(diǎn)擊通知都是打開特定的 Activity,你可以直接使用 getActivity 或 getActivities芦劣,也可以使用 getBroadcast 或 getService粗俱,然后在廣播或服務(wù)中做一些啟動 Activity 的操作。
所有的 app 點(diǎn)擊通知欄啟動 Activity 的方式不外乎以下三種:
- 如果要啟動的 app 已經(jīng)被殺掉虚吟,則啟動主界面寸认,否則啟動棧頂?shù)?Activity,使用方式如下:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClass(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
PendingIntent pendingIntent = PendingIntent.getActivity(context
, (int) SystemClock.uptimeMillis()
, intent
, PendingIntent.FLAG_UPDATE_CURRENT);
- 啟動特定的 Activity串慰,不管當(dāng)前 app 有沒有被殺掉偏塞,點(diǎn)擊后退鍵后返回到當(dāng)前 app 的主界面,例如微信邦鲫、QQ等等灸叼,使用方式有兩種:
使用 TaskStackBuilder
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setClass(context, ThirdActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(ThirdActivity.class);
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent = stackBuilder.getPendingIntent(
(int) SystemClock.uptimeMillis()
, PendingIntent.FLAG_UPDATE_CURRENT);
使用 getActivities
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent secondIntent = new Intent(context, SecondActivity.class);
Intent[] intents = {intent, secondIntent};
PendingIntent pendingIntent = PendingIntent.getActivities(context
, (int) SystemClock.uptimeMillis()
, intents
, PendingIntent.FLAG_UPDATE_CURRENT);
- 啟動特定的 Activity,啟動前判斷庆捺,如果當(dāng)前程序沒有被殺掉且除了當(dāng)前被啟動的 Activity 外至少有一個(gè) Activity 沒有被 finish古今,點(diǎn)擊后退鍵則返回到之前棧頂?shù)?Activity;如果當(dāng)前程序被殺掉或之前任務(wù)棧沒有 Activity滔以,點(diǎn)擊后退鍵則返回到當(dāng)前 app 的主界面捉腥,例如網(wǎng)易云音樂。
if (Util.isAppAlive(context, BuildConfig.APPLICATION_ID)
&& !DemoApplication.getInstance().isAllActivityFinished()) {
Intent intent = new Intent();
intent.setClass(context, ThirdActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
} else {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent secondIntent = new Intent(context, SecondActivity.class);
Intent[] intents = {intent, secondIntent};
startActivities(intents);
}
獲取 PendingIntent 對象的第四個(gè)參數(shù)為 flags你画,一般 flags 有5種選擇:
- FLAG_ONE_SHOT:表明這里構(gòu)建的 PendingIntent 只能使用一次抵碟,使用后將被自動刪除,再次使用將會報(bào)錯(cuò)坏匪;
- FLAG_NO_CREATE:如果前一個(gè) PendingIntent 已經(jīng)不存在了拟逮,將不再構(gòu)建它,直接返回 null适滓;
- FLAG_CANCEL_CURRENT:如果構(gòu)建的 PendingIntent 已經(jīng)存在敦迄,則取消前一個(gè),重新構(gòu)建一個(gè)凭迹;
- FLAG_UPDATE_CURRENT:如果構(gòu)建的PendingIntent已經(jīng)存在罚屋,那么系統(tǒng)將不會重復(fù)創(chuàng)建,只是將他的 intent 中的參數(shù)替換蕊苗;
- FLAG_IMMUTABLE 表示這是一個(gè)不可變的 PendingIntent。
通常情況下沿彭,我們會使用 FLAG_UPDATE_CURRENT 這個(gè) flags 值來構(gòu)造 PendingIntent朽砰,但是使用 FLAG_UPDATE_CURRENT 經(jīng)常會發(fā)生點(diǎn)擊通知欄后沒有任何響應(yīng),時(shí)靈時(shí)不靈。
原來使用 FLAG_UPDATE_CURRENT 這個(gè)參數(shù)后瞧柔,系統(tǒng)不會重新創(chuàng)建新的 PendingIntent漆弄,這樣一來,如果你傳遞的 Intent 的 extra 參數(shù)沒有變化的話造锅,那么系統(tǒng)就會認(rèn)為你沒有發(fā)送新的 PendingIntent撼唾,這樣就不會重新響應(yīng)你的點(diǎn)擊事件。一般情況下哥蔚,為了能夠區(qū)分每次的 PendingIntent 不一樣倒谷,我們常常會在構(gòu)造 Intent 的時(shí)候,設(shè)置不同的 Action 或 Extra 值糙箍,這樣一來渤愁,即便是使用 FLAG_UPDATE_CURRENT 這個(gè)參數(shù),系統(tǒng)也會因?yàn)閭髦祬?shù)的變化而去響應(yīng)每次的點(diǎn)擊事件深夯。不過這種解決方法還是很坑爹的抖格,大部分時(shí)候我們根本不需要傳遞額外的 Action 或 Extra 值,這個(gè)時(shí)候我們只需要把
PendingIntent.getActivity(context, requestCode, intent, flags)
這個(gè)方法中的第二個(gè)參數(shù)(requestCode)設(shè)置成唯一的標(biāo)識就可以了咕晋,可以直接使用
int requestCode = (int) SystemClock.uptimeMillis();
當(dāng)然你可以直接使用 FLAG_CANCEL_CURRENT 這個(gè) flag雹拄,每次都會創(chuàng)建一個(gè)新的,那么上面的問題就不存在了掌呜。
通知欄添加 Action
通過添加 Action,用戶可以不用打開 app 就可以直接對收到的信息就行操作站辉,比如回復(fù)短信呢撞、設(shè)置短信已讀和刪除短信等等。
是不是很方便饰剥?是不是比 iOS 厲害多了殊霞?
Android 提供了一個(gè)叫做 RemoteInput 的類,給通知欄的回復(fù)操作提供強(qiáng)有力的支持汰蓉,用戶點(diǎn)擊回復(fù)后绷蹲,直接在通知欄顯示輸入框,輸入內(nèi)容后點(diǎn)擊發(fā)送即可顾孽。那怎么使用呢祝钢?直接看代碼吧。
public final static String REPLY = "reply";
public final static String KEY_TEXT_REPLY = "key_text_reply";
RemoteInput input = new RemoteInput.Builder(KEY_TEXT_REPLY).setLabel("請輸入內(nèi)容").build();
Intent reply = new Intent();
reply.setAction(REPLY);
reply.setClass(context, NotificationReceiver.class);
PendingIntent pendingReply = PendingIntent.getBroadcast(context
, (int) SystemClock.uptimeMillis()
, reply
, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(0, "回復(fù)", pendingReply)
.addRemoteInput(input)
.setAllowGeneratedReplies(true)
.build();
builder.addAction(action);
這里使用了發(fā)送廣播組件若厚,你可以在接收到的廣播中直接獲取輸入框輸入的內(nèi)容拦英。
case REPLY:{
//獲取輸入框輸入的內(nèi)容
Bundle input = RemoteInput.getResultsFromIntent(intent);
if (input == null)
return;
//Do something
String content = input.getCharSequence(KEY_TEXT_REPLY)
Toast.makeText(context, content, Toast.LENGTH_SHORT).show();
break;
}
添加設(shè)置已讀和刪除操作
Intent maskAsRead = new Intent();
maskAsRead.setAction(MAKE_AS_READ);
maskAsRead.setClass(context, NotificationReceiver.class);
PendingIntent pendingMaskAsRead = PendingIntent.getBroadcast(context,
(int) SystemClock.uptimeMillis()
, maskAsRead
, PendingIntent.FLAG_UPDATE_CURRENT);
Intent delete = new Intent();
delete.setAction(DELETE);
delete.setClass(context, NotificationReceiver.class);
PendingIntent pendingDelete = PendingIntent.getBroadcast(context,
(int) SystemClock.uptimeMillis()
, delete
, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(0, "mask as read", pendingMaskAsRead);
builder.addAction(0, "delete", pendingDelete);
同樣的你可以在 Receiver 中獲取你做了什么操作,下一步該做什么等等测秸。
啟動 Notification
啟動 Notification 的方法很簡單疤估,如下所示:
NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(ID, builder.build());
對于同一個(gè) ID灾常,后啟動的 Notification 會替換先啟動的 Notification ,所以:
對于類似于微信這樣的聊天通知铃拇,你需要為每一個(gè)給你發(fā)消息的用戶設(shè)置一個(gè)獨(dú)一無二的 ID钞瀑,比如可以根據(jù)用戶 ID來生成;
對于類似于網(wǎng)易新聞這樣的新聞通知慷荔,你需要為每個(gè)通知設(shè)置不同的獨(dú)一無二的 ID雕什,比如使用 (int) SystemClock.uptimeMillis();
如果你的通知欄只需要顯示一個(gè)的話显晶,只需要用同一個(gè) ID 就可以贷岸;
對于需要代碼清除的 Notification,你需要為 ID 設(shè)置一個(gè)固定的獨(dú)一無二的值吧碾,比如短信通知凰盔,你點(diǎn)了刪除操作,刪除短信后你就需要清除對應(yīng)的 Notification
清除 Notification
兩種方式:
根據(jù) ID 清除特定的 Notification:
notificationManager.cancel(id);
清除該 app 所有的 Notification:
notificationManager.cancelAll();
詳細(xì)的代碼請參見GitHub
敬請期待 Android Notifications 百發(fā)百中之第二發(fā)