精华内容
下载资源
问答
  • android通知

    千次阅读 2015-04-02 17:44:00
    android.app.RemoteServiceException: Bad notification posted from package Couldn't create icon: StatusBarIcon(pkg=com.risetek.nfcuser=-1 id=0xffffffff level=0 visible=true num=0 )检查Notificatio

    注意:

    android.app.RemoteServiceException: Bad notification posted from package   Couldn't create icon: StatusBarIcon(pkg=com.risetek.nfcuser=-1 id=0xffffffff level=0 visible=true num=0 )
    检查Notification的各个参数是否配置齐全了。这里需要添加通知的图标:

     notification.icon = R.drawable.ic_launcher;




    展开全文
  • Android通知栏微技巧,8.0系统中通知栏的适配

    万次阅读 多人点赞 2018-04-17 07:39:11
    大家好,今天我们继续来学习Android 8.0系统的适配。...在上一篇文章当中,我们学习了Android 8.0系统应用图标的适配,那么本篇文章,我们自然要将重点放在通知栏上面了,学习一下Android 8.0系统的通知栏适配

    转载请注明出处:https://blog.csdn.net/guolin_blog/article/details/79854070

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

    大家好,今天我们继续来学习Android 8.0系统的适配。

    之前我们已经讲到了,Android 8.0系统最主要需要进行适配的地方有两处:应用图标和通知栏。在上一篇文章当中,我们学习了Android 8.0系统应用图标的适配,还没有看过这篇文章的朋友可以先去阅读 Android应用图标微技巧,8.0系统中应用图标的适配

    那么本篇文章,我们自然要将重点放在通知栏上面了,学习一下Android 8.0系统的通知栏适配。

    其实在8.0系统之前,还有一次通知栏变动比较大的版本,就是5.0系统。关于5.0系统需要对通知栏进行适配的内容,我也整理了一篇文章,感兴趣的朋友可以去阅读 Android通知栏微技巧,那些你所没关注过的小细节

    那么下面我们就开始进入本篇文章的正题。


    为什么要进行通知栏适配?

    不得不说,通知栏真是一个让人又爱又恨的东西。

    通知栏是Android系统原创的一个功能,虽说乔布斯一直认为Android系统是彻彻底底抄袭iOS的一个产品,但是通知栏确实是Android系统原创的,反而苹果在iOS 5之后也加入了类似的通知栏功能。

    通知栏的设计确实非常巧妙,它默认情况下不占用任何空间,只有当用户需要的时候用手指在状态栏上向下滑动,通知栏的内容才会显示出来,这在智能手机发展的初期极大地解决了手机屏幕过小,内容展示区域不足的问题。

    可是随着智能手机发展的逐渐成熟,通知栏却变得越来越不讨人喜欢了。各个App都希望能抢占通知栏的空间,来尽可能地宣传和推广自己的产品。现在经常是早上一觉醒来拿起手机一看,通知栏上全是各种APP的推送,不胜其烦。

    我个人虽然是Android应用开发者,但同时也是Android手机的资深用户。我已经使用了8年的Android手机,目前我对于通知栏的这种垃圾推送是零容忍的。现在每当我安装一个新的App时,我都会先到设置里面去找一找有没有推送开关,如果有的话我会第一时间把它关掉。而如果一个App经常给我推送垃圾信息却又无法关闭时,我会直接将它的通知总开关给关掉,如果还不是什么重要的App的话,那么我可能就直接将它卸载掉了。

    为什么一个很好的通知栏功能现在却变得这么遭用户讨厌?很大一部分原因都是因为开发者没有节制地使用导致的。就好像App保活一样,直到今天还是不断有人问我该如何保活App,试想如何每个人都能保活自己的App,那么最终受害的人是谁?还不是使用Android手机的用户。大家的手机只会越来越卡,最后只想把手机丢掉,变成iPhone用户了。也是因为开发者没节制地使用,Android现在的每个版本都会不断收缩后台权限。

    回到通知栏上也是一样,每个开发者都只想着尽可能地去宣传自己的App,最后用户的手机就乱得跟鸡窝一样了。但是通知栏又还是有用处的,比如我们收到微信、短信等消息的时候,确实需要通知栏给我们提醒。因此分析下来,通知栏目前最大的问题就是,无法让用户对感兴趣和不感兴趣的消息进行区分。就比如说,我希望淘宝向我推送卖家发货和物流的相关消息,但是我不想收到那些打折促销或者是让我去买衣服的这类消息。那么就目前来说,是没有办法对这些消息做区分的,我要么同意接受所有消息,要么就屏蔽所有消息,这是当前通知栏的痛点。

    那么在Android 8.0系统中,Google也是从这个痛点开始下手的。

    8.0系统的通知栏适配

    从Android 8.0系统开始,Google引入了通知渠道这个概念。

    什么是通知渠道呢?顾名思义,就是每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动、或者是否要关闭这个渠道的通知。

    拥有了这些控制权之后,用户就再也不用害怕那些垃圾推送消息的打扰了,因为用户可以自主地选择自己关心哪些通知、不关心哪些通知。举个具体的例子,我希望可以即时收到支付宝的收款信息,因为我不想错过任何一笔收益,但是我又不想收到支付宝给我推荐的周围美食,因为我没钱只吃得起公司食堂。这种情况,支付宝就可以创建两种通知渠道,一个收支,一个推荐,而我作为用户对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我关心的通知,又不会让那些我不关心的通知来打扰我了。

    对于每个App来说,通知渠道的划分是非常需要仔细考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道。这里我们来参考一下Twitter的通知渠道划分:

    可以看到,Twitter就是根据自己的通知类型,对通知渠道进行了非常详细的划分,这样用户的自主选择性就比较高了,也就大大降低了用户不堪其垃圾通知的骚扰而将App卸载的概率。


    我一定要适配吗?

    Google这次对于8.0系统通知渠道的推广态度还是比较强硬的。

    首先,如果你升级了appcompat库,那么所有使用appcompat库来构建通知的地方全部都会进行废弃方法提示,如下所示:

    上图告诉我们,此方法已废弃,需要使用带有通知渠道的方法才行。

    当然,Google也并没有完全做绝,即使方法标为了废弃,但还是可以正常使用的。可是如果你将项目中的targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的App已经做好了8.0系统的适配工作,当然包括了通知栏的适配。这个时候如果还不使用通知渠道的话,那么你的App的通知将完全无法弹出。因此这里给大家的建议就是,一定要适配。

    好了,前面向大家介绍了这么多的背景知识,那么现在开始我们就正式进入正题,来学习一下如何进行8.0系统中通知栏的适配。

    创建通知渠道

    首先我们使用Android Studio来新建一个项目,就叫它NotificationTest吧。

    创建好项目之后,打开app/build.gradle文件检查一下,确保targetSdkVersion已经指定到了26或者更高,如下所示:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 26
        defaultConfig {
            applicationId "com.example.notificationtest"
            minSdkVersion 15
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
    }

    可以看到,这里我在创建新项目的时候默认targetSdkVersion就是26,如果你是低于26的话,说明你的Android SDK有些老了,最好还是更新一下。当然如果你懒得更新也没关系,手动把它改成26就可以了。

    接下来修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                String channelId = "chat";
                String channelName = "聊天消息";
                int importance = NotificationManager.IMPORTANCE_HIGH;
                createNotificationChannel(channelId, channelName, importance);
    
                channelId = "subscribe";
                channelName = "订阅消息";
                importance = NotificationManager.IMPORTANCE_DEFAULT;
                createNotificationChannel(channelId, channelName, importance);
            }
        }
    
    
        @TargetApi(Build.VERSION_CODES.O)
        private void createNotificationChannel(String channelId, String channelName, int importance) {
            NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
            NotificationManager notificationManager = (NotificationManager) getSystemService(
                    NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
    }

    代码不长,我来简单解释下。这里我们在MainActivity中创建了两个通知渠道,首先要确保的是当前手机的系统版本必须是Android 8.0系统或者更高,因为低版本的手机系统并没有通知渠道这个功能,不做系统版本检查的话会在低版本手机上造成崩溃。

    创建一个通知渠道的方式非常简单,这里我封装了一个createNotificationChannel()方法,里面的逻辑相信大家都看得懂。需要注意的是,创建一个通知渠道至少需要渠道ID、渠道名称以及重要等级这三个参数,其中渠道ID可以随便定义,只要保证全局唯一性就可以。渠道名称是给用户看的,需要能够表达清楚这个渠道的用途。重要等级的不同则会决定通知的不同行为,当然这里只是初始状态下的重要等级,用户可以随时手动更改某个渠道的重要等级,App是无法干预的。

    上述代码我是模拟了这样一个场景。想象一下我们正在开发一个类似于微信的App,其中App通知主要可以分为两类,一类是我和别人的聊天消息,这类消息非常重要,因此重要等级我设为了IMPORTANCE_HIGH。另一类是公众号的订阅消息,这类消息不是那么重要,因此重要等级我设为了IMPORTANCE_DEFAULT。除此之外,重要等级还可以设置为IMPORTANCE_LOW、IMPORTANCE_MIN,分别对应了更低的通知重要程度。

    现在就可以运行一下代码了,运行成功之后我们关闭App,进入到设置 -> 应用 -> 通知当中,查看NotificationTest这个App的通知界面,如下图所示:

    刚才我们创建的两个通知渠道这里已经显示出来了。可以看到,由于这两个通知渠道的重要等级不同,通知的行为也是不同的,聊天消息可以发出提示音并在屏幕上弹出通知,而订阅消息只能发出提示音。

    当然,用户还可以点击进去对该通知渠道进行任意的修改,比如降低聊天消息的重要等级,甚至是可以完全关闭该渠道的通知。

    至于创建通知渠道的这部分代码,你可以写在MainActivity中,也可以写在Application中,实际上可以写在程序的任何位置,只需要保证在通知弹出之前调用就可以了。并且创建通知渠道的代码只在第一次执行的时候才会创建,以后每次执行创建代码系统会检测到该通知渠道已经存在了,因此不会重复创建,也并不会影响任何效率。


    让通知显示出来

    触发通知的代码和之前版本基本是没有任何区别的,只是在构建通知对象的时候,需要多传入一个通知渠道ID,表示这条通知是属于哪个渠道的。

    那么下面我们就来让通知显示出来。

    首先修改activity_main.xml中的代码,如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送聊天消息"
            android:onClick="sendChatMsg"
            />
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送订阅消息"
            android:onClick="sendSubscribeMsg"
            />
    </LinearLayout>

    这里我们在布局文件中加入了两个按钮,很显然,一个是用于触发聊天消息渠道通知的,一个是用于触发订阅消息渠道通知的。

    接下来修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
    
        ...
    
        public void sendChatMsg(View view) {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            Notification notification = new NotificationCompat.Builder(this, "chat")
                    .setContentTitle("收到一条聊天消息")
                    .setContentText("今天中午吃什么?")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.icon)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                    .setAutoCancel(true)
                    .build();
            manager.notify(1, notification);
        }
    
        public void sendSubscribeMsg(View view) {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            Notification notification = new NotificationCompat.Builder(this, "subscribe")
                    .setContentTitle("收到一条订阅消息")
                    .setContentText("地铁沿线30万商铺抢购中!")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.icon)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                    .setAutoCancel(true)
                    .build();
            manager.notify(2, notification);
        }
    }

    这里我们分别在sendChatMsg()和sendSubscribeMsg()方法中触发了两条通知,创建通知的代码就不再多做解释了,和传统创建通知的方法没什么两样,只是在NotificationCompat.Builder中需要多传入一个通知渠道ID,那么这里我们分别传入了chat和subscribe这两个刚刚创建的渠道ID。

    现在重新运行一下代码,并点击发送聊天消息按钮,效果如下图所示:

    由于这是一条重要等级高的通知,因此会使用这种屏幕弹窗的方式来通知用户有消息到来。然后我们可以下拉展开通知栏,这里也能查看到通知的详细信息:

    用户可以通过快速向左或者向右滑动来关闭这条通知。

    接下来点击发送订阅消息按钮,你会发现现在屏幕上不会弹出一条通知提醒了,只会在状态栏上显示一个小小的通知图标:

    因为订阅消息通知的重要等级是默认级别,这就是默认级别通知的展示形式。当然我们还是可以下拉展开通知栏,查看通知的详细信息:

    不过上面演示的都是通知栏的传统功能,接下来我们看一看Android 8.0系统中通知栏特有的功能。

    刚才提到了,快速向左或者向右滑动可以关闭一条通知,但如果你缓慢地向左或者向右滑动,就会看到这样两个按钮:

    其中,左边那个时钟图标的按钮可以让通知延迟显示。比方说这是一条比较重要的通知,但是我暂时没时间看,也不想让它一直显示在状态栏里打扰我,我就可以让它延迟一段后时间再显示,这样我就暂时能够先将精力放在专注的事情上,等过会有时间了这条通知会再次显示出来,我不会错过任何信息。如下所示:

    而右边那个设置图标的按钮就可以用来对通知渠道进行屏蔽和配置了,用户对每一个App的每一个通知渠道都有绝对的控制权,可以根据自身的喜好来进行配置和修改。如下所示:

    比如说我觉得订阅消息老是向我推荐广告,实在是太烦了,我就可以将订阅消息的通知渠道关闭掉。这样我以后就不会再收到这个通知渠道下的任何消息,而聊天消息却不会受到影响,这就是8.0系统通知渠道最大的特色。

    另外,点击上图中的所有类别就可以进入到当前应用程序通知的完整设置界面。


    管理通知渠道

    在前面的内容中我们已经了解到,通知渠道一旦创建之后就不能再通过代码修改了。既然不能修改的话那还怎么管理呢?为此,Android赋予了开发者读取通知渠道配置的权限,如果我们的某个功能是必须按照指定要求来配置通知渠道才能使用的,那么就可以提示用户去手动更改通知渠道配置。

    只讲概念总是不容易理解,我们还是通过具体的例子来学习一下。想一想我们开发的是一个类似于微信的App,聊天消息是至关重要的,如果用户不小心将聊天消息的通知渠道给关闭了,那岂不是所有重要的信息全部都丢了?为此我们一定要保证用户打开了聊天消息的通知渠道才行。

    修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
    
        ...
    
        public void sendChatMsg(View view) {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = manager.getNotificationChannel("chat");
                if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
                    Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
                    intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
                    intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
                    startActivity(intent);
                    Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
                }
            }
    
            Notification notification = new NotificationCompat.Builder(this, "chat")
                    ...
                    .build();
            manager.notify(1, notification);
        }
    
        ...
    
    }

    这里我们对sendChatMsg()方法进行了修改,通过getNotificationChannel()方法获取到了NotificationChannel对象,然后就可以读取该通知渠道下的所有配置了。这里我们判断如果通知渠道的importance等于IMPORTANCE_NONE,就说明用户将该渠道的通知给关闭了,这时会跳转到通知的设置界面提醒用户手动打开。

    现在重新运行一下程序,效果如下图所示:

    可以看到,当我们将聊天消息的通知渠道关闭后,下次再次发送聊天消息将会直接跳转到通知设置界面,提醒用户手动将通知打开。

    除了以上管理通知渠道的方式之外,Android 8.0还赋予了我们删除通知渠道的功能,只需使用如下代码即可删除:

    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    manager.deleteNotificationChannel(channelId);

    但是这个功能非常不建议大家使用。因为Google为了防止应用程序随意地创建垃圾通知渠道,会在通知设置界面显示所有被删除的通知渠道数量,如下图所示:

    这样是非常不美观的,所以对于开发者来说最好的做法就是仔细规划好通知渠道,而不要轻易地使用删除功能。


    显示未读角标

    前面我们提到过,苹果是从iOS 5开始才引入了通知栏功能,那么在iOS 5之前,iPhone都是怎么进行消息通知的呢?使用的就是未读角标功能,效果如下所示:

    实际上Android系统之前是从未提供过这种类似于iOS的角标功能的,但是由于很多国产手机厂商都喜欢跟风iOS,因此各种国产手机ROM都纷纷推出了自己的角标功能。

    可是国产手机厂商虽然可以订制ROM,但是却没有制定API的能力,因此长期以来都没有一个标准的API来实现角标功能,很多都是要通过向系统发送广播来实现的,而各个手机厂商的广播标准又不一致,经常导致代码变得极其混杂。

    值得高兴的是,从8.0系统开始,Google制定了Android系统上的角标规范,也提供了标准的API,长期让开发者头疼的这个问题现在终于可以得到解决了。

    那么下面我们就来学习一下如何在Android系统上实现未读角标的效果。

    修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
    
        ...
    
        @TargetApi(Build.VERSION_CODES.O)
        private void createNotificationChannel(String channelId, String channelName, int importance) {
            NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
            channel.setShowBadge(true);
            NotificationManager notificationManager = (NotificationManager) getSystemService(
                    NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
    
        public void sendSubscribeMsg(View view) {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            Notification notification = new NotificationCompat.Builder(this, "subscribe")
                    ...
                    .setNumber(2)
                    .build();
            manager.notify(2, notification);
        }
    }

    可以看到,这里我们主要修改了两个地方。第一是在创建通知渠道的时候,调用了NotificationChannel的setShowBadge(true)方法,表示允许这个渠道下的通知显示角标。第二是在创建通知的时候,调用了setNumber()方法,并传入未读消息的数量。

    现在重新运行一下程序,并点击发送订阅消息按钮,然后在Launcher中找到NotificationTest这个应用程序,如下图所示:

    可以看到,在图标的右上角有个绿色的角标,说明我们编写的角标功能已经生效了。

    需要注意的是,即使我们不调用setShowBadge(true)方法,Android系统默认也是会显示角标的,但是如果你想禁用角标功能,那么记得一定要调用setShowBadge(false)方法。

    但是未读数量怎么没有显示出来呢?这个功能还需要我们对着图标进行长按才行,效果如下图所示:

    这样就能看到通知的未读数量是2了。

    可能有些朋友习惯了iOS上的那种未读角标,觉得Android上这种还要长按的方式很麻烦。这个没有办法,因为这毕竟是Android原生系统,Google没有办法像国内手机厂商那样可以肆无忌惮地模仿iOS,要不然可能会吃官司的。但是我相信国内手机厂商肯定会将这部分功能进行定制,风格应该会类似于iOS。不过这都不重要,对于我们开发者来说,最好的福音就是有了统一的API标准,不管国内手机厂商以后怎么定制ROM,都会按照这个API的标准来定制,我们只需要使用这个API来进行编程就可以了。

    好的,关于Android 8.0系统适配的上下两篇文章到这里就结束了,感谢大家阅读。

    文章中的示例源码点击 这里 下载。

    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

    20160507110203928         20161011100137978

    展开全文
  • 这个是通知栏框架(Notificaiton)的全面学习,里面把大概所有的情况都列了出来,通过一个DEMO让你了解它的大致所有使用过程。 可以通过以下博文进行配套了解(有效果图): ...
  • Android 通知栏显示与设置

    千次阅读 2019-04-12 19:42:31
    Android通知栏 Android 基础设置和发送通知在通知栏显示。 Android 版本通知适配 Android 8.0 及以上使用通知都必须设置 NotificationChannel 类才能正常在通知栏弹出通知,只需 Application 中设置一次就可以了。 ...

    Android通知栏

    Android 基础设置和发送通知在通知栏显示。

    Android 版本通知适配

    Android 8.0 及以上使用通知都必须设置 NotificationChannel 类才能正常在通知栏弹出通知,只需 Application 中设置一次就可以了。 当 NotificationChannel 设置成功后具体可以在手机系统(设置 / 通知和状态栏 / 自身应用 / 通知类别)里看到详细的设置介绍。
    Android 8.0 以下可直接创建通知然后发送,并不需要设置 NotificationChannel 类。

    设置 NotificationChannel 代码

    在继承了 Application 的类中的 onCreate() 中进行设置。

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    	// 通知渠道的id(后台推送则与后台设置的Id保持一致和创建通知时传入的ChannelId保持一致)
        String channelId = "1";
        // 用户可以看到的通知渠道的名字.
        String name = "新通知";
        // 用户可以看到的通知渠道的描述
        String description = "新通知描述";
        // 该通知的重要级别
        int importance = NotificationManager.IMPORTANCE_HIGH;
        // 创建渠道
        NotificationChannel mChannel = new NotificationChannel(channelId, name, importance);
        // 配置通知渠道的属性
        mChannel.setDescription(description);
        // 设置通知出现时的闪灯(如果 android 设备支持的话)
        mChannel.enableLights(true);
        // 设置桌面图标右上角通知提示的颜色
        mChannel.setLightColor(Color.RED);
        // 设置通知出现时的震动(如果 android 设备支持的话)
        mChannel.enableVibration(true);
        // 设置发布到此频道的通知的振动模式
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        // 是否在久按桌面图标时显示此渠道的通知
        mChannel.setShowBadge(true);
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    	if (mNotificationManager != null) {
    		// 创建单个渠道
    		mNotificationManager.createNotificationChannel(mChannel);
    		// 创建多个渠道
    		// List<NotificationChannel> list = new ArrayList<>();
        	// list.add(mChannel);
    		// mNotificationManager.createNotificationChannels(list);
     	}
    }
    
    

    设置通知栏代码

    当接收到服务器的推送或自身应用服务发出命令后,则创建一个通知并在通知栏弹出提示。

    Notification notification;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    	Notification.Builder builder = new Notification.Builder(context, channelId);
            builder.setSmallIcon(R.mipmap.ic_launcher_round)//设置通知小图标在状态栏显示(必须设置)
            		//设置通知大图标,在通知栏显示
                    .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
                    //通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
                    .setWhen(System.currentTimeMillis())
                    //设置该通知优先级
                    .setPriority(Notification.PRIORITY_HIGH)
                    //通知首次出现在通知栏,带上升动画效果的
                    .setTicker(message.getTitle())
                    //设置通知栏标题
                    .setContentTitle(message.getTitle())
                    //设置通知栏内容
                    .setContentText(message.getContent())
                    //设置通知出现时的震动具体值
                    .setVibrate(new long[0])
                    //ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
                    .setOngoing(false)
                    //设置这个标志当用户单击面板就可以让通知将自动取消
                    .setAutoCancel(true);
                    //创建PendingIntent,处理点击通知之后的逻辑
           			Intent intent = new Intent(context, MainActivity.class);
            		PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
           			builder.setContentIntent(pendingIntent);
                    notification = builder.build();
    } else {
    	NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
            mBuilder.setContentTitle(message.getTitle())//设置通知栏标题
            		//通知首次出现在通知栏,带上升动画效果的
                    .setTicker("通知来啦")
                    //通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
                    .setWhen(System.currentTimeMillis())
                    //设置该通知优先级
                    .setPriority(NotificationCompat.PRIORITY_HIGH) 
                    //设置通知栏内容
                    .setContentText(message.getContent())
                    //设置通知大图标,在通知栏显示
                    .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
                    //设置这个标志当用户单击面板就可以让通知将自动取消
                    .setAutoCancel(true)
                    //ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
                    .setOngoing(false)
                    //设置通知小图标在状态栏显示(必须设置)
                    .setSmallIcon(R.mipmap.ic_launcher_round);//设置通知小ICON
            Intent intent = new Intent(context, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
            mBuilder.setContentIntent(pendingIntent);
            notification = mBuilder.build();
    }
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (notificationManager != null) {
    	notificationManager.notify("1", notification);
    }
    
    展开全文
  • Android通知系统源码解析

    千次阅读 2018-11-28 19:31:11
    Android通知系统源码解析1. 概述2. 流程图2.1. 发送通知流程图3. 源码解析3.1. 使用通知--APP进程3.1.1. 创建通知:3.1.2. 发送(更新)通知:3.1.3. 取消通知:3.1.4. 创建通知源码解析:3.2. 管理通知--...

    1. 概述

    Android 通知系统是应用与系统UI交互的重要系统,方便应用告知用户有新的通知或正在运行的后台程序,用户可通过通知面板直接或间接与应用交互,并可以随时跳转。
    本文基于Android P的代码,只讲述发送通知和SystemUI注册流程,与取消通知流程类似不再赘述,可自行查看源码。

    2. 流程图

    2.1. 发送通知流程图

    image

    3. 源码解析

    整个通知系统主要涉及到三个进程,分别是:

    • APP进程:负责更新、创建、发送或取消通知;
    • SystemServer进程:负责管理(添加、取消、控制等)通知,类似通知的管理中心;
    • SystemUI进程:负责显示通知,并保持与用户的交互;
    3.1. 使用通知–APP进程
    3.1.1. 创建通知:
    Intent intent = new Intent();
    intent.setClass(this, MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
    PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this,
            0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    
    Notification.Builder builder = new Notification.Builder(MainActivity.this, channelId)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("Test Title")
        .setContentText("Test Content")
        .setTicker("Test Ticker")
        .setAutoCancel(true)
        .setContentIntent(pendingIntent)
        .setVisibility(Notification.VISIBILITY_PUBLIC);
    
    Notification notification = builder.build();
    
    3.1.2. 发送(更新)通知:
    NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    nm.notify(0, notification);
    
    3.1.3. 取消通知:
    NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    nm.cancel(0);
    // 或取消全部通知
    // nm.cancelAll();
    
    3.1.4. 创建通知源码解析:
    • 构建一个Notification.Builder 对象,然后初始化Notification部分字段。典型的建造者模式,将构建复杂对象的操作分解成一步步简单的操作。

      Notification.Builder

          public Builder(Context context, String channelId) {
              this(context, (Notification) null);
              mN.mChannelId = channelId;
          }
          
          public Builder(Context context, Notification toAdopt) {
              ....
              if (toAdopt == null) {
                  mN = new Notification();
                  if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
                      mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
                  }
                  mN.priority = PRIORITY_DEFAULT;
                  mN.visibility = VISIBILITY_PRIVATE;
              }
              ...
          }
      
    • 通过Notification.Builder 建造并封装好Notification对象,然后返回。发现Android 5.0 之后,构建Notification的时候,不初始化和携带contentView、bigContentView等RemoteView(处理应用自定义RemoteView);
      保留问题1: 为什么呢?什么时候去加载RemoteView?
      在下面SystemUI侧的源码分析中会讲到这点。原因我认为是,Notification需要跨进程被传输,携带过多数据会降低性能。

      Notification.java

      public Notification build() {
          // first, add any extras from the calling code
          if (mUserExtras != null) {
              mN.extras = getAllExtras();
          }
      
          mN.creationTime = System.currentTimeMillis();
      
          // lazy stuff from mContext; see comment in Builder(Context, Notification)
          Notification.addFieldsFromContext(mContext, mN);
      
          buildUnstyled();
      
          // 初始化mStyle内容,在这里可以发现,可以通过setStyle()接口为Notification设置不同的style,比如我们最常见的BigPictureStyle,以及播放器MediaStyle等等,可自行去查询相关接口
          if (mStyle != null) {
              mStyle.reduceImageSizes(mContext);
              mStyle.purgeResources();
              mStyle.validate(mContext);
              mStyle.buildStyled(mN);
          }
          mN.reduceImageSizes(mContext);
      
          // Android 5.0 之后,构建Notification的时候,不初始化和携带contentView、bigContentView等RemoteView
          if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N && (useExistingRemoteView())) {
              if (mN.contentView == null) {
                  mN.contentView = createContentView();
                  mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
                              mN.contentView.getSequenceNumber());
                  }
              if (mN.bigContentView == null) {
                  mN.bigContentView = createBigContentView();
                  if (mN.bigContentView != null) {
                      mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,mN.bigContentView.getSequenceNumber());
                  }
              }
              if (mN.headsUpContentView == null) {
                  mN.headsUpContentView = createHeadsUpContentView();
                  if (mN.headsUpContentView != null) {
                      mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,mN.headsUpContentView.getSequenceNumber());
                  }
              }
          }
      
          if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
                  mN.flags |= FLAG_SHOW_LIGHTS;
          }
      
          mN.allPendingIntents = null;
      
          return mN;
      }
      
    3.2. 管理通知–SystemServer进程
    3.2.1. 发送通知:
    • 应用调用Notification.notify()接口发送通知,最终会进入system_server进程,调用NotificationManagerService的enqueueNotificationWithTag()。

      NotificationManager.java

      public void notify(int id, Notification notification){
          notify(null, id, notification);
      }
      
      public void notify(String tag, int id, Notification notification){
          notifyAsUser(tag, id, notification, mContext.getUser());
      }
      
      /**
      * @hide
      */
      public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){
          
          INotificationManager service = getService();
          String pkg = mContext.getPackageName();
          // Fix the notification as best we can.
          Notification.addFieldsFromContext(mContext, notification);
          ...
          fixLegacySmallIcon(notification, pkg);
          // Android 5.0之后必须要设置small icon 否则抛异常
          if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
              if (notification.getSmallIcon() == null) {
                  throw new IllegalArgumentException("Invalid notification (no valid small icon): "+ notification);
              }
          }
          if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
          notification.reduceImageSizes(mContext);
      
          ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
          boolean isLowRam = am.isLowRamDevice();
          final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam,
                      mContext);
          try {
              service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                      copy, user.getIdentifier());
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
      
    3.2.2. Service处理和推送通知:
    • 最终通过mHandler执行EnqueueNotificationRunnable。

      NotificationManagerService.java

      @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) {
          ...
          // 检查发送通知的App是不是同一个App或系统App
          checkCallerIsSystemOrSameApp(pkg);
      
          // The system can post notifications for any package, let us resolve that.
          final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
          .....
          // setup local book-keeping
          String channelId = notification.getChannelId();
          ...
          // 在Android 9.0上如果没有创建channel,则无法发送通知
          final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
                  notificationUid, channelId, false /* includeDeleted */);
          if (channel == null) {
              final String noChannelStr = "No Channel found for "
                      + "pkg=" + pkg
                      + ", channelId=" + channelId
                      + ", id=" + id
                      + ", tag=" + tag
                      + ", opPkg=" + opPkg
                      + ", callingUid=" + callingUid
                      + ", userId=" + userId
                      + ", incomingUserId=" + incomingUserId
                      + ", notificationUid=" + notificationUid
                      + ", notification=" + notification;
              Log.e(TAG, noChannelStr);
              boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
                      == NotificationManager.IMPORTANCE_NONE;
      
              if (!appNotificationsOff) {
                  doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                          "Failed to post notification on channel \"" + channelId + "\"\n" +
                          "See log for more details");
              }
              return;
          }
          // 将通知信息封装到StatusBarNotification对象
          final StatusBarNotification n = new StatusBarNotification(
                  pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                  user, null, System.currentTimeMillis());
          // 然后再次创建NotificationRecord对象,该对象仅仅在system_server进程中流通        
          final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
          r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));
      
          ...
          // 通知发送速率的检查、黑名单的检查、通知上限的检查,每个应用当前的通知数量最多为50个
          if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                  r.sbn.getOverrideGroupKey() != null)) {
              return;
          }
          ...
          mHandler.post(new EnqueueNotificationRunnable(userId, r));
      }
      
    • EnqueueNotificationRunnable最终又通过Handler 执行 PostNotificationRunnable进行最后的推送通知。

      NotificationManagerService.EnqueueNotificationRunnable

      protected class EnqueueNotificationRunnable implements Runnable {
          ...
          @Override
          public void run() {
              synchronized (mNotificationLock) {
                  mEnqueuedNotifications.add(r);
                  scheduleTimeoutLocked(r);
      
                  final StatusBarNotification n = r.sbn;
                  if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                  // 获取旧的NotificationRecord
                  NotificationRecord old = mNotificationsByKey.get(n.getKey());
                  if (old != null) {
                      // Retain ranking information from previous record
                      r.copyRankingInformation(old);
                  }
      
                  final int callingUid = n.getUid();
                  final int callingPid = n.getInitialPid();
                  final Notification notification = n.getNotification();
                  final String pkg = n.getPackageName();
                  final int id = n.getId();
                  final String tag = n.getTag();
                  .....
                  // This conditional is a dirty hack to limit the logging done on
                  //     behalf of the download manager without affecting other apps.
                  if (!pkg.equals("com.android.providers.downloads")
                          || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
                      int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
                      if (old != null) {
                          enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
                      }
                      // 打印Event Log,一般在进行通知的问题分析过程中,可通过该LOG查看当前通知的信息,判断当前通知是否发送成功;
                      EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                              pkg, id, tag, userId, notification.toString(),
                              enqueueStatus);
                  }
                  // 更新NotificationRecord内容,RankingHelper管理了通知的开关、channel维护,比如横幅通知是否开启,在此处更新;
                  mRankingHelper.extractSignals(r);
      
                  // tell the assistant service about the notification
                  if (mAssistants.isEnabled()) {
                      mAssistants.onNotificationEnqueued(r);
                      mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                              DELAY_FOR_ASSISTANT_TIME);
                  } else {
                      // Post 通知出去
                      mHandler.post(new PostNotificationRunnable(r.getKey()));
                  }
              }
          }
      }
      
      
    • 推送通知最后又交由NotificationListeners mListeners处理,通过类名可以判断出NotificationListeners应该维护了通知的监听者,那么我们可以猜测SystemUI的注册类应该有该Listerners维护。

      NotificationManagerService.PostNotificationRunnable

      protected class PostNotificationRunnable implements Runnable {
          ....
          @Override
          public void run() {
              synchronized (mNotificationLock) {
                  try {
                      NotificationRecord r = null;
                      int N = mEnqueuedNotifications.size();
                      for (int i = 0; i < N; i++) {
                          final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                          if (Objects.equals(key, enqueued.getKey())) {
                              r = enqueued;
                              break;
                          }
                      }
                      ....
                      NotificationRecord old = mNotificationsByKey.get(key);
                      final StatusBarNotification n = r.sbn;
                      final Notification notification = n.getNotification();
                      // 通知列表mNotificationList查看是否存在该通知 
                      int index = indexOfNotificationLocked(n.getKey());
                      if (index < 0) {
                          mNotificationList.add(r);
                          mUsageStats.registerPostedByApp(r);
                          r.setInterruptive(isVisuallyInterruptive(null, r));
                      } else {
                          old = mNotificationList.get(index);
                          mNotificationList.set(index, r);
                          mUsageStats.registerUpdatedByApp(r, old);
                          // Make sure we don't lose the foreground service state.
                          notification.flags |=
                                  old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                          r.isUpdate = true;
                          r.setTextChanged(isVisuallyInterruptive(old, r));
                      }
      
                      // 更新通知列表  
                      mNotificationsByKey.put(n.getKey(), r);
      
                      // 再一次确认前台通知的标签没有被清除掉
                      // Ensure if this is a foreground service that the proper additional
                      // flags are set.
                      if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                          notification.flags |= Notification.FLAG_ONGOING_EVENT
                                  | Notification.FLAG_NO_CLEAR;
                      }
      
                      applyZenModeLocked(r);
                      // 对通知列表进行排序
                      mRankingHelper.sort(mNotificationList);
      
                      if (notification.getSmallIcon() != null) {
                          StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                          // 关键步骤:将通知推送出去,让NotificationListeners:mListeners处理
                          mListeners.notifyPostedLocked(r, old);
                          if (oldSbn == null 
                          || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
                              mHandler.post(new Runnable() {
                                  @Override
                                  public void run() {
                                      mGroupHelper.onNotificationPosted(
                                              n, hasAutoGroupSummaryLocked(n));
                                  }
                              });
                          }
                      } else {
                          ........
                      }
      
                      if (!r.isHidden()) {
                          // 处理该通知,主要是是否发声,震动,Led灯
                          buzzBeepBlinkLocked(r);
                      }
                      maybeRecordInterruptionLocked(r);
                  } finally {
                      int N = mEnqueuedNotifications.size();
                      for (int i = 0; i < N; i++) {
                          final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                          if (Objects.equals(key, enqueued.getKey())) {
                              mEnqueuedNotifications.remove(i);
                              break;
                          }
                      }
                  }
              }
          }
      }
      
      
    • NotificationListeners 的notifyPosted中将通知推送至SystemUI中显示,在该方法中listener数据类型是NotificationListenerWrapper的代理对象,NotificationListenerWrapper在SystemUI进程,在此处listener是client, NotificationListenerWrapper是server。
      保留问题2: listener是什么时候成为SystemUI进程处理通知的代理类?

      NotificationManagerService.NotificationListeners

      public class NotificationListeners extends ManagedServices {
          
          public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
              notifyPostedLocked(r, old, true);
          }
      
          private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) {
              // Lazily initialized snapshots of the notification.
              StatusBarNotification sbn = r.sbn;
              StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
              TrimCache trimCache = new TrimCache(sbn);
              // 遍历所有ManagedServiceInfo
              for (final ManagedServiceInfo info : getServices()) {
                  ....
                  // 如果该通知变得不可见,则移除老的通知
                  // This notification became invisible -> remove the old one.
                  if (oldSbnVisible && !sbnVisible) {
                      final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
                      mHandler.post(new Runnable() {
                          @Override
                          public void run() {
                              notifyRemoved(info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
                          }
                      });
                      continue;
                  }
                  // 推送通知
                  mHandler.post(new Runnable() {
                      @Override
                      public void run() {
                          notifyPosted(info, sbnToPost, update);
                      }
                  });
              }
          }
              
          private void notifyPosted(final ManagedServiceInfo info,
                  final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
              // listener数据类型是NotificationListenerWrapper的代理对象,NotificationListenerWrapper在SystemUI进程,在此处listener是client, NotificationListenerWrapper是server    
              final INotificationListener listener = (INotificationListener) info.service;
              StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
              try {
                  // 跨进程调用,进入SystemUI进程
                  listener.onNotificationPosted(sbnHolder, rankingUpdate);
              } catch (RemoteException ex) {
                  Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
              }
          }        
      }
      
    3.3. 展示通知–SystemUI进程

    首先我们来解决上面留下的保留问题2,SystemUI是怎么接收和处理通知的。

    3.3.1. 注册监听

    • SystemUI进程在起来的时候,会执行StatusBar.start()方法,start()方法里面会执行mNotificationListener.setUpWithPresenter()在注册监听,其中mNotificationListener是NotificationListener类型,然后我们来看看NotificationListener类。

      StatusBar.java

      @Override
      public void start() {
          // Set up the initial notification state.
          mNotificationListener.setUpWithPresenter(this, mEntryManager);
      }
      
    • NotificationListener类继承于NotificationListenerWithPlugins,而NotificationListenerWithPlugins又继承于NotificationListenerService,同时也看到非常熟悉的方法,比如onNotificationPosted 等等。
      注册最终会调到NotificationListenerService.registerAsSystemService()方法;

      NotificationListener.java

      public class NotificationListener extends NotificationListenerWithPlugins {
          private static final String TAG = "NotificationListener";
      
          // Dependencies:
          private final NotificationRemoteInputManager mRemoteInputManager =
                  Dependency.get(NotificationRemoteInputManager.class);
      
          private final Context mContext;
      
          protected NotificationPresenter mPresenter;
          protected NotificationEntryManager mEntryManager;
      
          public NotificationListener(Context context) {
              mContext = context;
          }
      
          @Override
          public void onListenerConnected() {
              if (DEBUG) Log.d(TAG, "onListenerConnected");
              onPluginConnected();
              final StatusBarNotification[] notifications = getActiveNotifications();
              if (notifications == null) {
                  Log.w(TAG, "onListenerConnected unable to get active notifications.");
                  return;
              }
              final RankingMap currentRanking = getCurrentRanking();
              mPresenter.getHandler().post(() -> {
                  for (StatusBarNotification sbn : notifications) {
                      mEntryManager.addNotification(sbn, currentRanking);
                  }
              });
          }
      
          // 接收通知,然后交由mEntryManager进行处理
          @Override
          public void onNotificationPosted(final StatusBarNotification sbn,
                  final RankingMap rankingMap) {
              if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
              if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
                  mPresenter.getHandler().post(() -> {
                      processForRemoteInput(sbn.getNotification(), mContext);
                      String key = sbn.getKey();
                      mEntryManager.removeKeyKeptForRemoteInput(key);
                      boolean isUpdate =
                              mEntryManager.getNotificationData().get(key) != null;
                      // In case we don't allow child notifications, we ignore children of
                      // notifications that have a summary, since` we're not going to show them
                      // anyway. This is true also when the summary is canceled,
                      // because children are automatically canceled by NoMan in that case.
                      if (!ENABLE_CHILD_NOTIFICATIONS
                              && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
                          if (DEBUG) {
                              Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                          }
      
                          // Remove existing notification to avoid stale data.
                          if (isUpdate) {
                              mEntryManager.removeNotification(key, rankingMap);
                          } else {
                              mEntryManager.getNotificationData()
                                      .updateRanking(rankingMap);
                          }
                          return;
                      }
                      if (isUpdate) {
                          mEntryManager.updateNotification(sbn, rankingMap);
                      } else {
                          mEntryManager.addNotification(sbn, rankingMap);
                      }
                  });
              }
          }
      
          // 通知移除的回调,同样交由mEntryManager进行处理
          @Override
          public void onNotificationRemoved(StatusBarNotification sbn,
                  final RankingMap rankingMap) {
              if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
              if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
                  final String key = sbn.getKey();
                  mPresenter.getHandler().post(() -> {
                      mEntryManager.removeNotification(key, rankingMap);
                  });
              }
          }
      
          @Override
          public void onNotificationRankingUpdate(final RankingMap rankingMap) {
              if (DEBUG) Log.d(TAG, "onRankingUpdate");
              if (rankingMap != null) {
                  RankingMap r = onPluginRankingUpdate(rankingMap);
                  mPresenter.getHandler().post(() -> {
                      mEntryManager.updateNotificationRanking(r);
                  });
              }
          }
      
          // 注册回调
          public void setUpWithPresenter(NotificationPresenter presenter,
                  NotificationEntryManager entryManager) {
              mPresenter = presenter;
              mEntryManager = entryManager;
      
              try {
                  registerAsSystemService(mContext,
                          new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                          UserHandle.USER_ALL);
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to register notification listener", e);
              }
          }
      }
      
    • 创建NotificationListenerWrapper实例,NotificationListenerWrapper类就是上面所述的server端,然后将mWrapper对象通过NotificationManagerService传递出去;再来看看NotificationManagerService 的 registerListener 方法

      NotificationListenerService.java

      public void registerAsSystemService(Context context, ComponentName componentName,
              int currentUser) throws RemoteException {
          if (mWrapper == null) {
              mWrapper = new NotificationListenerWrapper();
          }
          mSystemContext = context;
          INotificationManager noMan = getNotificationInterface();
          mHandler = new MyHandler(context.getMainLooper());
          mCurrentUser = currentUser;
          noMan.registerListener(mWrapper, componentName, currentUser);
      }
      
      protected class NotificationListenerWrapper extends INotificationListener.Stub {
          @Override
          public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                  NotificationRankingUpdate update) {
              StatusBarNotification sbn;
              
              ...
              // protect subclass from concurrent modifications of (@link mNotificationKeys}.
              synchronized (mLock) {
                  applyUpdateLocked(update);
                  if (sbn != null) {
                      SomeArgs args = SomeArgs.obtain();
                      args.arg1 = sbn;
                      args.arg2 = mRankingMap;
                      // 发送通知,mHandler处理MSG_ON_NOTIFICATION_POSTED事件时,会调用自身的onNotificationPosted 方法,即NotificationListener 重写的onNotificationPosted 方法; 
                      mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
                              args).sendToTarget();
                  } else {
                      // still pass along the ranking map, it may contain other information
                      mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
                              mRankingMap).sendToTarget();
                  }
              }
      
          }
      
          @Override
          public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
                  NotificationRankingUpdate update, NotificationStats stats, int reason) {
              ........
      
          }
      
          @Override
          public void onListenerConnected(NotificationRankingUpdate update) {
              .....
          }
      
          @Override
          public void onNotificationRankingUpdate(NotificationRankingUpdate update) throws RemoteException {
              .....    
          }
      
          @Override
          public void onListenerHintsChanged(int hints) throws RemoteException {
              mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
                      hints, 0).sendToTarget();
          }
      
          .....
      }
      
    • mListeners 会将INotificationListener封装到ManagedServices.ManagedServiceInfo ,看到这里一切就明了。

      NotificationManagerService.java

      public void registerListener(final INotificationListener listener,
                                        final ComponentName component, final int userid) {
          enforceSystemOrSystemUI("INotificationManager.registerListener");
          mListeners.registerService(listener, component, userid);
      }
      
    • 保留问题1
      SystemUI什么时候去创建RemoteView,SystemUI里面有个创建通知视图的类NotificationInflater,在类里面会根据传递过来的StatusBarNotification 数据在自己进程端构建RemoteView。
      这是非常聪明的做法,对于标准通知视图,根本不用携带着在进程间通信流转,非常浪费资源甚至造成卡顿。

      NotificationInflater.java

      // 重建通知的RemoteView
      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;
          result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
          result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
                  true /* showingPublic */);
          return result;
      }
      
      // 最终也是调用Notification.Builder来创建
      private static RemoteViews createContentView(Notification.Builder builder,
              boolean isLowPriority, boolean useLarge) {
          if (isLowPriority) {
              return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
          }
          return builder.createContentView(useLarge);
      }
      

    4. 小结

    • Notification系统用了典型的建造者模式;
    • 针对标准视图样式,使用了传参的方式在SystemUI进程创建RemoteView,提高跨进程通讯的效率;
    展开全文
  • 请求大神Android通知栏颜色怎么设定,有系统限制吗?我想将APP的颜色与通知栏设为一个颜色
  • Android通知Notification

    千次阅读 2015-02-20 15:12:00
    一个小demo。点击 发送通知 按钮,则发送通知到设备的通知栏。点击 清除通知 则清除通知栏上的消息通知。 package zhangphil.notification;...import android.os.Bundle;...import android.app.Activit
  • Android 通知栏推送消息

    千次阅读 2017-08-22 16:55:20
    Android 通知栏消息 //创建通知管理类 NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); //创建通知建设类 Notification.Builder builder = new Notification.Bui
  • 关于Android通知的浮动通知(横幅)不显示的解决方法 大家在学习Android的通知的时候,可能会遇到浮动通知不显示的问题. 下面给大家介绍初步的解决方案. 相信大家查询解决方案的时候,多少有了解如何修改代码解决,csdn的...
  • Android通知栏介绍与适配总结

    千次阅读 2017-09-14 10:40:51
    由于历史原因,Android在发布之...本文总结了Android通知栏的版本迭代过程,在通知栏开发过程中所遇到的各种各样的坑,以及一些解决技巧,特别的,对于大众期盼的Android 7.0的到来,通知栏又会发生怎样的改变呢?接下
  • 目录Android 通知栏 8.0上升到9.0的适配 Android 通知栏 8.0上升到9.0的适配 1、9.0要在清单文件配置权限:<uses-permission android:name=“android.permission.FOREGROUND_SERVICE”/> 2、NotifyBuilder的...
  • Android通知栏版本兼容解决方案

    千次阅读 2017-03-09 19:58:11
    Android通知栏是我们在APP中几乎必须使用到的,自定义通知栏给我们带来很多拓展性,但是我们在使用自定义通知栏时往往会遇到自定义通知栏和系统本身颜色、字体等不兼容的问题,本篇博客就重点讲解如何解决Android...
  • android通知栏消息

    千次阅读 2019-06-14 16:35:29
    android通知栏弹出消息, 看了很多博客,方到代码里都略有问题,最后只能去官方文档找示例。 官方文档 这个通知栏既可以通过service触发,也可以通过activity触发。 public class Notify { private ...
  • Android通知栏图标空白情况

    千次阅读 2017-11-03 11:15:26
    最近项目上线,遇到个有意思的事情,Android通知栏图标会有空白,大概就是这个样子的 原因就不多说了,参考文章http://www.imooc.com/article/8175和http://blog.csdn.net/u013706904/article/details/51912634。 ...
  • Android 通知渠道Notification Channel

    万次阅读 2019-01-11 13:54:39
    Android8.0也就是API26开始要求通知设置Channel,否则会报错   查看官方通知: 通知Android 8.0 中,我们已重新设计通知,以便为管理通知行为和设置提供更轻松和更统一的方式。这些变更包括: Android ...
  • 原生通信系列 1. Flutter 调用 Android 2. Android 通知 Flutter 3. Flutter 调用 iOS 4. iOS 通知 Flutter 项目地址 第二篇介绍的是原生通知 dart 开篇就是灵魂流程图,自己体会吧
  • Builder (android.content.Context, String) in Builder cannot be applied(android通知) 《第一行代码》通知出错原因: 第一点注意: 书上说要引入support-v4,现在用的大多都是support-v7,本来就已经自动引入;...
  • 关于Android 通知栏主要是基于Android 4.x、Android 5.x、Android 7.x为解决界限,例如,你可能解决Android 5.x以上版本标题字体颜色适配问题,却发现通知小图标竟然却是小白块等等。
  • Android通知栏-Notification(通知消息)

    万次阅读 多人点赞 2019-05-29 22:29:08
    1.概述 当应用程序在后台运行,希望向用户发出一些提示学习,就需要借助Notification(通知)来实现。在发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后就可以...标准视图在Android中各...
  • android通知对话框、多选对话框、单选对话框
  • 全新的Android通知栏,已抛弃setLatestEventInfo,兼容高版本 这算是一个入门级的Android通知栏notification的文章,因为在项目中要用到, 又发现以前的低版本的用setLatestEventInfo已过时,还报错,完全不兼容。...
  • Android通知栏微技巧,那些你所没关注过的小细节

    万次阅读 多人点赞 2016-05-23 08:56:41
    对于通知栏的使用,Android各个版本其实都有比较大的调整,包括即将发布的Android 7.0版本,通知栏功能上又要有大动作。那么新版本的通知栏API无法兼容老系统这就会是一个很头疼的问题。 为此Android在appcompat-v7...
  • Android通知栏Notification弹出横幅显示的解决方法:  利用Toast模拟显示Notification横幅通知,测试了多款手机,没有发现任何设备兼容性,具体实现请参考github:  ...
  • 此文已由作者黎星授权网易云社区发布。欢迎访问网易云社区,了解更多网易技术产品运营经验。...本文总结了Android通知栏的版本迭代过程,在通知栏开发过程中所遇到的各种各样的坑,以及一些解决技巧...
  • android的应用层中,涉及到很多应用框架,例如:Service框架,Activity管理机制,Broadcast机制,对话框框架,标题栏框架,状态栏框架,通知机制,ActionBar框架等等。 下面就来说说经常会使用到通知机制中的通知...
  • Android 通知用户更新或移除通知

    千次阅读 2016-06-27 23:33:50
    原文地址:http://android.xsoftlab.net/training/notify-user/managing.html#Removing当需要在不同时段发布同一事件类型的通知时,应当避免创建新的通知。相反的,应当考虑更新原有的通知,比如更改通知的某些值...
  • 这是一篇关于 Android 通知栏的记录。包括:通知栏消息、点亮屏幕、震动、声音、显示样式等。下面是简单的效果图:   这是本文中的例子下载链接 下面主要讲的是: AndroidManifest 的权限配置 静态常量类 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,422
精华内容 7,768
关键字:

android通知