<activity
android:name="com.example.notification.SpecialActivity"
android:excludeFromrecents="true"
android:label="@string/title_activity_special"
android:launchMode="singleTask"
android:taskAffinity="" >
源码下载

Android N 前瞻 - Notification
Google 日前已经发布了 Android N 的开发者预览版,这比预期要提前了两个月。虽然国内的 Rom 通常会慢半拍,但是作为开发者提前了解一下 Android N 中的一些新特性还是很有必要的。
在 Android N 中,通知迎来了一些不小的更新,开发者有了更多的方法来控制通知的外观和行为。所以现在来看一下 Android N 中的 Notification 有了哪些新特性:
Google 提供了新的默认通知模板(更加的简洁和清爽)。
能够直接在 Notification 中回复消息和更新任务事项了。
现在相关的Notification 能够被收纳进一个通知组,而不是再堆满用户的通知栏了。
新的通知模板
Android N 内置了很多新的通知模板来供我们使用,新的通知模板更加清晰和简洁。简单的是,在 Android N 中系统会默认直接使用这些新的模板,所以我们不需要额外的修改我们已有的代码。目前在 Android N Preview 版本中的通知栏看起来就是这样:

Android N 通知示例.png
我个人感觉新的通知模板相比以前更能让用户专注于通知的内容上了,将小小的通知界面分割的更加简洁了。在不久的以后,这就将称为 Android 的默认通知样式了。
注意我们可以使用 setColor() 方法来给我们的通知自定义一种颜色,让通知和应用保持风格的一致。
使用 setColor() 方法能够改变通知栏中的应用图标和应用名颜色,也包括下面提到的通知内操作的文字颜色。
直接回复
Android N 允许用户直接在通知中回复消息,用户可以在专注于自己当前任务的同时来回复消息了,这个内置的回复操作是通过按钮的形式存在于通知中的。

Android N 通知回复界面(图片来自官网).png
当用户点击按钮时,系统就会将回复内容关联到指定的 Intent 并发送该 Intent 到对应的 App。
添加内嵌回复操作
为通知添加内置回复操作的步骤:
1.创建 RemoteInput.Builder 的实例,注意这里传入的 KEY_TEXT_REPLY 参数,之后我们从 Intent 中取到用户输入的数据也需要用到它。
// Key for the string that''s delivered in the action''s intent.
private static final String KEY_TEXT_REPLY = "key_text_reply";
String replyLabel = getResources().getString(R.string.reply_label);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel(replyLabel)
.build();
2.使用 addRemoteInput() 方法来将上面创建的 RemoteInput 和通知的 action 关联起来:
// Create the reply action and add the remote input.
Notification.Action action = new Notification.Action.Builder(
R.drawable.ic_reply_icon, getString(R.string.label), replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
3.为目标 Notification 设置 action 并发出 Notification:
// Build the notification and add the action.
Notification newMessageNotification = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle(getString(R.string.title))
.setContentText(getString(R.string.content))
.addAction(action))
.build();
// Issue the notification.
NotificationManager notificationManager = NotificationManager.from(mContext);
notificationManager.notify(notificationId, newMessageNotification);
系统就会在用户查看通知时提示用户输入回复消息了。

