13.4.3 状态栏通知

状态栏通知(Status Bar Notifications)的使用相比弹出式通知Toast而言要复杂得多。首先,状态栏通知的交互界面样式更为复杂,具有更为丰富的图标和文字内容;其次,状态栏通知需要绑定与之相关的事件,使用户可以与通知项进行交互,对通知所提示的内容进行进一步的处理;再次,状态栏通知的生命周期更为复杂,它不像Toast通知那样定时自动消失,而是有多种多样的控制模式,开发时需要根据场景的不同选择不同的生命周期模式,并维护状态信息的更新和交互事件的修改。

状态栏通知的交互界面,由两部分构成,一部分是状态栏界面,包括在状态栏上显示的图标和文字内容;另一部分是列表栏界面,指的是下拉通知列表中的通知项界面。使用状态栏通知android.app.Notification对象,可以设置这两部分交互界面:


//初始化状态栏上会显示的图标和文字

Notification notification=new Notification(

R.drawable.status_bar_notification,

getString(R.string.status_bar_notification),

System.currentTimeMillis());

//设置下拉通知栏会显示的文字信息

notification.setLatestEventInfo(context,

getString(R.string.notification_title),

getString(R.string.notification_content),

createPendingIntent());


随着Android版本的不断升级,状态栏通知的样式也变得更为复杂,于是在Android 3.0中,提供了状态栏通知的构造类android.app.Notification.Builder,通过Notification.Builder可以更清晰地构造状态栏通知对象:


//用新的Builder类来构建同样样式的状态栏通知

Notification notification=new Notification.Builder(context)

.setTicker(getString(R.string.status_bar_notification))

.setSamllIcon(R.drawable.status_bar_notification)

.setContentTitle(getString(R.string.notification_title))

