Android Notifications 百发百中之第一发

2017-08-21  本文已影响0人  SmileWei
Google.gif

Notification 是 Android 系统中一个特别特别重要的机制,它可以让你在不打开 app 的情况下就可以便捷的查看消息、新闻、通知等等。

我个人认为 Android 比 iOS 好用的一个很重要的地方就是通知,嗯,一定是这样的。

notification06.png

比如下图:


notification_status_bar.png

当一个app收到推送通知后,会在状态栏显示该 app 的 logo,这样当你过段时间查看手机的时候不会遗漏重要的消息。

notification1.png

Notification 主要样式

样式 描述
Normal 标准通知,显示标题和单行内容
BigText 多行文字,显示标题和多行内容
BigPicture 显示文字和图片,显示标题、内容和图片
Inbox 邮件,显示标题和多行邮件内容
Messaging 消息,显示和朋友的对话内容
Media 音乐播放器,显示常驻通知栏,可以执行各种操作
Custom 自定义,自定义 layout 的通知栏

Normal Notification

notification01.png notification02.png

图中的1是 small icon,通过下列方法来设置:

    builder.setSmallIcon(R.drawable.small_icon);

图中的2是 app name,通过下列方法来设置:

    builder.setTicker(context.getString(R.string.app_name));

图中的3是 show time,通过下列方法来设置:

    builder.setWhen(System.currentTimeMillis());

图中的4是 content title,通过下列方法来设置:

    builder.setContentTitle("标题");

图中的5是 content text,通过下列方法来设置:

    builder.setContentText("内容");

图中的6是 large icon,通过下列方法来设置:

    builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.large_icon));
<center>

</center>