用户输入回复(图片来自官网).png
得到内部动作发送的数据
当用户在通知内部进行了回复动作之后,应用就需要获取到用户输入的回复。
1.调用 getResultsFromIntent() 并将 Notification 发出的 intent 作为参数,就会返回一个包含了回复信息的 Bundle。
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
2.使用 result key 来查询该 Bundle,可以单独创建一个方法来完成这个工作:
// Obtain the intent that started this activity by calling
// Activity.getIntent() and pass it into this method to get the associated string.
private CharSequence getMessageText(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if(remoteInput != null) {
return remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
return null;
}
3.使用和之前的通知相同的 Notification ID 来创建并触发另外一个新的通知。这条通知的作用是用来提示用户消息已经成功回复了。当创建这条新的通知时,要使用通过 onReceive() 方法传入的 context 对象:
// Build a new notification, which informs the user that the system
// handled their interaction with the previous notification.
Notification repliedNotification =
new Notification.Builder(context)
.setSmallIcon(R.drawable.ic_message)
.setContentText(getString(R.string.replied))
.build();
// Issue the new notification.
NotificationManager notificationManager = NotificationManager.from(context);
notificationManager.notify(notificationId, repliedNotification);
对于交互属性强的 App,在处理收到的消息时包含额外的上下文信息通会常非常有用。比如,聊天类的 App 可能会想用户通过通知栏回复了消息后来更新用户的消息列表。当用户通过 RemoteInput 方法回复了消息,开发者可以使用 setRemoteInputHistory() 来更新消息列表。
设置内部回复的样式
理所当然的 Google 也提供了自定义内部回复栏样式的方法,虽然目前只是能够修改颜色 - -。具体就是我们可以在创建 notification builder 时使用 setColor() 方法来改变回复栏的颜色。
捆绑通知
Android N 提供了一种新的显示多个通知的方式:捆绑通知。在收到通知的时候,如果有多个属于同一应用的通知,那么就会把这些通知绑定为一个群组。可以使用 Builder.setGroup() 方法来捆绑。
捆绑之后的通知就具有了一种层次关系,可以避免因为大量的通知挤满用户的通知栏而惹恼用户了。层级结构的最顶层是父级通知,显示通知群组的摘要信息。用户点击后可以展开通知组,显示其中所有的自通知。
必须设置通知组中的一个通知来作为群组的摘要,该通知就会作为整个通知组的顶层展示,否则你的通知就不会被显示为一个通知组。代码也很简单,只需要在创建通知的时候,同时调用
setGroup(String) 和 setGroupSummary(true) 就可以了。
使用捆绑通知的时机
子通知可以操作,并且每个子通知具有特定的操作。
子通知中包含用户想要查看的更多信息。
好的捆绑通知示例包括:显示消息列表的短信应用,显示电子邮件列表的电子邮件应用。
以上大概就是 Android N 中对 Notification 的更新,也稍微了解了怎么来具体的实现。除了通知,Google 在新版本中还为 Android 平台加入了很多非常棒的特性,想要了解更详细的内容,可以查看 Android N 官方说明。
参考资料
Android N Notification 官方文档

android notification
现在需要做一个notification的功能,要求12小时没有点击应用,就会给你发送一个notification。点击后进入应用,又重新计时,保证关机的情况下,一直在计时,有做过的给点提示啊
Android Notification 从 notify 到添加 view 的处理流程
创建 Notification 是很容易的,android8.0 以后开始加入通知渠道 NotificationChannel,然后在构造 NotificationCompat.Builder 的时候,指定要发送的渠道,最后调用 NotificationManager.notify (id,notification) 发送通知。
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()));
}
---------------
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
...
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
-------------------
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
获取了 INotificationManager 远程接口对象,把 Notification 给加入到队列中,
INotificationManager 的远程对象是 NotificationManagerService 里的 mservice,NotificationManagerService 是系统服务。在开机启动的时候,Systemserver 里和其他系统服务一起启动,最后注册到 ServiceManager 里。
publishBinderService (Context.NOTIFICATION_SERVICE, mService); // 把 service 注册到 Servicemanager 里
----
private final IBinder mService = new INotificationManager.Stub(){
...
}
接着前面的,找到了 enqueueNotificationWithTag
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, userId);
}
------------
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId) {
.... 处理 notification 包含的信息,通知渠道,优先级。。
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
把 notification 转换为 NotificationRecord,并 post 给 EnqueueNotificationRunnable,
EnqueueNotificationRunnable 的 run 方法里。
@Override
public void run() {
synchronized (mNotificationLock) {
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
...
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
------------
@Override
public void run() {
synchronized (mNotificationLock) {
try {
NotificationRecord r = null;
int N = mEnqueuedNotifications.size();
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
...
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGroupHelper.onNotificationPosted(
n, hasAutoGroupSummaryLocked(n));
}
});
}
}
...
buzzBeepBlinkLocked(r);
}
------------
void buzzBeepBlinkLocked(NotificationRecord record) {
处理 notification 的 声音 震动和灯光闪烁
}
------------
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
发送给状态栏
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update);
}
});
}
↓
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
INotificationListener 是另一个远程接口对象
protected class NotificationListenerWrapper extends INotificationListener.Stub
------------
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
}
case MSG_ON_NOTIFICATION_POSTED: {
SomeArgs args = (SomeArgs) msg.obj;
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
RankingMap rankingMap = (RankingMap) args.arg2;
args.recycle();
onNotificationPosted(sbn, rankingMap);
------------
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
很意外,发现方法是空的,那调这么多有什么用? 研究了一下发现,onNotificationPosted 的这个方法属于的是
public abstract class NotificationListenerService extends Service {
1
NotificationListenerService 是个抽象方法,那很自然调用的时候会调用它的子类,
然后
public class NotificationListenerWithPlugins extends NotificationListenerService
1
但是找了一圈 NotificationListenerWithPlugins ,没有 onNotificationPosted,那只有继续找它的子类了,
后来发现,在 Statusbar 里有个匿名内部类实现了 NotificationListenerService 的方法。
private final NotificationListenerWithPlugins mNotificationListener =
new NotificationListenerWithPlugins() {
...
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
mHandler.post(new Runnable() {
@Override
public void run() {
...
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
...
}
}
...
}
------------
public void addNotification(StatusBarNotification notification, RankingMap ranking)
throws InflationException {
Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);.
...
}
------------
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
inflateViews(entry, mStackScroller);
}
------------
protected void inflateViews(Entry entry, ViewGroup parent) {
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
});
}
最后 bindRow 就是去构造通知栏的通知 View,然后 updateNotification 就是去显示到状态栏。
private void updateNotification(Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
...
row.updateNotification(entry);
}
------------
public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
mNotificationInflater.inflateNotificationViews();
}
------------
public void inflateNotificationViews() {
inflateNotificationViews(FLAG_REINFLATE_ALL);
}
------------
void inflateNotificationViews(int reInflateFlags) {
...
StatusBarNotification sbn = mRow.getEntry().notification;
new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
mCallback, mRemoteViewClickHandler).execute();
}
使用了异步任务 AsyncTask 去完成布局
AsyncInflationTask
@Override
protected InflationProgress doInBackground(Void... params) {
return createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup,
mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
packageContext);
}
------------
private static InflationProgress createRemoteViews(int reInflateFlags,
Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
Context packageContext) {
InflationProgress result = new InflationProgress();
isLowPriority = isLowPriority && !isChildInGroup;
if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
result.newExpandedView = createExpandedView(builder, isLowPriority);
}
if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
result.newPublicView = builder.makePublicContentView();
}
if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification()
: builder.makeAmbientNotification();
}
result.packageContext = packageContext;
return result;
}
到了这里,都是创建各种布局
比如 createContentView
public RemoteViews createContentView() {
return createContentView(false /* increasedheight */ );
}
------------
public RemoteViews createContentView(boolean increasedHeight) {
if (mN.contentView != null && useExistingRemoteView()) {
return mN.contentView;
} else if (mStyle != null) {
final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
if (styleView != null) {
return styleView;
}
}
return applyStandardTemplate(getBaseLayoutResource());
}
这里会去判断我们是否有在 notification 里添加 style, 如果有不同的 style,比如音乐播放器那种 notification,就是自定义 style,如果没有,那就用默认的 layout。
private int getBaseLayoutResource() {
return R.layout.notification_template_material_base;
}
------------
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="base" >
<include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
<include
android:layout_width="match_parent"
android:layout_height="@dimen/notification_progress_bar_height"
android:layout_marginTop="@dimen/notification_progress_margin_top"
layout="@layout/notification_template_progress" />
</LinearLayout>
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
原来这就是我们用的 notification 的布局内容。
private RemoteViews applyStandardTemplate(int resId) {
return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this));
}
private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
return applyStandardTemplate(resId, mParams.reset().hasProgress(hasProgress)
.fillTextsFrom(this));
}
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p) {
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
bindLargeIcon(contentView);
}

