• 深入理解通知服务NotificationListenerService原理


    前言

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

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

    NotificationListenerService方法集

    NotificationLisenerService是Service的子类

    public abstract class NotificationListenerService extends Service
    

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

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

    NotificationListenerService接收流程

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

    1. @Override
    2. public IBinder onBind(Intent intent) {
    3.     if (mWrapper == null) {
    4.         mWrapper = new NotificationListenerWrapper();
    5.     }
    6.     return mWrapper;
    7. }

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

    1. /** @hide */
    2. protected class NotificationListenerWrapper extends INotificationListener.Stub {
    3.     @Override
    4.     public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
    5.             NotificationRankingUpdate update) {
    6.         StatusBarNotification sbn;
    7.         try {
    8.             sbn = sbnHolder.get();
    9.         } catch (RemoteException e) {
    10.             Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
    11.             return;
    12.         }
    13.         if (sbn == null) {
    14.             Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
    15.             return;
    16.         }
    17.         try {
    18.             // convert icon metadata to legacy format for older clients
    19.             createLegacyIconExtras(sbn.getNotification());
    20.             maybePopulateRemoteViews(sbn.getNotification());
    21.             maybePopulatePeople(sbn.getNotification());
    22.         } catch (IllegalArgumentException e) {
    23.             // warn and drop corrupt notification
    24.             Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
    25.                     sbn.getPackageName());
    26.             sbn = null;
    27.         }
    28.         // protect subclass from concurrent modifications of (@link mNotificationKeys}.
    29.         synchronized (mLock) {
    30.             applyUpdateLocked(update);
    31.             if (sbn != null) {
    32.                 SomeArgs args = SomeArgs.obtain();
    33.                 args.arg1 = sbn;
    34.                 args.arg2 = mRankingMap;
    35.                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
    36.                         args).sendToTarget();
    37.             } else {
    38.                 // still pass along the ranking map, it may contain other information
    39.                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
    40.                         mRankingMap).sendToTarget();
    41.             }
    42.         }
    43.         ...省略onNotificationRemoved等方法
    44.     }

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

    在NotificationListenerWrapper的onNotificationPosted中通过代码

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

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

    1. private final class MyHandler extends Handler {
    2.         public static final int MSG_ON_NOTIFICATION_POSTED = 1;
    3.         @Override
    4.         public void handleMessage(Message msg) {
    5.             if (!isConnected) {
    6.                 return;
    7.             }
    8.             switch (msg.what) {
    9.                 case MSG_ON_NOTIFICATION_POSTED: {
    10.                     SomeArgs args = (SomeArgs) msg.obj;
    11.                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
    12.                     RankingMap rankingMap = (RankingMap) args.arg2;
    13.                     args.recycle();
    14.                     onNotificationPosted(sbn, rankingMap);
    15.                 } break;
    16.            ...
    17.             }
    18.         }
    19.     }

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

    通知消息发送流程

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

    notificationManager.notify(1, notification)
    

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

    1. public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    2. {
    3.     INotificationManager service = getService();
    4.     String pkg = mContext.getPackageName();
    5.     try {
    6.         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
    7.         service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
    8.                 fixNotification(notification), user.getIdentifier());
    9.     } catch (RemoteException e) {
    10.         throw e.rethrowFromSystemServer();
    11.     }
    12. }

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

    1. /** {@hide} */
    2. interface INotificationManager
    3. {
    4.     @UnsupportedAppUsage
    5.     void cancelAllNotifications(String pkg, int userId);
    6.     ...
    7.     void cancelToast(String pkg, IBinder token);
    8.     void finishToken(String pkg, IBinder token);
    9.     void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
    10.             in Notification notification, int userId);
    11.     ...
    12.  }

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

    1. @VisibleForTesting
    2. final IBinder mService = new INotificationManager.Stub() {
    3.   @Override
    4.        public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
    5.                   Notification notification, int userId) throws RemoteException {
    6.               enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
    7.                       Binder.getCallingPid(), tag, id, notification, userId);
    8.          }
    9. }

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

    1. void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
    2.             final int callingPid, final String tag, final int id, final Notification notification,
    3.             int incomingUserId, boolean postSilently) {
    4.         ...
    5.         //构造StatusBarNotification,用于分发监听服务
    6.         final StatusBarNotification n = new StatusBarNotification(
    7.                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
    8.                 user, null, System.currentTimeMillis());
    9.         // setup local book-keeping
    10.         String channelId = notification.getChannelId();
    11.         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
    12.             channelId = (new Notification.TvExtender(notification)).getChannelId();
    13.         }
    14.         ...
    15.         // 设置intent的白名点,是否盛典、是否后台启动等
    16.         if (notification.allPendingIntents != null) {
    17.             final int intentCount = notification.allPendingIntents.size();
    18.             if (intentCount > 0) {
    19.                 final long duration = LocalServices.getService(
    20.                         DeviceIdleInternal.class).getNotificationAllowlistDuration();
    21.                 for (int i = 0; i < intentCount; i++) {
    22.                     PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
    23.                     if (pendingIntent != null) {
    24.                         mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
    25.                                 ALLOWLIST_TOKEN, duration,
    26.                                 TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
    27.                                 REASON_NOTIFICATION_SERVICE,
    28.                                 "NotificationManagerService");
    29.                         mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
    30.                                 ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
    31.                                         | FLAG_SERVICE_SENDER));
    32.                     }
    33.                 }
    34.             }
    35.         }
    36.         ...
    37.         mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
    38.     }

    EnqueueNotificationRunnable源码如下所示。

    1. protected class EnqueueNotificationRunnable implements Runnable {
    2.         private final NotificationRecord r;
    3.         private final int userId;
    4.         private final boolean isAppForeground;
    5.         EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
    6.             this.userId = userId;
    7.             this.r = r;
    8.             this.isAppForeground = foreground;
    9.         }
    10.         @Override
    11.         public void run() {
    12.             synchronized (mNotificationLock) {
    13.                 ...
    14.                 //将通知加入队列
    15.                 mEnqueuedNotifications.add(r);
    16.                 scheduleTimeoutLocked(r);
    17.                 ...
    18.                 if (mAssistants.isEnabled()) {
    19.                     mAssistants.onNotificationEnqueuedLocked(r);
    20.                     mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
    21.                             DELAY_FOR_ASSISTANT_TIME);
    22.                 } else {
    23.                     mHandler.post(new PostNotificationRunnable(r.getKey()));
    24.                 }
    25.             }
    26.         }
    27.     }

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

    PostNotificationRunable源码如下所示。

    1. protected class PostNotificationRunnable implements Runnable {
    2.         private final String key;
    3.         PostNotificationRunnable(String key) {
    4.             this.key = key;
    5.         }
    6.         @Override
    7.         public void run() {
    8.             synchronized (mNotificationLock) {
    9.                 try {
    10.                     ...
    11.                     //发送通知
    12.                     if (notification.getSmallIcon() != null) {
    13.                         StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
    14.                         mListeners.notifyPostedLocked(r, old);
    15.                         if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
    16.                                 && !isCritical(r)) {
    17.                             mHandler.post(new Runnable() {
    18.                                 @Override
    19.                                 public void run() {
    20.                                     mGroupHelper.onNotificationPosted(
    21.                                             n, hasAutoGroupSummaryLocked(n));
    22.                                 }
    23.                             });
    24.                         } else if (oldSbn != null) {
    25.                             final NotificationRecord finalRecord = r;
    26.                             mHandler.post(() -> mGroupHelper.onNotificationUpdated(
    27.                                     finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
    28.                         }
    29.                     } else {
    30.                         //...
    31.                     }
    32.                 } finally {
    33.                     ...
    34.                 }
    35.             }
    36.         }
    37.     }

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

    其方法源码如下所示。

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

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

    1.  private void notifyPosted(final ManagedServiceInfo info,
    2.       final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    3.            final INotificationListener listener = (INotificationListener) info.service;
    4.            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
    5.            try {
    6.                listener.onNotificationPosted(sbnHolder, rankingUpdate);
    7.            } catch (RemoteException ex) {
    8.                 Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
    9.            }
    10.  }

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

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

    NotificationListenerService注册

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

    1.  @SystemApi
    2.     public void registerAsSystemService(Context context, ComponentName componentName,
    3.             int currentUser) throws RemoteException {
    4.         if (mWrapper == null) {
    5.             mWrapper = new NotificationListenerWrapper();
    6.         }
    7.         mSystemContext = context;
    8.         INotificationManager noMan = getNotificationInterface();
    9.         mHandler = new MyHandler(context.getMainLooper());
    10.         mCurrentUser = currentUser;
    11.         noMan.registerListener(mWrapper, componentName, currentUser);
    12.     }

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

    总结

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

    • NotificationListenerService将 NotificationListenerWrapper注册到NotificationManagerService中。

    • 当有通知被发送时 ,NotificationManagerService跨进程通知到每个NotificationListenerWrapper。

    • NotificationListenerWrapper中信息由NotificationListenerService类中的Handler中处理,从而调用NotificationListenerService中对应的回调方法。

  • 相关阅读:
    vue3+vite中使用vuex
    阿里巴巴为什么能抗住90秒100亿?看完这篇你就明白了!
    Python的一些小基础
    【从零开始学习 SystemVerilog】3.5、SystemVerilog 控制流——阻塞(Blocking)与非阻塞(Non-Blocking)
    SpringCloud | 单体商城项目拆分(微服务)
    Oracle(2-2)Oracle Net Architecture
    Linux权限管理【理论讲解+实操演示】
    MyBatis-----5、MyBatis中特殊SQL的执行
    BERT论文阅读
    unity pivot and center
  • 原文地址:https://blog.csdn.net/huangliniqng/article/details/127890379