下面是创建一个标准的通知栏的代码:

        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setClass(context, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        PendingIntent pendingIntent = PendingIntent.getActivity(context
                        , (int) SystemClock.uptimeMillis()
                        , intent
                        , PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        
        //设置通知栏大图标,上图中右边的大图
        builder.setLargeIcon(BitmapFactory.decodeResource(
                        context.getResources(), R.drawable.large_icon))
                // 设置状态栏和通知栏小图标
                .setSmallIcon(R.drawable.small_icon)
                // 设置通知栏应用名称
                .setTicker(context.getString(R.string.app_name))
                // 设置通知栏显示时间
                .setWhen(System.currentTimeMillis())
                // 设置通知栏标题
                .setContentTitle("标题")
                // 设置通知栏内容
                .setContentText("内容")
                // 设置通知栏点击后是否清除,设置为true,当点击此通知栏后,它会自动消失
                .setAutoCancel(true)
                // 将Ongoing设为true 那么左滑右滑将不能删除通知栏  
                //.setOngoing(true);  
                // 设置通知栏点击意图
                .setContentIntent(pendingIntent);
                // 铃声、闪光、震动均系统默认
                .setDefaults(Notification.DEFAULT_ALL);
                // 设置为public后,通知栏将在锁屏界面显示
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
                // 从Android4.1开始,可以通过以下方法,设置通知栏的优先级,优先级越高的通知排的越靠前,
                // 优先级低的,不会在手机最顶部的状态栏显示图标
                // 设置优先级为PRIORITY_MAX,将会在手机顶部显示通知栏
                .setPriority(NotificationCompat.PRIORITY_MAX);
     
        NotificationManager noti = (NotificationManager)
                     context.getSystemService(Context.NOTIFICATION_SERVICE);
        noti.notify((int) System.currentTimeMillis(), builder.build());

点击消失

如果你想点击通知后,该通知自动消失,那么你就需要调用 setAutoCancel(boolean b) 这个方法,并将其设置为 true;

设置铃声、震动、闪光效果

setDefaults(Notification.DEFAULT_ALL) 表示系统系统默认的铃声、震动和闪光效果。举例:
自定义通知铃声:

锁屏显示

notification03.png

如上图所示,如果你想在锁屏情况下显示通知,则需要调用 setVisibility(int visibility),并将其设置为NotificationCompat.VISIBILITY_PUBLIC

头部显示

notification04.png

如上图所示,如果你想通知显示在手机头部,比如来电时的通知,那么你就需要调用 setPriority(int pri),并将其设置为NotificationCompat.PRIORITY_MAX

禁止删除

如果你想让你的通知栏常驻,用户无法滑动删除,也不能通过手机的清除键删除,类似于网易云音乐等 app 的通知栏,那么你可以设置 setOngoing 方法,也设为 true,这样,通知栏只能通过代码调用 cancel 方法才能消失;

通知点击事件

使用 build.setContentIntent(PendingIntent intent) 设置点击事件。
先看官方的代码吧:

        /**
         * Supply a {@link PendingIntent} to send when the notification is clicked.
         * If you do not supply an intent, you can now add PendingIntents to individual
         * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
         * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}.  Be sure to
         * read {@link Notification#contentIntent Notification.contentIntent} for
         * how to correctly use this.
         */
        public Builder setContentIntent(PendingIntent intent) {
            mContentIntent = intent;
            return this;
        }

从代码注释可以看出,当通知被点击的时候,系统会发送一个 PendingIntent。
从字面上看,PendingIntent:等待的,未决定的Intent。

要获取 PendingIntent 对象,有以下静态方法:

分别对应着 Intent 的3个行为,跳转到一个或几个 activity 组件、打开一个广播组件和打开一个服务组件。
PendingIntent 是一种特殊的 Intent。主要的区别在于 Intent 的执行立刻的,而 PendingIntent 的执行不是立刻的。
PendingIntent 执行的操作实质上是参数传进来的 Intent 的操作,但是使用 PendingIntent 的目的在于它所包含的 Intent 的操作的执行是需要满足某些条件的。

一般情况下,点击通知都是打开特定的 Activity,你可以直接使用 getActivity 或 getActivities,也可以使用 getBroadcast 或 getService,然后在广播或服务中做一些启动 Activity 的操作。

所有的 app 点击通知栏启动 Activity 的方式不外乎以下三种:

        Intent intent = new Intent(Intent.ACTION_MAIN);
        
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setClass(context, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        PendingIntent pendingIntent = PendingIntent.getActivity(context
                        , (int) SystemClock.uptimeMillis()
                        , intent
                        , PendingIntent.FLAG_UPDATE_CURRENT);

使用 TaskStackBuilder

        Intent intent = new Intent();
        
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                        | Intent.FLAG_ACTIVITY_SINGLE_TOP 
                        |  Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.setClass(context, ThirdActivity.class);
        
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
        stackBuilder.addParentStack(ThirdActivity.class);
        stackBuilder.addNextIntent(intent);
        
        PendingIntent pendingIntent = stackBuilder.getPendingIntent(
                        (int) SystemClock.uptimeMillis()
                        , PendingIntent.FLAG_UPDATE_CURRENT);

使用 getActivities

        Intent intent = new Intent(context, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                        | Intent.FLAG_ACTIVITY_SINGLE_TOP 
                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        Intent secondIntent = new Intent(context, SecondActivity.class);
        Intent[] intents = {intent, secondIntent};
        PendingIntent pendingIntent = PendingIntent.getActivities(context
                        , (int) SystemClock.uptimeMillis()
                        , intents
                        , PendingIntent.FLAG_UPDATE_CURRENT);
        if (Util.isAppAlive(context, BuildConfig.APPLICATION_ID) 
            && !DemoApplication.getInstance().isAllActivityFinished()) {
            Intent intent = new Intent();
            intent.setClass(context, ThirdActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                            | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
        } else {
            Intent intent = new Intent(context, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                            | Intent.FLAG_ACTIVITY_SINGLE_TOP 
                            | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            Intent secondIntent = new Intent(context, SecondActivity.class);
            Intent[] intents = {intent, secondIntent};
            startActivities(intents);
        }
获取 PendingIntent 对象的第四个参数为 flags,一般 flags 有5种选择:

通常情况下,我们会使用 FLAG_UPDATE_CURRENT 这个 flags 值来构造 PendingIntent,但是使用 FLAG_UPDATE_CURRENT 经常会发生点击通知栏后没有任何响应,时灵时不灵。

notification05.png

原来使用 FLAG_UPDATE_CURRENT 这个参数后,系统不会重新创建新的 PendingIntent,这样一来,如果你传递的 Intent 的 extra 参数没有变化的话,那么系统就会认为你没有发送新的 PendingIntent,这样就不会重新响应你的点击事件。一般情况下,为了能够区分每次的 PendingIntent 不一样,我们常常会在构造 Intent 的时候,设置不同的 Action 或 Extra 值,这样一来,即便是使用 FLAG_UPDATE_CURRENT 这个参数,系统也会因为传值参数的变化而去响应每次的点击事件。不过这种解决方法还是很坑爹的,大部分时候我们根本不需要传递额外的 Action 或 Extra 值,这个时候我们只需要把

PendingIntent.getActivity(context, requestCode, intent, flags)

这个方法中的第二个参数(requestCode)设置成唯一的标识就可以了,可以直接使用

int requestCode = (int) SystemClock.uptimeMillis();

当然你可以直接使用 FLAG_CANCEL_CURRENT 这个 flag,每次都会创建一个新的,那么上面的问题就不存在了。

notification07.jpg

注:判断 APP 是否被杀死和所有的 Activity 是否都被杀死,详见这篇 BlogDemo

通知栏添加 Action

demo03.gif

通过添加 Action,用户可以不用打开 app 就可以直接对收到的信息就行操作,比如回复短信、设置短信已读和删除短信等等。
是不是很方便?是不是比 iOS 厉害多了?

Android 提供了一个叫做 RemoteInput 的类,给通知栏的回复操作提供强有力的支持,用户点击回复后,直接在通知栏显示输入框,输入内容后点击发送即可。那怎么使用呢?直接看代码吧。

      public final static String REPLY = "reply";
      public final static String KEY_TEXT_REPLY = "key_text_reply";
      RemoteInput input = new RemoteInput.Builder(KEY_TEXT_REPLY).setLabel("请输入内容").build();

      Intent reply = new Intent();
      reply.setAction(REPLY);
      reply.setClass(context, NotificationReceiver.class);
      PendingIntent pendingReply = PendingIntent.getBroadcast(context
                      , (int) SystemClock.uptimeMillis()
                      , reply
                      , PendingIntent.FLAG_UPDATE_CURRENT);

      NotificationCompat.Action action = 
                      new NotificationCompat.Action.Builder(0, "回复", pendingReply)
                      .addRemoteInput(input)
                      .setAllowGeneratedReplies(true)
                      .build();

      builder.addAction(action);

这里使用了发送广播组件,你可以在接收到的广播中直接获取输入框输入的内容。

     case REPLY:{
          //获取输入框输入的内容
          Bundle input = RemoteInput.getResultsFromIntent(intent);
          if (input == null)
              return;
          //Do something
          String content = input.getCharSequence(KEY_TEXT_REPLY)
          Toast.makeText(context, content, Toast.LENGTH_SHORT).show();
          break;
    }

添加设置已读和删除操作

     Intent maskAsRead = new Intent();
     maskAsRead.setAction(MAKE_AS_READ);
     maskAsRead.setClass(context, NotificationReceiver.class);
     PendingIntent pendingMaskAsRead = PendingIntent.getBroadcast(context, 
                     (int) SystemClock.uptimeMillis()
                     , maskAsRead
                     , PendingIntent.FLAG_UPDATE_CURRENT);

     Intent delete = new Intent();
     delete.setAction(DELETE);
     delete.setClass(context, NotificationReceiver.class);
     PendingIntent pendingDelete = PendingIntent.getBroadcast(context, 
                     (int) SystemClock.uptimeMillis()
                     , delete
                     , PendingIntent.FLAG_UPDATE_CURRENT);
     builder.addAction(0, "mask as read", pendingMaskAsRead);
     builder.addAction(0, "delete", pendingDelete);

同样的你可以在 Receiver 中获取你做了什么操作,下一步该做什么等等。

启动 Notification

启动 Notification 的方法很简单,如下所示:

    NotificationManager notificationManager 
            = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(ID, builder.build());

对于同一个 ID,后启动的 Notification 会替换先启动的 Notification ,所以:

对于类似于微信这样的聊天通知,你需要为每一个给你发消息的用户设置一个独一无二的 ID,比如可以根据用户 ID来生成;
对于类似于网易新闻这样的新闻通知,你需要为每个通知设置不同的独一无二的 ID,比如使用 (int) SystemClock.uptimeMillis();
如果你的通知栏只需要显示一个的话,只需要用同一个 ID 就可以;
对于需要代码清除的 Notification,你需要为 ID 设置一个固定的独一无二的值,比如短信通知,你点了删除操作,删除短信后你就需要清除对应的 Notification

清除 Notification

两种方式:

根据 ID 清除特定的 Notification:

notificationManager.cancel(id);

清除该 app 所有的 Notification:

notificationManager.cancelAll();

详细的代码请参见GitHub

敬请期待 Android Notifications 百发百中之第二发

上一篇 下一篇

猜你喜欢

热点阅读