android Notification 的一个简单应用(在 Notification 中嵌入一个进度条,并且这个 Notification 点击消失但不会跳转)
网上很多的例子都是直接获取 Notification 对象来设置一个通知,其实 Notification 跟 Dialog 一样,也有自己的 Builder,可以用 builder 对象来设置一个 Notification
这个例子是在 Notification 中嵌入一个进度条,并且这个 Notification 点击消失但不会跳转(跟 android 的 vcard 文件导入时弹出的 Notification 一样)
NotificationManager mNotificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context);
builder.setOngoing(true);
builder.setProgress (total, current, false);// 设置进度条,false 表示是进度条,true 表示是个走马灯
builder.setTicker (title);// 设置 title
builder.setWhen(System.currentTimeMillis());
builder.setContentTitle (content);// 设置内容
builder.setAutoCancel (true);// 点击消失
builder.setSmallIcon(R.drawable.upload);
builder.setContentIntent (PendingIntent.getActivity (context, 0, new Intent (), 0));// 这句和点击消失那句是 “Notification 点击消失但不会跳转” 的必须条件,如果只有点击消失那句,这个功能是不能实现的
Notification noti = builder.getNotification();
mNotificationManager.notify(id,noti);
希望这个例子对其他人有点用,因为我特曾为这个功能苦恼过,呵呵!
我们今天的关于android notification 的总结分析和android notificationmanager的分享已经告一段落,感谢您的关注,如果您想了解更多关于Android N 前瞻 - Notification、android notification、Android Notification 从 notify 到添加 view 的处理流程、android Notification 的一个简单应用(在 Notification 中嵌入一个进度条,并且这个 Notification 点击消失但不会跳转)的相关信息,请在本站查询。