功能介绍

通知栏是位于手机顶层并可以展开收缩的通知列表,常用于交互事件的通知提醒,如:

1. 短信息、及时消息的提醒,如接收短信、QQ消息、微信聊天信息等;

2. 手机客户端APP推送的信息,如新闻信息、版本更新信息、广告信息等;

3. 显示正在进行的事物,如音乐播放器当前播放的歌曲、版本更新的下载进度提示灯;

4. .....

学习链接:

《Android设计指南非官方简体中文版》对Notification的介绍:http://adchs.github.io/patterns/notifications.html

相关知识点结构一览

21 android 通知栏 安卓手机通知栏_Notification

Notification的使用,必然离不开和Activity、BroadcastReceiver、Service组件间的配合,如:

后台Service的运行,实时发送BroadcastReceiver,并在receiver中展示Notification,通过Intent实现用户的意图操作(后台程序更新、后台音乐播放等)。

样式概述

普通视图:高度64dp(拓展布局形成的大视图在展开之前也为普通视图!)

21 android 通知栏 安卓手机通知栏_ico_02

相关实现

简单实现步骤

1. Notification.Builder的builder(Context)方法构建Notification对象;

2. 设置Notification对象的相关属性,如contentTitle(标题)、contentMessage(内容信息)、icon(图标)等;

3. 获取NoticationManager,并发送通知。

相关类

NotificationManager

NotificationManager nManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);

通知栏通知的管理类,是一个系统的Service,必须通过上述方法获取,负责通知的发送、清除等管理工作,常用方法介绍如下:

发送通知:notify(int id, Notification notification)

注意:id参数:通知的唯一标识符,如果通知栏存在相同id,则更新已有的通知栏通知,如果没有,则创建新的通知。

清除指定通知:cancel(int id)

清除所有通知:cancelAll()

Notification.Build

构建Notification的类,常见方法如下:

