Quickview
- A status notification allows your application to notify the user of an event without interupting their current activity
- You can attach an intent to your notification that the system will initiate when the user clicks it
In this document
- The Basics
- Responding to Notifications
- Managing your Notifications
- Creating a Notification
- Creating a Custom Notification Layout
Key classes
See also
一个状态栏通知会在状态栏上显示一个图标(并且会显示一行信息,by汉尼拔萝卜:这个显示文字已经被取消了,因为这样子显示在状态栏上太难看了,google终于把它给取消了。顺带说一句,因为翻译的文档并不是对应Google最新的api,所以有些功能会与最新的api有出入),下拉状态栏后会显示完整的信息。当用户点击这个 notification 时,系统就会处理创建 notification 是传入的 Intent
(通常是启动一个 Activity).你也可以给你的notification添加声音、震动、闪光灯功能。
当后台服务需要提示用户来响应某个事件时,应该使用状态栏通知。后台服务不应该自己去启动一个 activity 来与用户互动,它应该创建一个状态栏通知,当用户选择这个通知时再去启动 activity.
Figure 1 在状态栏的左边显示了一个通知图像.
Figure 2 显示了状态栏窗口的通知消息.
Notification Design
For design guidelines, read Android Design's Notifications guide.
The Basics
可以在 Activity
或者 Service
中初始化一个状态栏通知,但是由于 activity 只能在它运行在前台并获取焦点时采取操作,所以状态栏通知通常都是由服务创建的。这也就是说,即使你在使用其他的程序或者你的设备在休眠,一样可以弹出状态栏通知。必须使用 Notification
类和 NotificationManager
类来创建一个状态栏通知。
使用Notification
实例去配置一个状态栏通知的属性,比如图标、内容、其他的设置(比如声音)等。NotificationManager
是用来发送和管理状态栏通知的系统服务,不要直接去新建这个类的对象,应该使用 getSystemService()
方法来获取 NotificationManager
对象的引用。然后,通过notify()
方法将你的 Notification
发送出去.
创建状态栏通知:
- 获取
NotificationManager
的引用:String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
- 创建
Notification
:int icon = R.drawable.notification_icon; CharSequence tickerText = "Hello"; long when = System.currentTimeMillis(); Notification notification = new Notification(icon, tickerText, when);
- 定义通知的内容和
PendingIntent
:Context context = getApplicationContext(); CharSequence contentTitle = "My notification"; CharSequence contentText = "Hello World!"; Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
- 把
Notification
传给NotificationManager
:private static final int HELLO_ID = 1; mNotificationManager.notify(HELLO_ID, notification);
这时候,用户就可以看到状态栏通知啦.
Responding to Notifications
状态栏通知的核心部分就是围绕着如何设计用户与用户界面进行交互。你必须在应用程序中提供正确的实现。
有两种常用的状态栏通知,其中之一是日历发出的事件提醒通知,还有一种是收到邮件时邮箱发出的通知。他们代表两种处理状态栏通知的典型模式:启动一个不属于主程序的 activity,或者启动一整个应用的实例来代表通知的各个状态点.
下面两个段落描述了两种模式下的 activity 返回栈是如何工作的,首先是处理日历的通知:
- 用户正在日历里新建一个事件,它想到需要到邮箱中复制一段信息。
- 用户选择注解面 > 邮箱.
- 在邮箱界面时,日历弹出了一个新会议通知。
- 用户点击通知,将会跳转到日历中的 activity,显示这次会议的具体信息.
- 用户看完了会议信息,按下返回键,将会回到邮箱界面,也是当时收到通知后离开的那个界面.
处理邮件通知:
- 用户正在写一封邮件,此时需要去日历中查看日期.
- 主界面 > 日历.
- 当在日历中时,收到一个新邮件通知.
- 用户选择这个通知,跳转到了这封新邮件的详细信息界面,这个界面代替了之前写邮件的界面,但是之前写的内容会存到草稿中去.
- 用户点击一次返回键,返回到了邮件列表中(邮箱的典型流程),再按一次返回键就会回到日历中去.
在一个类似邮箱通知中,通知启动的界面代表该通知状态中的主程序。举个例子,当点击邮件通知启动邮件界面时,根据收到邮件的数量要么显示邮件列表、要么显示一个邮件详情。为了实现这样的行为,我们将启动新的activity栈来表示通知的不管应用程序的状态是什么.
下面的代码展示如何实现这样的通知。最重要的方法是 makeMessageIntentStack()
,它构造了代表新的acticity栈中的 intents.(如果使用fragments,你会需要初始化你的 fragment和应用状态,以便当用户点击返回时,会返回到fragment的上一个状态) 这个方法的核心代码是 Intent.makeRestartActivityTask()
方法,它会使用合适的标志创建 activity 栈中的根 activity,比如 Intent.FLAG_ACTIVITY_CLEAR_TASK
.
/** * This method creates an array of Intent objects representing the * activity stack for the incoming message details state that the * application should be in when launching it from a notification. */ static Intent[] makeMessageIntentStack(Context context, CharSequence from, CharSequence msg) { // A typical convention for notifications is to launch the user deeply // into an application representing the data in the notification; to // accomplish this, we can build an array of intents to insert the back // stack stack history above the item being displayed. Intent[] intents = new Intent[4]; // First: root activity of ApiDemos. // This is a convenient way to make the proper Intent to launch and // reset an application's task. intents[0] = Intent.makeRestartActivityTask(new ComponentName(context, com.example.android.apis.ApiDemos.class)); // "App" intents[1] = new Intent(context, com.example.android.apis.ApiDemos.class); intents[1].putExtra("com.example.android.apis.Path", "App"); // "App/Notification" intents[2] = new Intent(context, com.example.android.apis.ApiDemos.class); intents[2].putExtra("com.example.android.apis.Path", "App/Notification"); // Now the activity to display to the user. Also fill in the data it // should display. intents[3] = new Intent(context, IncomingMessageView.class); intents[3].putExtra(IncomingMessageView.KEY_FROM, from); intents[3].putExtra(IncomingMessageView.KEY_MESSAGE, msg); return intents; } /** * The notification is the icon and associated expanded entry in the * status bar. */ void showAppNotification() { // look up the notification manager service NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // The details of our fake message CharSequence from = "Joe"; CharSequence message; switch ((new Random().nextInt()) % 3) { case 0: message = "r u hungry? i am starved"; break; case 1: message = "im nearby u"; break; default: message = "kthx. meet u for dinner. cul8r"; break; } // The PendingIntent to launch our activity if the user selects this // notification. Note the use of FLAG_CANCEL_CURRENT so that, if there // is already an active matching pending intent, cancel it and replace // it with the new array of Intents. PendingIntent contentIntent = PendingIntent.getActivities(this, 0, makeMessageIntentStack(this, from, message), PendingIntent.FLAG_CANCEL_CURRENT); // The ticker text, this uses a formatted string so our message could be localized String tickerText = getString(R.string.imcoming_message_ticker_text, message); // construct the Notification object. Notification notif = new Notification(R.drawable.stat_sample, tickerText, System.currentTimeMillis()); // Set the info for the views that show in the notification panel. notif.setLatestEventInfo(this, from, message, contentIntent); // We'll have this notification do the default sound, vibration, and led. // Note that if you want any of these behaviors, you should always have // a preference for the user to turn them off. notif.defaults = Notification.DEFAULT_ALL; // Note that we use R.layout.incoming_message_panel as the ID for // the notification. It could be any integer you want, but we use // the convention of using a resource id for a string related to // the notification. It will always be a unique number within your // application. nm.notify(R.string.imcoming_message_ticker_text, notif); }
在一个日历的通知中,通知启动的界面通常不属于应用程序的进程。比如当用户收到日历的通知时,选择这个通知将会启动一个特殊界面来显示代办事项—这个界面仅能从通知中启动,无法通过日历导航过去.
下面的代码发送了一条这样的通知,与上面的通知相似,但是 PendingIntent
只能展示一个activity,也就是专门用来显示代办事项的activity.
/** * The notification is the icon and associated expanded entry in the * status bar. */ void showInterstitialNotification() { // look up the notification manager service NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // The details of our fake message CharSequence from = "Dianne"; CharSequence message; switch ((new Random().nextInt()) % 3) { case 0: message = "i am ready for some dinner"; break; case 1: message = "how about thai down the block?"; break; default: message = "meet u soon. dont b late!"; break; } // The PendingIntent to launch our activity if the user selects this // notification. Note the use of FLAG_CANCEL_CURRENT so that, if there // is already an active matching pending intent, cancel it and replace // it with the new Intent. Intent intent = new Intent(this, IncomingMessageInterstitial.class); intent.putExtra(IncomingMessageView.KEY_FROM, from); intent.putExtra(IncomingMessageView.KEY_MESSAGE, message); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // The ticker text, this uses a formatted string so our message could be localized String tickerText = getString(R.string.imcoming_message_ticker_text, message); // construct the Notification object. Notification notif = new Notification(R.drawable.stat_sample, tickerText, System.currentTimeMillis()); // Set the info for the views that show in the notification panel. notif.setLatestEventInfo(this, from, message, contentIntent); // We'll have this notification do the default sound, vibration, and led. // Note that if you want any of these behaviors, you should always have // a preference for the user to turn them off. notif.defaults = Notification.DEFAULT_ALL; // Note that we use R.layout.incoming_message_panel as the ID for // the notification. It could be any integer you want, but we use // the convention of using a resource id for a string related to // the notification. It will always be a unique number within your // application. nm.notify(R.string.imcoming_message_ticker_text, notif); }
然而这样还不够,因为android默认所有的组件都跑在一个进程当中,所有这样简单启动一个 activity可能会将这个 activity 加入到你的应用的返回栈中。想要正确的实现这样的通知,需要在 manifest 文件中给 activity 添加这些属性:android:launchMode="singleTask"
,
android:taskAffinity=""
和
android:excludeFromRecents="true"
.完整的描述如下:
<activity android:name=".app.IncomingMessageInterstitial" android:label="You have messages" android:theme="@style/ThemeHoloDialog" android:launchMode="singleTask" android:taskAffinity="" android:excludeFromRecents="true"> </activity>
当你从这个 activity 当中启动其他 activity时要十分小心,因为它不是应用一部分,也不会显示在最近的应用中,当有新的数据需要显示时必须要重新启动这个 activity. 最好的办法是从这个activity中启动的其他activity都要加载在它们自己的栈中。这样做的时候要多留意这个新的栈与之前离开的栈的互动。这同前面提到的邮件类型的通知很相似,使用前面代码中的 makeMessageIntentStack()
方法,完成类似下面的点击事件:
/** * Perform a switch to the app. A new activity stack is started, replacing * whatever is currently running, and this activity is finished. */ void switchToApp() { // We will launch the app showing what the user picked. In this simple // example, it is just what the notification gave us. CharSequence from = getIntent().getCharSequenceExtra(IncomingMessageView.KEY_FROM); CharSequence msg = getIntent().getCharSequenceExtra(IncomingMessageView.KEY_MESSAGE); // Build the new activity stack, launch it, and finish this UI. Intent[] stack = IncomingMessage.makeMessageIntentStack(this, from, msg); startActivities(stack); finish(); }
Managing your Notifications
NotificationManager
类是用来管理所有通知的系统服务,必须通过 getSystemService()
方法来获得它的引用,示例:
String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
当需要发送状态栏通知时,将Notification
对象传给 NotificationManager
的 notify(int, Notification)
方法。这个方法的第一个参数是通知的 ID,第二个参数是要发送的 Notification
实例。ID是你程序中通知的身份识别,如果你需要更新状态栏通知或者(假如你的应用程序管理者各种各样的通知)当用户通过定义在通知中的 intent 返回你的应用时,需要选择合适的 action.
如果想在用户点击通知后将它从状态栏清除掉,给Notification
添加"FLAG_AUTO_CANCEL"标志即可。你也可以通过传入通知的 ID 给 cancel(int)
方法手动清除通知,或者通过 cancelAll()
来清除应用的所有通知.
Creating a Notification
Notification
中定义了通知显示在状态来和通知栏窗口上的具体信息,还包括其他的提示设置,比如声音和闪光灯。
一个状态栏通知必须要有下面的内容:
- 显示在状态栏上的图标
- 标题和信息,除非你自定义通知内容
PendingIntent
, 用户选择这个通知时会处理
状态栏通知的一些可选设置:
- 一个显示在状态栏的提示信息(注:by汉尼拔萝卜,在高版本上设置这个信息会失效,因为android默认将这个功能给关掉了)
- 声音
- 震动
- 闪光灯
最基本的通知需要使用 Notification(int, CharSequence, long)
构造器和 setLatestEventInfo(Context, CharSequence, CharSequence, PendingIntent)
方法。他们可以定义一个通知所有必备的设置,下面的代码创建了一个最基础的通知:
int icon = R.drawable.notification_icon; // icon from resources CharSequence tickerText = "Hello"; // ticker-text long when = System.currentTimeMillis(); // notification time Context context = getApplicationContext(); // application Context CharSequence contentTitle = "My notification"; // message title CharSequence contentText = "Hello World!"; // message text Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); // the next two lines initialize the Notification, using the configurations above Notification notification = new Notification(icon, tickerText, when); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
Updating the notification
当你的应用里持续有事件发生时,你可以更新状态栏通知中的信息。举个例子,当上一条短信还没来得及阅读时又收到新的短信,短信应用会更新已有的通知来显示一共收到了几条短信。这种情况下,更新通知比新建一个通知要好很多,可以避免通知栏的杂乱。
因为每一个通知都有一个独一无二的 ID,可以重新调用 setLatestEventInfo()
方法来给通知设置新的信息,然后使用 notify()
方法再发送一遍这个通知。
几乎可以修改一个通知对象的所有属性,在调用 setLatestEventInfo()
方法修改通知时,总是要传入新的 contentTitle 和 contentText 值,然后调用 notify()
方法来更新通知(如果你自定义了通知的布局,那么更新标题和内容将会没有任何意义)。
Adding a sound
你可以给通知设置一个默认的声音(用户设置的)或者在程序中指定一个声音.
要使用默认的声音给通知的 defaults 属性增加"DEFAULT_SOUND"值:
notification.defaults |= Notification.DEFAULT_SOUND;
想要给通知设置自己的声音,将 Uri 附给通知的 sound 属性。下面的代码展示如何使用设备 SD 卡中的音频文件:
notification.sound = Uri.parse("file:///sdcard/notification/ringer.mp3");
接下来的示例展示如何使用 MediaStore
的 ContentProvider
:
notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
这个示例中,音频文件的ID是已知的并且被附到 Uri
后面。如果不知到确切的 ID值,必须通过 ContentResolver
去查询所有存储在 MediaStore
中的音频文件。怎样使用 ContentResolver
可以在 Content Providers 中查看。
如果你需要一直循环播放声音直到用户点击通知或者取消通知,给通知的 flags 属性添加 FLAG_INSISTENT
值。
Note: 如果 defaults 属性中有 DEFAULT_SOUND
这个值,那么默认的声音将会覆盖 sound 属性中设置的声音。
Adding vibration
你可以通过默认的震动提示用户,或者在应用中自定义一种震动形式.
使用默认震动,给通知的 defaults 属性添加 DEFAULT_VIBRATE
值:
notification.defaults |= Notification.DEFAULT_VIBRATE;
要实现自定义的震动,将一个 long 型数组传给 vibrate 属性:
long[] vibrate = {0,100,200,300}; notification.vibrate = vibrate;
长整型数组定义了震动的时长,第一个数是第一次震动需要等待的时间(从发送通知开始计算),第二个数是第一次震动的时长,第三个数是第二次震动的时长,依次类推。这个数组的长度是任意的,但是无法设置循环震动。
Note: 如果 defaults 属性中包含了 DEFAULT_VIBRATE
值,那么默认的震动形式将会覆盖自定义的震动形式。
Adding flashing lights
使用闪光灯来提示用户,可以使用默认的闪光灯,也可以在程序中自定义闪光灯的颜色和形式.
要使用默认的闪光灯设置,需要给 defaults 属性设置添加一个 DEFAULT_LIGHTS
值:
notification.defaults |= Notification.DEFAULT_LIGHTS;
想要实现自己的闪光灯颜色和形式,需要给通知的 ledARGB 属性设置一个值(表示颜色),ledOffMS属性的值表示LED熄灭的时间,ledOnMS属性表示LED亮起的时间,同样需要给 flags 属性添加 FLAG_SHOW_LIGHTS
标志:
notification.ledARGB = 0xff00ff00; notification.ledOnMS = 300; notification.ledOffMS = 1000; notification.flags |= Notification.FLAG_SHOW_LIGHTS;
上面代码示例中,绿色的闪光灯将会亮起0.3秒,熄灭1秒,循环往复。LED并不不能支持所有的颜色,而且不同的设备会支持不同的颜色,所以考虑到兼容所有的硬件,绿色是最常用的通知颜色.
More features
还可以使用 Notification
的属性和标志增加更多的功能,一些常用的如下:
FLAG_AUTO_CANCEL
标志- 给通知的 flags 属性增加这个标志后,用户点击通知后,这个通知就会自动取消掉.
FLAG_INSISTENT
flag- 给 flags 属性添加这个标志后,通知音频将会不断重复播放,直到用户对这个通知作出响应.
FLAG_ONGOING_EVENT
flag- 给 flags 属性添加这个标志可以将这个 notification 归类到“正在运行”下。这表示应用仍在运行—也就是说它的进程仍在后台运行,即使界面没有显示(比如音乐或者通话)。
FLAG_NO_CLEAR
flag- 给通知的 flags 属性添加这个标志意味点击"Clear notifications"时不会清除这一个 notification,如果你的通知是正在进行的,通常也要加上这个标志.
number
field- by汉尼拔萝卜:这一个原文中的描述有误,因为版本更新的原因,现在的 number 显示在通知的内容当中,并且数字是任意的。比如我们的短信应用在连续收到信息时,如果用户一直不去查看,那么在通知中将会显示出一共有多少未读短信,这就是这个属性的作用。
iconLevel
field- 这个值表示的是通知使用的
LevelListDrawable
的等级。你可以通过切换你定义在LevelListDrawable中的图片,来让你通知的图标动起来。阅读LevelListDrawable
获取更多信息.
查看 Notification
来去找到更多你可以使用的功能.
Creating a Custom Notification Layout
显示在通知栏上的通知默认拥有一个标题和内容。他们的值是 setLatestEventInfo()
方法的 contentTitle 参数和 contentText 参数。然而你可以使用 RemoteViews
来自定义通知的显示内容。Figure3 显示的是一个自定义的通知,虽然看起来跟默认的通知很相似,但是它的布局是我们通过 XML 文件创建的.
想要自定义通知,首先要初始化一个 RemoteViews
对象来加载布局文件,然后将这个对象设给通知的 contentView 属性.
实践是检验真理的唯一标准:
- 首先要创建一个布局文件,举个例子,请看下面的布局文件:
custom_notification.xml
:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_marginRight="10dp" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/image" style="@style/NotificationTitle" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/image" android:layout_below="@id/title" style="@style/NotificationText" /> </RelativeLayout>
注意到两个
TextView
都包含了样式
属性. 在自定义的布局中使用样式属性是非常有必要的,因为通知的背景颜色可能在各种机器上会不一样。从android版本2.3开始,系统为通知使用的文字定义了一个样式。因此,在android版本高于2.3时,你应该使用这个样式来确保通知中的文字不会被北京颜色衬不见了。举个例子,在低于2.3版本上要使用标准的文字颜色,应该参照
res/values/styles.xml
中的样式:<?xml version="1.0" encoding="utf-8"?> <resources> <style name="NotificationText"> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="NotificationTitle"> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textStyle">bold</item> </style> <!-- If you want a slightly different color for some text, consider using ?android:attr/textColorSecondary --> </resources>
在高于2.3的版本上要使用系统默认的颜色,参考下面的样式:
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" /> <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" /> </resources>
现在,在android版本高于2.3的设备上,自定义通知中的文字颜色将会和系统默认通知中的文字颜色一样。这非常有用,因为后面的android版本将北京颜色变为深色,沿用系统的样式确保文字的颜色变为了亮色;而且即使北京改变为其他颜色,你的文字也会作出合适的变化。
- 接下来使用 RemoveViews 来定义图像和文字,然后将 RemoveViews 对象传给通知的 contentView 属性,像下面这样写:
RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification_layout); contentView.setImageViewResource(R.id.image, R.drawable.notification_image); contentView.setTextViewText(R.id.title, "Custom notification"); contentView.setTextViewText(R.id.text, "This is a custom layout"); notification.contentView = contentView;
如上面代码所示,将程序的包名和布局文件的 ID 传给 RemoteViews 的构造器。然后通过
setImageViewResource()
方法和setTextViewText()
方法来设置图像和文字,将你要设置的View 的 ID 和对应的值一起传给这些方法。最后将 RemoteViews 对象传给通知的 contentView 属性。 - 在自定义通知时不需要使用
setLatestEventInfo()
方法,所以必须给通知添加 contentIntent 属性,如下所示:Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.contentIntent = contentIntent;
- 这个通知就可以被发出啦:
mNotificationManager.notify(CUSTOM_VIEW_ID, notification);
RemoteViews
类中有一些方法可以方便我们添加 Chronometer
或者是 ProgressBar
到通知的布局中。需要了解更多关于自定义通知布局的信息,请参考 RemoteViews
类.
Caution: 在自定义通知的布局时,必须要确认你的布局在各种分辨率的设备上都能合适的显示出来。这条建议适合在android中创建的所有布局文件,因为布局资源是非常有限的,因此不要让你的布局文件太复杂,并且要在各种不同的机器上测试.