.setContentText(getString(R.string.notification_content)

.setContentIntent(createPendingIntent())

.getNotifcation();


除此之外,状态栏通知也支持自定义样式。状态栏通知的构造者和展示者隶属于不同的应用(这一点与Toast通知有很大区别,Toast通知的显示是在构造应用的主线程中完成的),因此,与App Widght的界面构造类似,需要使用RemoteViews来定制交互界面,实现构造和展示的分离:


//使用RemoteViews构造自定义的状态栏通知的列表栏样式

//通知列表栏样式为左边文字,右边图标

RemoteViews contentView=new RemoteViews(getPackageName(),R.layout.my_

notification);

contentView.setImageViewResource(R.id.icon, R.drawable.my_notification_icon);

contentView.setTextViewText(R.id.text, getString(R.string.my_notification);

notification.contentView=contentView;


在实际开发中,除非有特别的界面样式需求,尽量不要自定义状态栏通知的样式。因为不同的Android ROM可能会定制不一样的通知栏样式,自定义状态栏通知很可能在一些设备上显示效果不佳,并且与其他通知项格格不入。如果默认的通知栏样式实在无法满足需求,必须使用自定义状态栏通知样式,那么最好使用系统定义的背景和前景样式,保证显示效果与系统风格一致:


自定义的my_notification样式

<!—背景使用系统定义的样式—>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="horizontal"

android:background="@android:drawable/status_bar_item_background"

…>

<!—左边的文本使用系统定义的样式—>

<TextView android:id="@+id/text"

android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"

…/>

<ImageView android:id="@+id/icon"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

…/>

</LinearLayout>


在Android中,开发者除了可以为状态栏通知定制提示文本和图标内容,还可以为状态栏通知设置闪烁(Flashing LED)、震动(Vibrate)和提示音(Alert Sound)等效果,进一步增强状态栏通知的提醒效果:


//开启和设置闪烁效果

//开发者可以设定闪烁灯的颜色以及闪烁灯开闭的时长

notification.defaults|=Notification.DEFAULT_LIGHTS;

notification.ledARGB=0xffff0000;//红光闪烁

notification.ledOnMS=400;//开启400毫秒

notification.ledOffMS=800;//然后关闭800毫秒

notification.flags|=Notification.FLAG_SHOW_LIGHTS;

//开启和设置震动效果

//开发者可以控制震动的节奏和时长

notification.defaults|=Notification.DEFAULT_VIBRATE;

//设置震动效果,每两个值为一对,第一个表示开启时间(毫秒),另一个表示持续时长,此例中,一

共会震动两次,在0毫秒震动200毫秒,在300毫秒时再继续震动500毫秒

long[]vibrate={0,200,300,500};

notification.vibrate=vibrate;

//开启和设置提示音的效果

//开发者可以通过Uri或者文件地址设置提示音

notification.defaults|=Notification.DEFAULT_SOUND;

notification.sound=Uri.parse(

"file:///sdcard/my_ring.mp3");//将my_ring设为提示音


与Toast通知不同,状态栏通知不仅具有交互界面,还可以为其绑定相应的交互事件。当用户在通知的下拉列表中点击相应的通知项,就会触发通知所绑定的事件,向目标组件(包括界面组件、服务组件和触发器组件)发送Intent请求。

同样,状态栏通知的事件也是通过PendingIntent来构造、并绑定到通知项中异步触发的:


//createPendingIntent函数,构造通知所需的PendingIntent对象

//构造一个打开邮件组件的新邮件通知

Intent intent=new Intent();

intent.setAction(NEW_EMAIL_ACTION);

intent.setExtra(NEW_EMAIL_ID, newEmailId);

PendingIntent pendingIntent=PendingIntent.getActivity(

context,

0,

intent,

0);


发送状态栏通知,需要使用android.app.NotifcationManager对象。NotificationManager是一个系统服务的客户端对象,它会将请求转发到系统服务NotificationManagerService进行处理。使用NotificationManager.notify可以发送请求:


//获得NotificationManager对象

NotificationManager manager=(NotificationManager)getSystemService(Context.

NOTIFICATION_SERVICE);

//发送notification

manager.notify(MY_ID, notification);


其中,MY_ID是此次状态栏通知的标识[1],通知管理服务会根据这个标识,对所有通知项进行过滤,保证同样标识的通知项只有一个,开发者需要为每个通知项添加全局唯一的标识,例如,使用包名和一些描述信息作为通知项标识:


My.Package.Name.new_mail_notification


通过这样唯一性的标识信息,开发者可以更新或取消通知,比如:


//取消通知

manager.cancel(MY_ID);


在实际开发中,系统有时候无法准确获知用户是否看到了通知,是否该取消通知。为了对状态栏通知的生命周期管理更为智能,Android为Notification添加了一些标志位,使其生命周期更可控。

比如,开发者可以使用Notifcation.FLAG_AUTO_CANCEL标志位,将通知设定为自动取消。一旦用户点击了该通知,通知项会自动消失,无需调用NotificationManager.cancel函数来取消:


//设置为自动取消

notification.flags|=Notification.FLAG_AUTO_CANCEL


再比如,如果状态栏通知非常重要(比如来电通知),需要始终在状态栏保持一个稳定的入口,就可以为通知添加Notification.FLAG_ONGOING_EVENT标志位。这样,在通知列表中就无法通过清除操作取消该通知了(如果没有On-Going的标志位,用户可以在通知列表中清理该通知项),直至程序主动调用NotificationManager.cancel:


//设置为On-Going

notification.flags|=Notification.FLAG_ONGOING_EVENT


注意 本节介绍了Android的全局通知机制,包括通知机制的实现架构、使用方式以及界面构建、事件绑定等内容。

在实际开发中,全局通知是应用与用户进行沟通的最重要方式之一。使用Toast通知,可以实现非阻塞的反馈效果;而使用状态栏通知,则可以实现后台的事件提醒。对于每一个开发者而言,都需要对其有所了解。

[1]在Android 2.0以上的系统里,新增了一个字符串Tag作为辅助标识,默认实现下是null,开发者可以使用Tag和Id共同来标识一次通知。