一、通知(Notification)
通知是可以在应用常规UI外部向用户显示的消息。通常使用NotificationCompat.Builder对象构建UI信息和操作,NotificationCompat.Builder.build()返回具体的Notification对象,最后通过调用NotificationManager.notify()将Notification对象传递给系统。
Notification必须包含以下内容:小图标(smallIcon),标题(ContentTitle),详细文本(ContentText)。
通知操作:当用户点击时触发的操作。一般会在应用打开Activity。在Notification内部,操作由PendingIntent定义,后者包含在应用中启动activity的Intent,通过setContentIntent()来添加PendingIntent。
通知优先级:优先级用于提醒设备UI如何显示通知。通过NotificationCompat.Builder,setPriority()并传入一个NotificationCompat优先级常量。共五个,从PRIORITY_MIN(-2)到PRIORITY_MAX(2);默认为PRIORITY_DEFAULT(0);
创建简单通知:
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!");
...
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId用于之后更新notification
mNotificationManager.notify(mId, mBuilder.build());
扩展布局:在4.1之前不可用。
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("多文本可扩展标题")
.setContentText("多文本可扩展内容")
// 多文本,可扩展
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(msg))
.addAction (R.drawable.ic_stat_dismiss,
getString(R.string.dismiss), piDismiss)
.addAction (R.drawable.ic_stat_snooze,
getString(R.string.snooze), piSnooze);
...
兼容性处理:如,扩展通知仅在Android4.1及更高版本可用等。
为了确保兼容性,需要使用NorificationCompat及其子类创建通知。此外请遵循以下流程:
1.为用户提供通知的全部功能,无论何种版本的Android系统。因此要验证是否可以从应用的Activity中获得所有功能。要实现这个要求,可能要添加新的Activity;例如,若要使用addAction()提供停止和启动媒体播放的控件,应先在Activity中实现此控件。
2.确保用户均可通过点击通知启动Activity来获得该Activity中的功能。为此,要创建PendingIntent。调用setContentIntent()以将PendingIntent添加到通知。
3.将要使用的扩展通知功能添加到通知。添加的任何功能必须在用户点击通知时启动的activity中可用。
二、通知管理
1.更新通知
同一类型的事件多次发生时,要避免重新发送多次的通知。使用notify()所用的通知ID来更新或创建通知。
mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 设置通知id,以便更新
int notifyID = 1;
mNotifyBuilder = new NotificationCompat.Builder(this)
.setContentTitle("New Message")
.setContentText("You've received new messages.")
.setSmallIcon(R.drawable.ic_notify_status)
numMessages = 0;
// 启动轮询处理数据,并通知用户
...
mNotifyBuilder.setContentText(currentText)
.setNumber(++numMessages); // 设置该类型通知有多少个
// 只要id一致,则通知就会更新而不是新建
mNotificationManager.notify(
notifyID,
mNotifyBuilder.build());
...
2.删除通知
用户单独或通过使用“全部清除”清除了该通知(如果通知可以清除);
用户点击通知,并且创建通知时调用了setAutoCancel();
针对特定的通知ID调用了cancel()。此方法还会删除当前通知;
调用了cancelAll()方法,该方法删除之前所发出的所有通知。
三、启动Activity时保留导航
从通知启动Activity时,必须保留用户的预期导航体验。点击“返回”应使用户将应用的正常工作流返回到主屏幕,点击“最新动态”则应将Activity显示为单独的任务。要保留导航体验,应该在全新任务中启动Activity。如何设置PendingIntent以获得全新任务取决于启动的Activity的性质。一般有两种情况:
1.常规Activity
要启动的Activity是应用的正常工作流的一部分。这种情况下,设置PendingIntent以启动全新任务并为PendingIntent提供返回栈,这将重现应用的正常“返回”行为。例如正在别的应用时,点击了邮件的通知,点击返回,则会依次转到收件箱和主屏幕,而不是之前的那个应用。
1)在清单文件中定义应用的Activity层次结构;
a.添加对Android4.0.3及更低版本的支持。通过添加<meta-data>元素作为<activity>的子项来指定正在启动的Activity的父项(父Activity)。
b.添加对Android4.1及更高版本的支持,将android:parentActivityName属性添加到正在启动的Activity的<activity>元素中。
示例如下:
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ResultActivity"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
2)根据可启动Activity的Intent创建返回栈:
a.创建Intent以启动Activity;
b.通过TaskStackBuilder.create()创建堆栈生成器;
c.通过调用addParentStack()将返回栈添加到堆栈生成器。对于在清单文件中所定义层次结构内的每个Activity,返回栈均可包含可启动Activity的Intent对象。此方法还会添加一些可在全新任务中启动堆栈的标志。(注:尽管addParentStack()的参数是对已启动Activity的引用,但是方法调用不会添加可启动Activity的Intent,而是留待下一步进行处理)
d.如果调用addNextIntent(),添加可从通知中启动Activity的Intent。将在第一步中创建的Intent作为addNextIntent的参数传递。
e.如需,通过调用TaskStackBuilder.editIntentAt()向堆栈中的Intent对象添加参数。有时,需要确保目标Activity在用户使用“返回”导航回它时会显示有意义的数据。
f.调用getPendingIntent()获得此返回栈的PendingIntent。使用此PendingItent作为setContentIntent()的参数。
...
Intent resultIntent = new Intent(this, ResultActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// 添加返回栈
stackBuilder.addParentStack(ResultActivity.class);
// 将intent添加到栈顶
stackBuilder.addNextIntent(resultIntent);
// 获取包含全部返回栈的PendingIntent
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
...
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(id, builder.build());
注意:此处的示例在模拟器上可能展现不出效果。
2.特殊Activity
仅当从通知启动时,用户才会看到此Activity。可以视Activity为通知的扩展。这种情况将PendingIntent设置为在全新的任务中启动。但由于启动的Activity不是应用Activity流程的一部分,因此无需创建返回栈。点击返回会将用户带到主屏幕。
不必在清单文件中定义Activity层次结构,也不必调用addParentStack()来构建返回栈。可以使用清单文件设置Activity任务选项,并通过调用getActivity()创建PendingIntent:
1)在清单中添加以下属性:
android:name="activityclass",全限定类名;
android:taskAffinity="",与在代码中设置的FLAG_ACTIVITY_NEW_TASK标志相结合,确保Activity不会进入应用的默认任务。任何具有应用默认关联的现有任务均不受影响。
android:excludeFromRecents="true",将新任务从“最新动态”中排除,这样用户就不会再无意中导航回它。
示例:
<activity
android:name=".ResultActivity"
...
android:launchMode="singleTask"
android:taskAffinity=""
android:excludeFromRecents="true">
</activity>
2)构建并发出通知
a.创建可启动Activity的Intent;
b.通过使用FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TASK标志调用setFlags(),将Activity设置为在新的空任务中启动;
c.为Intent设置所需的任何其他选项;
d.调用getActivity()从Intent中创建PendingIntent。使用该PendingIntent作为setContentIntent的参数。
示例:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// 为activity创建intent
Intent notifyIntent = new Intent(this, ResultActivity.class);
// 设置activity启动一个新的空栈
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
// 创建PendingIntent
PendingIntent notifyPendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 把PendingIntent放到builder中
builder.setContentIntent(notifyPendingIntent);
// Notifications 发送给 NotificationManager
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 通过builder构建一个匿名Notification对象并传递给NotificationManager
mNotificationManager.notify(id, builder.build());
四、在通知中显示进度
通知中可以显示进度条。如果可以估计操作时间以及完成进度,使用可指定进度的进度条。否则,使用无限循环进度条。
要在Android4.0及更高版本的平台上使用进度条,要调用setProgress()。早期版本,需要创建包含ProgressBar视图的自定义通知布局。
1.显示持续时间固定的进度指示器
通过setProgress(max, progress, false)将进度栏添加到通知,然后发出通知。操作结束,progress等于max。常见方式是,max为100,progress作为百分比值递增。
操作完成后可以保留进度栏,也可删除。但是都要更新通知文本以显示操作已完成。要删除进度栏,调用setProgress(0, 0, false)。
...
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("Picture Download")
.setContentText("Download in progress")
.setSmallIcon(R.drawable.ic_notification);
// 在后台线程进行冗长操作
new Thread(
new Runnable() {
@Override
public void run() {
int incr;
// 20次冗长操作
for (incr = 0; incr <= 100; incr+=5) {
// 设置进度指示器最大值,完成值,确定状态
mBuilder.setProgress(100, incr, false);
// 第一次时,显示进度栏
mNotifyManager.notify(0, mBuilder.build());
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
Log.d(TAG, "sleep failure");
}
}
// 当循环完成,更新notification
mBuilder.setContentText("Download complete")
// 移除进度栏
.setProgress(0,0,false);
mNotifyManager.notify(ID, mBuilder.build());
}
}
// 启动线程
).start();
2.显示持续Activity指示器
使用setProgress(0, 0, true)添加到通知(忽略前两个参数),然后发出通知。setProgress(0, 0, false)可以删除指示器,不删除的话会一直显示。
五、自定义通知布局
使用RemoteViews对象自定义通知布局。自定义布局的可用高度取决于通知视图。普通视图布局限制为64dp,扩展视图布局限制为256dp。
首先实例化RemoteViews来扩充XML布局文件,调用setContent()方法设置详细内容,注意要使用RemoteViews中的方法设置视图子项的值:
1.在单独文件中为通知创建XML布局。
2.避免为RemoteViews设置背景Drawable,背景色可能使文本难以阅读。
这里按照文档描述的很简洁,以后出一个RemoteViews的详细版本。
六、其他功能
1.通知元数据(NotificationCompat.Builder)
1)当设备处于“优先”模式时,setCategory()会告知系统如何处理应用通知(如,通知代表电话呼入、消息、闹铃);
2)如果优先级设为PRIORITY_MAX或PRIORITY_HIGH,通知会有声音或振动,则setPriority()会将其显示在小型浮动窗口中;
3)addPerson()允许向通知添加人员名单。
2.浮动通知
5.0之后,设备处于活动状态时,即屏幕已打开,可以显示为浮动通知(在屏幕顶部出现notification,不需要拉开就能看见)。也可以提供一些操作按钮。
触发条件包括:用户的Activity处于全屏模式中(应使用fullScreenIntent),或者通知具有较高的优先级并使用铃声或振动。
fullScreenIntent要使用bulder.setFullScreenIntent(pendingIntent, true);// 官网上说有些系统会选择直接启动该Activity,有些则会显示为浮动通知。测三个真机都直接启动。
3.锁定屏幕通知
5.0之后,通知可以显示在锁定屏幕上。可以用此功能提示媒体播放控件。用户在“设置”中可选择通知是否显示在锁定屏幕上。
1)调用setVisibility()指定是否可见:
a.VISIBILITY_PUBLIC:显示通知的完整内容;
b.VISIBILITY_SECRET:不在锁定屏幕上显示;
c.VISIBILITY_PRIVATE:显示通知图标和内容等基本信息,隐藏内容。
这里是指在锁屏状态可以看见通知,但是不好说,国内定制机太多,像华为荣耀就不显示,魅族会显示,但是上面几个可见性不起作用,魅族全部显示。
2)锁屏状态控制媒体播放
Android5.0(API21),锁屏不再基于RemoteControlClient显示媒体控件。而是用Notification.MediaStyle与addAction()方法结合。后者将操作转换为可点击的图标。
注意:该模板和addAction()方法未包含在支持库中,因此只有5.0及以上版本才能支持。且要将可见性设置为VISIBILITY_PUBLIC。
下面给出代码,测试的时候魅族可以显示,华为荣耀7可以在下拉的通知栏里显示,但是不显示在锁屏屏幕上。
Notification notification = new Notification.Builder(context)
// 设置可见性
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat_player)
// 添加媒体控制按钮调用媒体service中的intent。
.addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
.addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
// 应用媒体style模板
.setStyle(new Notification.MediaStyle()
.setShowActionsInCompactView(1 /* #1: pause button */)
.setMediaSession(mMediaSession.getSessionToken())
.setContentTitle("Wonderful music")
.setContentText("My Awesome Band")
.setLargeIcon(albumArtBitmap)
.build();