Android NotificationListenerService 通知服务原理解析

目录
  • 前言
  • NotificationListenerService方法集
  • NotificationListenerService接收流程
  • 通知消息发送流程
  • NotificationListenerService注册
  • 总结

前言

在上一篇通知服务NotificationListenerService使用方法 中,我们已经介绍了如何使用NotificationListenerService来监听消息通知,在最后我们还模拟了如何实现微信自动抢红包功能。

那么NotificationListenerService是如何实现系统通知监听的呢?(本篇源码分析基于API 32)

NotificationListenerService方法集

NotificationLisenerService是Service的子类

public abstract class NotificationListenerService extends Service

除了Service的方法属性外,NotificationListenerService还为我们提供了收到通知、通知被移除、连接到通知管理器等方法,如下图所示。

一般业务中我们只关注有标签的那四个方法即可。

NotificationListenerService接收流程

既然NotificationListenerService是继承自Service的,我们先来看它的onBind方法,代码如下所示。

@Override
public IBinder onBind(Intent intent) {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    return mWrapper;
}

在onBind方法中返回了一个NotificationListenerWrapper实例,NotificationListenerWrapper对象是定义在NotificationListenerService中的一个内部类。主要方法如下所示。

/** @hide */
protected class NotificationListenerWrapper extends INotificationListener.Stub {
    @Override
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
            NotificationRankingUpdate update) {
        StatusBarNotification sbn;
        try {
            sbn = sbnHolder.get();
        } catch (RemoteException e) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
            return;
        }
        if (sbn == null) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
            return;
        }
        try {
            // convert icon metadata to legacy format for older clients
            createLegacyIconExtras(sbn.getNotification());
            maybePopulateRemoteViews(sbn.getNotification());
            maybePopulatePeople(sbn.getNotification());
        } catch (IllegalArgumentException e) {
            // warn and drop corrupt notification
            Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
                    sbn.getPackageName());
            sbn = null;
        }
        // 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.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();
            }
        }
        ...省略onNotificationRemoved等方法
    }

NotificationListenerWrapper继承自INotificationListener.Stub,当我们看到Stub这一关键字的时候,就应该知道这里是使用AIDL实现了跨进程通信。

在NotificationListenerWrapper的onNotificationPosted中通过代码

mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
                        args).sendToTarget();

将消息发送出去,handler接受后,又调用NotificationListernerService的onNotificationPosted方法,进而实现通知消息的监听。代码如下所示。

private final class MyHandler extends Handler {
        public static final int MSG_ON_NOTIFICATION_POSTED = 1;
        @Override
        public void handleMessage(Message msg) {
            if (!isConnected) {
                return;
            }
            switch (msg.what) {
                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);
                } break;
           ...
            }
        }
    }

那么,消息通知发送时,又是如何与NotificationListenerWrapper通信的呢?

通知消息发送流程

当客户端发送一个通知的时候,会调用如下所示的代码

notificationManager.notify(1, notification)

notify又会调用notifyAsUser方法,代码如下所示

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
    INotificationManager service = getService();
    String pkg = mContext.getPackageName();
    try {
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                fixNotification(notification), user.getIdentifier());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

紧接着又会走到INotificationManager的enqueueNotificationWithTag方法中,enqueueNotificationWithTag是声明在INotificationManager.aidl文件中的接口

/** {@hide} */
interface INotificationManager
{
    @UnsupportedAppUsage
    void cancelAllNotifications(String pkg, int userId);
    ...
    void cancelToast(String pkg, IBinder token);
    void finishToken(String pkg, IBinder token);
    void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
            in Notification notification, int userId);
    ...
 }

这个接口是在NotificationManagerService中实现的,接着我们转到NotificationManagerService中去查看,相关主要代码如下所示。

@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
  @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);
         }
}

