诗书勤乃有,不勤腹空虚. ——韩愈
小弟初学安卓,该文算是小弟的学习过程,课后笔记与一些自己的思考,希望在自己的自学路上留下印记,也许有一些自己想的不对的地方,希望各位前辈斧正。
一、Android中的Notification
Notification翻译过来就是通知的意思,实际上我们在使用安卓手机实经常见,如果你没做特别设置,当你手机收到短信或是受到APP的推送,都会先显示在手机顶上方的通知栏上几秒钟,然后我们下拉通知栏,就可以看到我们接收到的通知。
二、创建Notification
Notification可以在Activity中创建也可以在Service中创建,由于弃用了以前的直接实例化Notification方法(小弟现在用的是API 24),我们现在要使用NotificationCompat.Builder来创建通知了,创建一个简单的通知只需要简短的几行代码:
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("通知")
.setContentText("我是通知")
.setWhen(System.currentTimeMillis());
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, mBuilder.build());
setSmallIcon,setContentTitle,setContentText,分别是设置Notification的图标,通知标题,通知内容,与上图中相对应的就是:安卓机器人图标,”通知“,”我是通知“,他们是谷歌在官方文档中要求我们必须要设置的,这些方法的返回值都是NotificationCompat.Builder对象,所以我们能像上面代码那样实例化。为什么说是必须设置的呢,我测试了以下两种情况:
①不调用setSmallIcon:通知根本不会显示,即使你设置了标题和内容
②不调用setContentTitle,setContentText:不会显示标题和内容,但是通知会出现在标题栏。(若要自定义通知样式,则无需设置了)
可知setSmallIcon是一定要有的。而setWhen呢,是让我们设置Notification产生的时间,不设置它通知然会产生,但是这个时间会影响到我们的通知在通知栏中的排序。
/**
* Set the time that the event occurred. Notifications in the panel are
* sorted by this time.
*/
public Builder setWhen(long when) {
mNotification.when = when;
return this;
}
最后,我们获取系统的NOTIFICATION_SERVICE服务来获得NotificationManager 实例,并使用 它的notify方法来将通知发给系统,notify及其重载的源码如下:
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
tag和id都是给我们创建的Notification用来表示身份的标志,最后一个参数就是我们创建的Notification,我们在一块代码中创建Notification时直接使用了mBuilder的build方法返回了一个notification的匿名实例,但是当我们并不是像这样简单的展示Notification时应该再声明一个Notification对象来引用它(Notification notification=mBuilder.build();)。
第一个方法传参没有tag,id的作用就是标识我们的Notification,如果我们在某处再调用notify来发Notification,若第一个Notification我们还没划掉(可见),且第二个通知与第一个Notification的id相同的话,就能起到更新通知的作用,若不同,则会作为一条新Notification与第一条一起存在于通知栏。而第二个方法参数中多了String类型的tag,作用其实与id相同,不过这种通知必须要tag和id都相同时才会更新Notification,单单tag或id相同都不行,另外若标志相同,Notification可不同,显示其他的Notification,tag可为null,如何使用就因人而异了。
我们一般在点击通知时能跳转到发送通知的应用指引我们打开的页面,上面的代码实现后还没有这个功能,我们只需要使用NotificationCompat.Buidler的setContentIntent并传入一个点击后发送的PendingIntent即可(并不仅限于跳转页面)。关于PendingIntent的官方文档。
三、自定义Notification
如果Notification只是用来显示普通的文本信息,那么上面简单的Notification可能就足够了,但是想想一些音乐应用,它们是不是在通知栏给我们提供了简易的可以上一曲,下一曲,暂停播放的控制台?那这样的Notification就需要我们自定义才能满足我们的个性化需求了。
之前我自己写代码时是api 22,当时是利用Notification对象的contentView或bigContentView(都是RemoteViews对象,bigContentView允许我们加宽Notification)来引用我们自己实例化的RemoteViews对象,不过就在刚才我用api 24时就发现contentView 和 bigContentView已经弃用了,源码中的注释指导我们应该使用Notification.Builder#setCustomContentView(RemoteViews)或Notification.Builder#setCustomBigContentView(RemoteViews)。使用起来也很简单,新建一个RemoteViews对象传入即可:
RemoteViews rviews = new RemoteViews(getPackageName(),R.layout.notification_layout);
mBuilder.setCustomContentView(rviews);//用哪种View看个人需求
我模拟了一个音乐播放器的通知样式,
XML文件notification_layout.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/musicName_textView"
android:layout_width="200dp"
android:layout_height="20dp"
android:gravity="center_vertical"
android:text="歌名: 套马滴汉子你威武雄壮~"/>
<TextView
android:id="@+id/musician_textView"
android:layout_width="200dp"
android:layout_height="20dp"
android:layout_below="@id/musicName_textView"
android:layout_marginTop="3dp"
android:gravity="center_vertical"
android:text="歌手: 作者"/>
<ImageButton
android:id="@+id/notification_next_imageButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="#00000000"
android:scaleType="fitCenter"
android:src="@drawable/next"/>
<ImageButton
android:id="@+id/notification_play_imageButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/notification_next_imageButton"
android:background="#00000000"
android:scaleType="fitCenter"
android:src="@drawable/play"/>
<ImageButton
android:id="@+id/notification_prev_imageButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/notification_play_imageButton"
android:background="#00000000"
android:scaleType="fitCenter"
android:src="@drawable/prev"/>
</RelativeLayout>
话说这货和Widget一样,布局里只能使用一些基本控件。
那既然在Notification中加入了按钮,总要给一个点击事件监听器吧,因为是使用的RemoteViews,和Widget一样,(如果你的按钮不仅仅只是让页面跳转的话)我们要使用PendingIntent并且以广播形式来让接收者处理,并且执行RemoteViews对象的setOnClickPendingIntent方法。
大概形式为:
Notification.Builder notificationBuilder = new Notification.Builder(this)
.setSmallIcon(R.drawable.music)
.setWhen(System.currentTimeMillis())
.setCustomBigContentView(new RemoteViews(getPackageName(),R.layout.notification_layout))
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MusicPlayActivity.class), PendingIntent.FLAG_UPDATE_CURRENT));
//定义PendingIntent
Intent playButtonIntent=new Intent(MusicPlayActivity.FROM_NOTIFICATION_ACTION);
playButtonIntent.putExtra("act","play");
mBigContentView.setOnClickPendingIntent(R.id.imageButton_nt_play,PendingIntent.getBroadcast(this,1,playButtonIntent,PendingIntent.FLAG_UPDATE_CURRENT));
Intent nextButtonIntent=new Intent(MusicPlayActivity.FROM_NOTIFICATION_ACTION);
nextButtonIntent.putExtra("act","next");
mBigContentView.setOnClickPendingIntent(R.id.imageButton_nt_next,PendingIntent.getBroadcast(this,2,nextButtonIntent,PendingIntent.FLAG_UPDATE_CURRENT));
Intent prevButtonIntent=new Intent(MusicPlayActivity.FROM_NOTIFICATION_ACTION);
prevButtonIntent.putExtra("act","prev");
上面的代码,实际只是给Notification布局中的三个按钮添加了PendingIntent,并发送广播交给广播接收器处理。需要注意的是PendingIntent的getXXX方法中的最后一个int flag参数,有四个参数供我们传(下面的描述来自优快云博客):
FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。
FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。
四、将Notification设置为前台服务
要将Notification设置为前台服务,必须要在Service中创建Notification,并调用Service的startForeground方法:
startForeground(1, mNotification);
使用这个方法发出Notification可以不用notify了,当然要更新还是要用。改为前台服务后呢,若你没有再调用stopForeground方法来停止或者内存实在不够,Notification是划不掉或销毁的。