Builder.setContentIntent(PendingIntent intent

设置通知被点击时的意图事件

Builder.setDefaults(int defaults)

设置发送通知时的提醒效果,可选参数如下:

Notification.DEFAULT_VIBRATE    默认振动提醒,需要添加android.permission.VIBRATE权限

Notification.DEFAULT_SOUND      默认声音提醒

Notification.DEFAULT_LIGHTS      默认三色灯提醒

Notification.DEFAULT_ALL             默认使用以上全部三种提醒

Builder.setOngoing(boolean ongoing)

官方解释:

Set whether this is an "ongoing" notification. Ongoing notifications cannot be dismissed by the user, so your application or service must take care of canceling them. They are typically used to indicate a background task that the user is actively engaged with (e.g., playing music) or is pending in some way and therefore occupying the device (e.g., a file download, sync operation, active network connection).设置是否为一个正在进行的通知。一个正在进行的通知不能被用户清除掉,所以必须在应用中或者服务中清除。常用于表示一个后台任务,如播放音乐等。

setProgress(int max, int progress, boolean indeterminate)

设置带进度条的通知,比如用于任务下载之类的情形,注意:此方法只能在4.0及4.0以后的版本中使用,4.0以下的版本只能通过自定义布局实现。

PendingIntent

PendingIntent可以看做是对Intent的封装,顾名思义,表示即将发生的意图,延迟执行的Intent。

使用范围:

Notification.Builder.setContentIntent(pendingIntent),表示通知被用户点击时的处理事件;

其他事件还有:

Notification.Builder.setDeleteIntent(pendingIntent),表示通知被用户清除时的处理事件;

Notification.Builder.setFullScreenIntent(pendingIntent),表示紧急状态下的全屏事件(如来电事件),也就是说通知来的时候,跳过在通知区域点击通知这一步,直接执行fullScreenIntent代表的事件。;

创建对象:

PendingIntent pendingIntent = PendingIntent.getXXX(context, requestCode, intent, flags)

21 android 通知栏 安卓手机通知栏_android_03

XXX方法:

可以看出,通知被点击时响应方式有多种,如Activity、Broadcast、Service等,可以根据不同需要去分别处理。

flags参数(官方解释一目了然):

PendingIntent.FLAG_CANCEL_CURRENT  

f the described PendingIntent already exists, the current one is canceled before generating a new one.

如果所描述的PendingIntent已经存在,则取消前者,创建新的PendingIntent,这种设置有利于保持数据始终为最新状态,常用语即时通讯场景中,

PendingIntent.FLAG_NO_CREATE

if the described PendingIntent does not already exist, then simply return null instead of creating it.

如果所描述的PendingIntent不存在,直接返回null

PendingIntent.FLAG_ONE_SHOT

this PendingIntent can only be used once.

pendingIntent只能使用一次,并且只有在使用完才能取消。

PendingIntent.FLAG_UPDATE_CURRENT 

if the described PendingIntent already exists, then keep it but its replace its extra data with what is in this new Intent.

如果所描述的PendingIntent已经存在,则直接更新该PendingIntent数据,即Extra Data。

RequestCode参数:

如何判断所描述的PendingIntent是否已经存在呢,及如何保证PendingIntent的唯一性呢?通过测试可以看出,如果将requestCode值写成固定值,如0,然后该表Notification的ID,实现发送展示多条通知的效果(设定每条通知的PendingIntent值不相同,如跳转到不同的页面),通过改变Flags值,会出现各种各样的问题,如Flags为update_current或者cancel_current时,所有通知的点击事件均为最后一次发送的通知的点击事件,当Flags值为one_shot时,所有通知的点击事件均为第一次发送的通知的点击事件!

原来,requestCode参数可以作为PendingIntent事件的唯一性的标识符!使用UUID.randomUUID().hashCode()来确保每条通知的点击事件的唯一性!

Notification

通知信息类,包含了通知的相关属性

注意:

1. icon属性必须设置,否则Notification将会不予显示!只设置Notification.icon的话,状态栏和通知栏都显示这个Icon。

notification.icon = R.drawable.ic_launcher;

2. 在显示状态栏Ticker滚动文本部分时,部分自定义系统会自动裁剪状态栏Ticker的图标,如MIUI系统,为防止出现异常,最好添加多个尺寸的Icon图片。

正常显示如下:

21 android 通知栏 安卓手机通知栏_android_04

当Icon过大时:

状态栏滚动文本的图标会被裁剪,如下图所示:

21 android 通知栏 安卓手机通知栏_ico_05

3. 设置提醒标识符Flags

notification.defaults= Notification.DEFAULT_SOUND | Notification.FLAG_AUTO_CANCEL;通过设置Flags,达到闪灯、振动、声音等消息提醒功能,auto_cancel属性控制通知栏消息被用户点击后,自动清除的功能!

常见问题处理及使用注意事项

1. 发送通知后,通知栏不显示通知的问题:

如果不设置Notification.icon属性,则通知不予显示,查看官方解释如下:

The resource id of a drawable to use as the icon in the status bar. This is required; notifications with an invalid icon resource will not be shown.

2. Notification的ID问题

使用NotificationManager.notify(int id, Notification notification)发送通知时,记得针对使用情形,设置通知ID参数。

3. 使用RemoteViews自定义通知栏布局时,适配问题

当使用RemoteViews自定义通知栏布局时,为了达到适配效果(不同手机的通知栏背景颜色可能不一样,如黑色、白色等),可以将布局文件的根容器布局背景色设置

为透明色或者使用默认颜色(即不设置background属性),TextView等子控件的字体等颜色也使用默认颜色(即不设置TextColor或者background等属性)。

另外,如果含有ProgressBar控件,调用RemoteViews.setProgressBar(int viewId, int max, int progress, boolean indeterminate)方法时,最后一个参数设置为false,

否则progressBar控件显示不正常!

附:GitHub开源案例

https://github.com/wenmingvs/NotifyUtil

附:使用情景之消息推送

功能介绍:

使用第三方推送平台,如JPush极光推送、个推、百度云推送等,可以向客户端推送通知、自定义消息、富媒体等消息类型,三者消息类型区别如下:

通知:推送文本内容直接展示在用户的通知栏中。

自定义消息:推送自定义的消息内容透传给应用处理。

富媒体:推送预先编辑好的图文并茂的HTML页面内容。

如JPush推送平台的个人控制台页面如下:

21 android 通知栏 安卓手机通知栏_21 android 通知栏_06

说明:

自定义消息类型较为常用,可以满足依据推送内容,客户端做出不同处理,如跳转至不同页面。

1. 可以指定推送对象,如广播、客户端平台、设备标签、设备别名;

2. 可选设置中添加的附加字段,JPush平台会自动按照Json数据格式拼接,发送给客户端,按照指定官方Key值获取。

实战代码展示

package com.toutouunion.thirdparty.jpush;

import java.util.List;
import java.util.UUID;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import cn.jpush.android.api.JPushInterface;

import com.alibaba.fastjson.JSON;
import com.toutouunion.R;
import com.toutouunion.entity.JPushContent;
import com.toutouunion.ui.HomeActivity;
import com.toutouunion.ui.commodity.FundMarketActivity;
import com.toutouunion.ui.commodity.HotInvestActivity;
import com.toutouunion.ui.commodity.InvestmentHotActivity;
import com.toutouunion.ui.person.ActivityDetailActivity;
import com.toutouunion.ui.product.ProductDetailActivity;
import com.toutouunion.ui.welcome.WelcomeActivity;

/**
 * 自定义推送消息接收器
 * @author XianFeng
 * @created_time 2015年7月14日 下午1:49:32
 * @usage 推送消息接收器
 *
 */
public class JPushReceiver extends BroadcastReceiver {
	
	private static final String TAG = "JPush Message";
	
	private final String CUSTOM_MESSAGE_RECEIVER_ACTION = "com.feng.toutoutunion.custom.message.opened";
	
	//定义一个静态全局变量,用于主页面启动时是否执行通知的点击事件
	public static Bundle dataBundle;

	@Override
	public void onReceive(Context context, Intent intent) {
		
		//自定义消息:默认不会展示在通知栏
		if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
			Log.i(TAG, "自定义消息:\n" + 
					intent.getExtras().getString(JPushInterface.EXTRA_MESSAGE) +
					"\n" + 
					intent.getExtras().getString(JPushInterface.EXTRA_EXTRA));
			
			showNotificationMessage(context, intent.getExtras());
		} 
		
		//通知:展示在通知栏,在这里可以做一些统计之类的工作
		else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
			Log.i(TAG, "通知:" + intent.getExtras().getString(JPushInterface.EXTRA_ALERT));
		} 
		
		//平台默认通知点击处理:用户点击通知栏的推送通知
		else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
			Log.i(TAG, "用户点击平台推送的通知类型!");
			
		}
		
		//自定义消息发送的通知点击处理:用户点击通知栏的推送通知
		else if (CUSTOM_MESSAGE_RECEIVER_ACTION.equals(intent.getAction())) {
			Log.i(TAG, "用户点击平台推送的自定义消息类型!");
			//应用启动时,应该清除应用所有通知
			((NotificationManager)(context.getSystemService(Service.NOTIFICATION_SERVICE))).cancelAll();
			
			dataBundle = intent.getExtras();
			
			if (isApplicationRunning(context)) {
				onNotificationClick(context);
			}else {
				Intent homeIntent = new Intent(context, WelcomeActivity.class);
				homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				context.startActivity(homeIntent);
			}
			
		}
	}
	
	/**
	 * 判断应用当前是否运行中,包括后台运行
	 * @param context
	 * @return
	 */
	private boolean isApplicationRunning(Context context) {
		ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		List<RunningTaskInfo> taskList = am.getRunningTasks(Integer.MAX_VALUE);
		for (RunningTaskInfo runningTaskInfo : taskList) {
			if (runningTaskInfo.topActivity.getPackageName().equals(context.getPackageName())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 发送通知
	 * @param context
	 * @param bundle
	 */
	private void showNotificationMessage(Context context, Bundle bundle) {
		NotificationManager nManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 
		
		//通知点击时,发送广播,注意设置注册广播的action动作
		Intent intent = new Intent();
		intent.putExtra(JPushInterface.EXTRA_EXTRA, bundle.getString(JPushInterface.EXTRA_EXTRA));
		intent.setAction(CUSTOM_MESSAGE_RECEIVER_ACTION);
		//使用UUID保证requestCode唯一性,避免多个通知的PendingIntent事件冲突
		PendingIntent pendingIntent = PendingIntent.getBroadcast(context, UUID.randomUUID().hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
		
		Notification.Builder builder = new Notification.Builder(context)
			.setContentIntent(pendingIntent)
			.setTicker(context.getString(R.string.app_name))
			.setContentTitle(context.getString(R.string.app_name))
			.setContentText(bundle.getString(JPushInterface.EXTRA_MESSAGE))
			.setDefaults(Notification.DEFAULT_SOUND)
			.setAutoCancel(true);
		Notification notification = builder.build();
		//通知的Icon属性必须设置,否则通知将不会显示
		notification.icon = R.drawable.ic_launcher;
		
		//发送通知,注意通知的ID属性按需设置为不同的值,保证能够实现多条通知的显示,同时,统一类型的通知只显示一条,实时更新即可, 如同一个人的聊天消息
		nManager.notify(Integer.valueOf(JSON.parseObject(bundle.getString(JPushInterface.EXTRA_EXTRA), JPushContent.class).getJumpType()), notification);
	}
	
	/****
	 * 通知点击事件处理,定义成公共静态方法,提供给应用首页调用
	 * 		点击通知,当应用尚未启动即关闭状态时,启动后在首页可以调用此方法,执行通知点击事件
	 * @param context
	 * @param bundle
	 */
	public static void onNotificationClick(Context context){
		//用于判断应用启动,在首页判断,应用是否是点击通知栏通知进入的
		if (dataBundle == null) {
			return;
		}
		
		String contentStr = dataBundle.getString(JPushInterface.EXTRA_EXTRA);
		JPushContent contentObject = JSON.parseObject(contentStr, JPushContent.class);
		
		Intent targetIntent = new Intent();
		targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
		
		//产品详情页面
		if(contentObject.getJumpType().contains("06")){
			targetIntent.setClass(context, ProductDetailActivity.class);
			targetIntent.putExtra("fundCode", contentObject.getJumpUrl());
		}
		//投资热点列表页面
		else if(contentObject.getJumpType().contains("09")){
			targetIntent.setClass(context, InvestmentHotActivity.class);
		}
		//基金产品专题页面
		else if(contentObject.getJumpType().contains("05")){
			targetIntent.setClass(context, HotInvestActivity.class);
			targetIntent.putExtra("investHotId", contentObject.getJumpUrl());
		}
		//产品超市
		else if(contentObject.getJumpType().contains("08")){
			targetIntent.setClass(context, FundMarketActivity.class);
		}
		
		//活动详情页面
		else if(contentObject.getJumpType().contains("03")){
			targetIntent.setClass(context, ActivityDetailActivity.class);
			targetIntent.putExtra("link", contentObject.getJumpUrl());
		}
		//投什么页面
		else if(contentObject.getJumpType().contains("07")){
			targetIntent.setClass(context, HomeActivity.class);
			targetIntent.putExtra("index", HomeActivity.HOME_TAB_PRODUCT_INDEX);
		}
		//投什么页面
		else if(contentObject.getJumpType().contains("00")){
			targetIntent.setClass(context, HomeActivity.class);
			targetIntent.putExtra("index", HomeActivity.HOME_TAB_PRODUCT_INDEX);
		}
		
	    context.startActivity(targetIntent);
	    dataBundle = null;
	}	
}

重要说明:

1. 巧用PendingIntent的getBroadcast方法实现通知的点击的动态事件处理,因为通知栏显示之后,应用的状态可以发生很多改变,如页面切换,应用启动和关闭等,如果使用getActivity只能跳转至固定的Activity页面,无法达到动态判断效果!

2. 应用的状态有前台运行、后台运行以及没有运行的状态,简化为运行状态和关闭状态; 

运行状态下,通过Intent的Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP标记,可以达到跳转目标Activity的效果,如果当前已经处于目标Activity界面,显示当前界面即可!

关闭状态下,使用Intent.FLAG_ACTIVITY_NEW_TASK标记,启动应用至首页界面,然后在首页界面中调用onNotificationClick方法,利用全局静态变量dataBundle是否为null判断应用的启动是否是通过点击通知栏通知引起的,注意,dataBundle的赋值是在接收器的onReceive方法中的相应动作中赋值的,同时,在事件处理完将dataBundle重新赋值为null。

AndroidManifest.xml注册相应广播接收器:

<receiver
            android:name="com.toutouunion.thirdparty.jpush.JPushReceiver"
            android:enabled="true" >
            <intent-filter>
                <action android:name="cn.jpush.android.intent.REGISTRATION" />
                <!-- Required  用户注册SDK的intent -->
                <action android:name="cn.jpush.android.intent.UNREGISTRATION" />
                <!-- Required  用户注销SDK的intent  -->
                <action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" />
                <!-- Required  用户接收SDK自定义消息的intent -->
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED" />
                <!-- Required  用户接收SDK通知栏通知的intent -->
                <action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" />
                <!-- Required  用户打开通知栏默认通知的intent -->
                <action android:name="cn.jpush.android.intent.ACTION_RICHPUSH_CALLBACK" />
                <!-- Optional 用户接受Rich Push Javascript 回调函数的intent -->
                <action android:name="cn.jpush.android.intent.CONNECTION" />
                <!-- 接收网络变化 连接/断开 since 1.6.3 -->
                
                <action android:name="com.feng.toutoutunion.custom.message.opened"/>
                <!-- 开发人员自定义的Intent动作,用于点击自定义消息类型的通知事件 -->
                
                <category android:name="com.toutouunion" />
            </intent-filter>
        </receiver>

说明:包含第三方平台JPush的Action动作,以及开发人员自定义的Action动作!

参考链接:

附加Demo下载:

21 android 通知栏 安卓手机通知栏_android_07