enqueueNotificationWithTag方法会走进enqueueNotificationInternal方法,在方法最后会通过Handler发送一个EnqueueNotificationRunnable,代码如下所示。

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, boolean postSilently) {
        ...
        //构造StatusBarNotification,用于分发监听服务
        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());
        // setup local book-keeping
        String channelId = notification.getChannelId();
        if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
            channelId = (new Notification.TvExtender(notification)).getChannelId();
        }
        ...
        // 设置intent的白名点,是否盛典、是否后台启动等
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
                final long duration = LocalServices.getService(
                        DeviceIdleInternal.class).getNotificationAllowlistDuration();
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                        mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, duration,
                                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                                REASON_NOTIFICATION_SERVICE,
                                "NotificationManagerService");
                        mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
                                        | FLAG_SERVICE_SENDER));
                    }
                }
            }
        }
        ...
        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
    }

EnqueueNotificationRunnable源码如下所示。

protected class EnqueueNotificationRunnable implements Runnable {
        private final NotificationRecord r;
        private final int userId;
        private final boolean isAppForeground;
        EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
            this.userId = userId;
            this.r = r;
            this.isAppForeground = foreground;
        }
        @Override
        public void run() {
            synchronized (mNotificationLock) {
                ...
                //将通知加入队列
                mEnqueuedNotifications.add(r);
                scheduleTimeoutLocked(r);
                ...
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueuedLocked(r);
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

在EnqueueNotificationRunnable最后又会发送一个PostNotificationRunable,

PostNotificationRunable源码如下所示。

protected class PostNotificationRunnable implements Runnable {
        private final String key;
        PostNotificationRunnable(String key) {
            this.key = key;
        }
        @Override
        public void run() {
            synchronized (mNotificationLock) {
                try {
                    ...
                    //发送通知
                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                        mListeners.notifyPostedLocked(r, old);
                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
                                && !isCritical(r)) {
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationPosted(
                                            n, hasAutoGroupSummaryLocked(n));
                                }
                            });
                        } else if (oldSbn != null) {
                            final NotificationRecord finalRecord = r;
                            mHandler.post(() -> mGroupHelper.onNotificationUpdated(
                                    finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
                        }
                    } else {
                        //...
                    }
                } finally {
                    ...
                }
            }
        }
    }

从代码中可以看出,PostNotificationRunable类中会调用notifyPostedLocked方法,这里你可能会有疑问:这里分明判断notification.getSmallIcon()是否为null,不为null时才会进入notifyPostedLocked方法。为什么这里直接默认了呢?这是因为在Android5.0中规定smallIcon不可为null,且NotificationListenerService仅适用于5.0以上,所以这里是必然会执行到notifyPostedLocked方法的。

其方法源码如下所示。

 private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
            try {
                // Lazily initialized snapshots of the notification.
                StatusBarNotification sbn = r.getSbn();
                StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                TrimCache trimCache = new TrimCache(sbn);
                //循环通知每个ManagedServiceInfo对象
                for (final ManagedServiceInfo info : getServices()) {
                    ...
                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                }
            } catch (Exception e) {
                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
            }
        }

notifyPostedLocked方法最终会调用notifyPosted方法,我们再来看notifyPosted方法。

 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) {
                Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
           }
 }

notifyPosted方法,最终会调用INotificationListerner的onNotificationPosted方法,这样就通知到了NotificationListenerService的onNotificationPosted方法。

上述方法的流程图如下图所示。

NotificationListenerService注册

在NotificationListenerService中通过registerAsSystemService方法注册服务,代码如下所示。

 @SystemApi
    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);
    }

registerAsSystemService方法将NotificationListenerWrapper对象注册到NotificationManagerService中。如此就实现了对系统通知的监听。

总结

NotificationListenerService实现对系统通知的监听可以概括为三步:

  • NotificationListenerService将 NotificationListenerWrapper注册到NotificationManagerService中。
  • 当有通知被发送时 ,NotificationManagerService跨进程通知到每个NotificationListenerWrapper。
  • NotificationListenerWrapper中信息由NotificationListenerService类中的Handler中处理,从而调用NotificationListenerService中对应的回调方法。

以上就是Android NotificationListenerService 通知服务原理解析的详细内容,更多关于Android NotificationListenerService的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android给通知channel静音的方法实例

    目录 前言 静音 总结 前言 目前各个市场都要求targetsdkversion要不低于26,也就是android 8.0. 相应的影响很多功能,比如通知. 当targetsdkversion >= 26,需要为通知添加channel,如 manager = (NotificationManager) BaseApp.getAppContext() .getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT

  • Android 通知使用权(NotificationListenerService)的使用

    Android  通知使用权(NotificationListenerService)的使用 简介 当下不少第三方安全APP都有消息管理功能或者叫消息盒子功能,它们能管理过滤系统中的一些无用消息,使得消息栏更清爽干净.其实此功能的实现便是使用了Android中提供的通知使用权权限.Android4.3后加入了通知使用权NotificationListenerService,就是说当你开发的APP拥有此权限后便可以监听当前系统的通知的变化,在Android4.4后还扩展了可以获取通知详情信息.下面

  • Android App应用退到后台显示通知的实现方法

    目录 需求背景 崩溃信息 总结 需求背景 刚开始接到这个需求时,第一时间想到的是做成跟银行类app一样用户退到主页之后,需要在通知栏显示“XXX在后台运行”,并且该通知不能被清除,只有用户重新进入app再消失.然后就想到了一个方案前台服务(foregroundService) 来实现,于是撸起袖子就是干. 1.创建一个ForegroundService继承Service 2.重写onCreate等一系列方法 3.创建通知,根据不同版本来开启服务 根据不同版本开启服务 4.监听Applicatio

  • Android使用Notification实现通知功能

    这篇文章并未详细介绍通知相关的api,而是作者自己对通知的一些大致总结,以便日后查看,请读者自行参考阅读. andorid关于通知在多个sdk版本中均有修改,因此部分api涉及到版本兼容的问题.编程中我们使用NotificationCompat来实现通知的相关功能. 1.通知中添加按钮的方式 Notification可以通过直接调Notification.Builder.addAction(int icon, CharSequence title, PendingIntent intent)或者

  • Android无障碍监听通知的实战过程

    目录 监听通知 无障碍服务监听通知逻辑 ToastPresenter NotificationManagerService PostNotificationRunnable 总结 监听通知 Android 中的 AccessibilityService 可以监听通知信息的变化,首先需要创建一个无障碍服务,这个教程可以自行百度.在无障碍服务的配置文件中,需要以下配置: <accessibility-service ... android:accessibilityEventTypes="其他

  • android使用NotificationListenerService监听通知栏消息

    NotificationListenerService是通过系统调起的服务,在应用发起通知时,系统会将通知的应用,动作和信息回调给NotificationListenerService.但使用之前需要引导用户进行授权.使用NotificationListenerService一般需要下面三个步骤. 注册服务 首先需要在AndroidManifest.xml对service进行注册. <service android:name=".NotificationCollectorService&q

  • Android NotificationListenerService 通知服务原理解析

    目录 前言 NotificationListenerService方法集 NotificationListenerService接收流程 通知消息发送流程 NotificationListenerService注册 总结 前言 在上一篇通知服务NotificationListenerService使用方法 中,我们已经介绍了如何使用NotificationListenerService来监听消息通知,在最后我们还模拟了如何实现微信自动抢红包功能. 那么NotificationListenerSe

  • Android RecyclerView缓存复用原理解析

    目录 一.牵出缓存 1.缓存还在屏幕内的ViewHolder——Scrap缓存 mAttachedScrap mChangeScrap 用一个例子说明 2.缓存屏幕之外的ViewHolder——CacheView 3.mViewCacheExtension 4.RecycledViewPool 二.到底是四级缓存还是三级缓存 一.牵出缓存 都有哪些缓存,作用是什么,为什么这么设计 1.缓存还在屏幕内的ViewHolder——Scrap缓存 Scrap是RecyclerView中最轻量的缓存,它不

  • Android开发Compose remember原理解析

    目录 正文 随机色文本 原因分析 正确实现 remember的原理剖析 小结 正文 看过Compose案例或者源码的你,相信肯定是见过 remember 了的.顾名思义,Compose是要让我们的代码“记住”东西,那到底是记住什么呢?要是不 remember,相关功能就实现不了了吗? 带着这些问题,来一探究竟吧 随机色文本 假设有这么一个“随机底色文本”的需求:实现一个 Text,其背景色每次启动都随机产生,且生命周期内不变 用Compose可以实现如下: private val items =

  • Android APK应用安装原理解析之AndroidManifest使用PackageParser.parserPackage原理分析

    本文实例讲述了Android APK应用安装之AndroidManifest使用PackageParser.parserPackage原理.分享给大家供大家参考,具体如下: Android 安装一个APK的时候首先会解析APK,这里要做很多事情,其中一个事情就是解析Manifest.xml文件,并将所有APK的Manifest封装到各种对象中并保存在内存当中 解析Manifest的类是非常重要的,该类就是frameworks\base\core\java\android\content\pm\P

  • Android中微信抢红包插件原理解析及开发思路

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

  • Android ListView 单条刷新方法实践及原理解析

    对于使用listView配合adapter进行刷新的方法大家都不陌生,先刷新adapter里的数据,然后调用notifydatasetchange通知listView刷新界面. 方法虽然简单,但这里面涉及到一个效率的问题,调用notifydatasetchange其实会导致adpter的getView方法被多次调用 (画面上能显示多少就会被调用多少次),如果是很明确的知道只更新了list中的某一个项的数据(比如用户点击list某一项后更新该项的显示状态,或者 后台回调更新list某一项,等等),

  • Android截屏方案实现原理解析

    Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 在截屏功能中,有时需要截取全屏的内容,有时需要截取超过一屏的内容(比如:Listview,Scrollview,RecyclerView).下面介绍各种场景获取Bitmap的方法 普通截屏的实现 获取当前Window的DrawingCache的方式,即decorView的DrawingCache /** * shot the current screen ,with the sta

  • 从云数据迁移服务看MySQL大表抽取模式的原理解析

    摘要:MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 小编最近在云上的一个迁移项目中被MySQL抽取模式折磨的很惨.一开始爆内存被客户怼,再后来迁移效率低下再被怼.MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 1.1 Java-JDBC通信原理 JDBC与数据库之间的通信是通过socket完,大致流程如下图所示.Mysql Server ->内核Socket Buffer -> 客户端Socket Buffer ->JDBC所在的JV

  • React服务端渲染原理解析与实践

    关于服务端渲染也就是我们说的SSR大多数人都听过这个概念,很多同学或许在公司中已经做过服务端渲染的项目了,主流的单页面应用比如说Vue或者React开发的项目采用的一般都是客户端渲染的模式也就是我们说的CSR. 但是这种模式会带来明显的两个问题,第一个就是TTFP时间比较长,TTFP指的就是首屏展示时间,同时不具备SEO排名的条件,搜索引擎上排名不是很好.所以我们可以借助一些工具来进行改良我们的项目,将单页面应用编程服务器端渲染项目,这样就可以解决掉这些问题了. 目前主流的服务器端渲染框架也就是

  • SpringBoot服务监控机制原理解析(面试官常问)

    前言 任何一个服务如果没有监控,那就是两眼一抹黑,无法知道当前服务的运行情况,也就无法对可能出现的异常状况进行很好的处理,所以对任意一个服务来说,监控都是必不可少的. 就目前而言,大部分微服务应用都是基于 SpringBoot 来构建,所以了解 SpringBoot 的监控特性是非常有必要的,而 SpringBoot 也提供了一些特性来帮助我们监控应用. 本文基于 SpringBoot 2.3.1.RELEASE 版本演示. SpringBoot 监控 SpringBoot 中的监控可以分为 H

随机推荐