Notifications 通知

Notifications 通知

通知是指 Android 在应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或应用中的其他实时信息。用户可以点按通知来打开应用,也可以直接在通知中执行某项操作。

通知显示

通知可以在不同的位置以不同的格式显示,例如,状态栏中的图标、抽屉式通知栏中比较详细的条目、应用图标上的标志,以及在配对的穿戴式设备上自动显示。

状态栏和抽屉式通知栏

发出通知后,通知先以图标的形式显示在状态栏中。
Notifications 通知
用户可以在状态栏向下滑动以打开抽屉式通知栏,并在其中查看更多详情及对通知执行操作。
Notifications 通知
用户可以向下拖动抽屉式通知栏中的某条通知以查看展开后视图,其中会显示更多内容以及操作按钮(如果有)。
在应用或用户关闭通知之前,通知会一直显示在抽屉式通知栏中。

提醒式通知

从 Android 5.0 开始,通知可以短暂地显示在浮动窗口中,称之为提醒式通知。这种行为通常适用于用户应立即知晓的重要通知,而且仅在设备未锁定时才显示。
Notifications 通知
提醒式通知会在应用发出通知后立即出现,稍后便会消失,但仍照常显示在抽屉式通知栏中。

可能会触发提醒式通知的条件示例:

  • 用户的 Activity 处于全屏模式(应用使用 fullScreenIntent)。
  • 通知的优先级很高,且在搭载 Android 7.1(API 级别 25)及更低版本的设备上使用铃声或振动。
  • 在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知渠道的重要程度比较高。

锁定屏幕

从 Android 5.0 开始,通知可以显示在锁定屏幕上。

您可以程序化地设置您的应用在安全锁定屏幕上所发布通知的详情可见等级,甚至可以设置通知是否显示在锁定屏幕上。

用户可以通过系统设置来选择锁定屏幕通知的详情可见等级,包括选择停用所有锁定屏幕通知。从 Android 8.0 开始,用户可以选择停用或启用各个通知渠道的锁定屏幕通知。
Notifications 通知
要了解详情,请参阅如何设置锁定屏幕的可见性

应用图标的标志

在所支持的设备(搭载 Android 8.0(API 级别 26)及更高版本)启动器中,应用图标通过在相应的应用启动器图标上显示彩色“标志”(又称“通知圆点”)来表示有新通知。

用户可以长按应用图标以查看该应用的通知。然后,用户可以关闭通知或者在长按菜单中对通知执行操作(类似于抽屉式通知栏)。
Notifications 通知
要详细了解标志的工作原理,请参阅通知标志

APP侧应用

实例 https://github.com/googlearchive/android-Notifications

通知剖析

通知的设计由系统模板决定,您的应用只需要定义模板中各个部分的内容即可。通知的部分详情仅在展开后视图中显示。
Notifications 通知

  1. 小图标:必须提供,通过 setSmallIcon() 进行设置。
  2. 应用名称:由系统提供。
  3. 时间戳:由系统提供,但您可以通过 setWhen() 将其替换掉或者通过 setShowWhen(false) 将其隐藏。
  4. 大图标:可选内容(通常仅用于联系人照片,请勿将其用于应用图标),通过 setLargeIcon() 进行设置。
  5. 标题:可选内容,通过 setContentTitle() 进行设置。
  6. 文本:可选内容,通过 setContentText() 进行设置。

要详细了解如何创建包含上述功能及其他功能的通知,请参阅创建通知

通知渠道

从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示。通过将通知归类为不同的渠道,用户可以停用您应用的特定通知渠道(而非停用您的所有通知),还可以控制每个渠道的视觉和听觉选项,所有这些操作都在 Android 系统设置中完成(如图 11 所示)。用户还可以长按通知以更改所关联渠道的行为。

在搭载 Android 7.1(API 级别 25)及更低版本的设备上,用户仅可以按应用来管理通知(在搭载 Android 7.1 及更低版本的设备上,每个应用其实只有一个渠道)。
Notifications 通知

注意:界面将渠道称作“类别”。

发布限制

从 Android 8.1(API 级别 27)开始,应用无法每秒发出一次以上的通知提示音。如果应用在一秒内发出了多条通知,这些通知都会按预期显示,但是每秒中只有第一条通知发出提示音。

不过,Android 还对通知更新频率设定了限制。如果您过于频繁地发布有关某条通知的更新(不到一秒内发布多个),系统可能会放弃部分更新。

通知的兼容性

自 Android 1.0 开始,通知系统界面以及与通知相关的 API 就在不断发展。要在支持旧设备的同时使用最新的通知 API 功能,请使用支持库通知 API NotificationCompat 及其子类,以及 NotificationManagerCompat。这样一来,您就无需编写条件代码来检查 API 级别,因为这些 API 会为您代劳。

NotificationCompat 随着平台的发展不断更新,旨在纳入最新的方法。需要注意的是,某个方法在 NotificationCompat 中可用并不能保证可以在旧设备上采用相应功能。在某些情况下,调用新推出的 API 会在旧设备上产生空操作。例如,NotificationCompat.addAction() 仅在搭载 Android 4.1(API 级别 16)及更高版本的设备上显示操作按钮。

以下是最明显的 Android 通知行为变化的摘要:
 
Android 4.1,API 级别 16

  • 推出了展开式通知模板(称为通知样式),可以提供较大的通知内容区域来显示信息。用户可以通过单指向上/向下滑动的手势展开通知。
  • 还支持以按钮的形式向通知添加其他操作。
  • 允许用户在设置中按应用关闭通知。
     

Android 4.4,API 级别 19 和 20

  • 向 API 中添加了通知监听器服务。
  • API 级别 20 中新增了 Android Wear(现已更名为 Wear OS)支持。
     

Android 5.0,API 级别 21

  • 推出了锁定屏幕和提醒式通知。
  • 用户现在可以将手机设为勿扰模式,并配置允许哪些通知在设备处于优先模式时打扰他们。
  • 向 API 集添加了通知是否在锁定屏幕上显示的方法 (setVisibility()),以及指定通知文本的“公开”版本的方法。
  • 添加了 setPriority() 方法,告知系统通知的“干扰性”(例如,将其设为“高”可使通知以提醒式通知的形式显示)。
  • 向 Android Wear(现已更名为 Wear OS)设备添加了通知堆栈支持。使用 setGroup() 将通知放入堆栈。注意,平板电脑和手机尚不支持通知堆栈。通知堆栈在以后会称为组或集合。
     

Android 7.0,API 级别 24

  • 重新设置了通知模板的样式以强调主打图片和头像。
  • 添加了三个通知模板:一个用于短信应用,另外两个用于借助展开式选项和其他系统装饰来装饰自定义内容视图。
  • 向手持设备(手机和平板电脑)添加了对通知组的支持。使用与 Android 5.0(API 级别 21)中推出的 Android Wear(现已更名为 Wear OS)通知堆栈相同的 API。
  • 用户可以使用内嵌回复功能直接在通知内进行回复(他们输入的文本将转发到通知的父级应用)。
     

Android 8.0,API 级别 26

  • 现在必须将各个通知放入特定渠道中。
  • 现在,用户可以按渠道关闭通知,而非关闭来自某个应用的所有通知。
  • 包含有效通知的应用将在主屏幕/启动器屏幕上相应应用图标的上方显示通知“标志”。
  • 现在,用户可以从抽屉式通知栏中暂停某个通知。您可以为通知设置自动超时时间。
  • 您还可以设置通知的背景颜色。
  • 部分与通知行为相关的 API 从 Notification 移至了 NotificationChannel。例如,在搭载 Android 8.0 及更高版本的设备中,使用 NotificationChannel.setImportance(),而非 NotificationCompat.Builder.setPriority()。

创建通知

build.gradle 添加支持库

dependencies {
    implementation "com.android.support:support-compat:28.0.0"
}
  • 1
  • 2
  • 3

创建基本通知

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle(textTitle)
        .setContentText(textContent)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);

通知渠道(CHANNEL_ID)

从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示。
 
Android 实例创建

    public static String createNotificationChannel(Context context) {

        // NotificationChannels are required for Notifications on O (API 26) and above.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // The id of the channel.
            String channelId = context.getPackageName();
            // The user-visible name of the channel.
            CharSequence channelName = "XhBruce";
            // Initializes NotificationChannel.
            NotificationChannel notificationChannel =
                    new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);

            NotificationManager notificationManager =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(notificationChannel);

            return channelId;
        } else {
            // Returns null for pre-O (26) devices.
            return null;
        }
    }

发送通知

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

// notificationId is a unique int for each notification that you must define
notificationManager.notify(notificationId, builder.build());

上面这个是支持包中的,实质上:

mNotificationManager = (NotificationManager) mContext.getSystemService(
                Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(tag, id, notification);

取消通知

mNotificationManager.cancel(notifyId); //取消指定ID的通知
mNotificationManager.cancelAll(); //取消所有通知

NotificationManagerService

android-9.0.0_r3

gityuan.com 通知处理流程图

Notifications 通知

gityuan.com 核心类图

Notifications 通知

通知发送原理分析

1 NM.notify

[-> 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());
}

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {
    //获取通知的代理对象
    INotificationManager service = getService();
    String pkg = mContext.getPackageName();
    //将包名和userId保存到通知的extras
    Notification.addFieldsFromContext(mContext, notification);
    
    if (notification.sound != null) {
        notification.sound = notification.sound.getCanonicalUri();
        if (StrictMode.vmFileUriExposureEnabled()) {
            notification.sound.checkFileUriExposed("Notification.sound");
        }

    }
    fixLegacySmallIcon(notification, pkg);
    //对于Android 5.0之后的版本,smallIcon不可为空
    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);
    final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
    try {
        //binder调用,进入system_server进程【2.2】
        service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                copy, user.getIdentifier());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

在App端调用NotificationManager类的notify()方法,最终通过binder调用,会进入system_server进程的 NotificationManagerService(简称NMS),执行enqueueNotificationWithTag()方法,走到enqueueNotificationInternal()。

2 NMS.enqueueNotificationInternal

[-> NotificationManagerService.java]

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,否则抛出异常
    checkCallerIsSystemOrSameApp(pkg); 
    
    ...
    //从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示
    String channelId = notification.getChannelId();
    if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
        channelId = (new Notification.TvExtender(notification)).getChannelId();
    }
    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
    final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
    ...
    //检查是否可以发布通知:
    //1、除了系统通知和已注册的监听器允许入队列,其他通知都会限制数量上限,默认是一个package上限MAX_PACKAGE_NOTIFICATIONS=50个、入队间隔MIN_PACKAGE_OVERRATE_LOG_INTERVAL=5000
    //2、snoozed apps
    //3、blocked apps
    if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
            r.sbn.getOverrideGroupKey() != null)) {
        return;
    }
    ...
    //将通知异步发送到handler线程【见小节2.3】
    mHandler.post(new EnqueueNotificationRunnable(userId, r));
}

这个过程主要功能:

  • 从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示
  • 创建NotificationRecord对象,里面包含了notification相关信息
  • 采用异步方式,将任务交给mHandler线程来处理,mHandler是WorkerHandler类的实例对象
    接下来看看WorkerHandler到底运行在哪个线程,这需要从NMS服务初始化过程来说起:

2.1 SS.startOtherServices

[-> SystemServer.java]

private void startOtherServices() {
    ...
    //【见小节2.2】
    traceBeginAndSlog("StartNotificationManager");
    mSystemServiceManager.startService(NotificationManagerService.class);
    SystemNotificationChannels.createAll(context);
    notification = INotificationManager.Stub.asInterface(
            ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    traceEnd();
    ...
}

该过程运行在system_server进程的主线程。

2.2 SSM.startService

[-> SystemServiceManager.java]

public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        final String name = serviceClass.getName();
        Slog.i(TAG, "Starting " + name);
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);

        // Create the service.
        if (!SystemService.class.isAssignableFrom(serviceClass)) {
            throw new RuntimeException(...);
        }
        final T service;
        try {
            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
            //创建NotificationManagerService对象
            service = constructor.newInstance(mContext);
        } catch (InstantiationException ex) {
            ...
        }

        //注册该服务,并调用NMS的onStart方法,【见小节2.3】
        startService(service);
        return service;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
    }
}

该过程先创建NotificationManagerService(简称NMS),然后再调用其onStart方法。

2.3 NMS.onStart

[-> NMS.java]

public void onStart() {
    ...
    init(Looper.myLooper(),
            AppGlobals.getPackageManager(), getContext().getPackageManager(),
            getLocalService(LightsManager.class),
            new NotificationListeners(AppGlobals.getPackageManager()),
            new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
                    AppGlobals.getPackageManager()),
            new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
            null, snoozeHelper, new NotificationUsageStats(getContext()),
            new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
            (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
            getGroupHelper(), ActivityManager.getService(),
            LocalServices.getService(UsageStatsManagerInternal.class),
            LocalServices.getService(DevicePolicyManagerInternal.class));
    ...
    publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
            DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
    publishLocalService(NotificationManagerInternal.class, mInternalService);
}
void init(Looper looper, IPackageManager packageManager,
        PackageManager packageManagerClient,
        LightsManager lightsManager, NotificationListeners notificationListeners,
        NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
        ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
        NotificationUsageStats usageStats, AtomicFile policyFile,
        ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
        UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm) {
    ...
    mHandler = new WorkerHandler(looper); //运行在system_server的主线程
    mRankingThread.start(); //线程名为"ranker"的handler线程
    ...
    mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
    ...
    //用于记录所有的listeners的MangedServices对象
    mListeners = notificationListeners;

onStart()过程创建的mHandler运行在system_server的主线程。那么上面的执行流便进入了 system_server 主线程。

3 EnqueueNotificationRunnable\PostNotificationRunnable

[-> NMS.java]

private class EnqueueNotificationRunnable implements Runnable {
    private final NotificationRecord r;
    private final int userId;

    EnqueueNotificationRunnable(int userId, NotificationRecord r) {
        this.userId = userId;
        this.r = r;
    };
    
    @Override
    public void run() {
        synchronized (mNotificationLock) {
            ...
            final StatusBarNotification n = r.sbn;
            ...
            // tell the assistant service about the notification
            if (mAssistants.isEnabled()) {
                mAssistants.onNotificationEnqueued(r);
                mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                        DELAY_FOR_ASSISTANT_TIME);
            } else {
                mHandler.post(new PostNotificationRunnable(r.getKey()));
            }
        }
    }
}
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;
                    }
                }
                if (r == null) {
                    Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                    return;
                }

                r.setHidden(isPackageSuspendedLocked(r));
                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);
                    // 确保通知的前台服务属性不会被丢弃
                    notification.flags |=
                            old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                    r.isUpdate = true;
                    r.setTextChanged(isVisuallyInterruptive(old, r));
                }

                mNotificationsByKey.put(n.getKey(), r);

                // 如果是前台服务的通知,则添加不允许被清除和正在运行的标签
                if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                    notification.flags |= Notification.FLAG_ONGOING_EVENT
                            | Notification.FLAG_NO_CLEAR;
                }

                applyZenModeLocked(r);
                mRankingHelper.sort(mNotificationList);

                //当设置小图标,则通知NotificationListeners处理 【2.4】
                if (notification.getSmallIcon() != null) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(r, old);
                    ...
                } else {
                    Slog.e(TAG, "Not posting notification without small icon: " + notification);
                    if (old != null && !old.isCanceled) {
                        mListeners.notifyRemovedLocked(r,
                                NotificationListenerService.REASON_ERROR, null);
                        ...
                    }
                    ...
                }
                //r.isHidden()由于应用pkg已暂停,此通知是否被隐藏?
                if (!r.isHidden()) {
                    //处理该通知,主要是是否发声,震动,Led
                    buzzBeepBlinkLocked(r);
                }
                maybeRecordInterruptionLocked(r);
            } finally {
                ...
            }
        }
    }
}

这里的mListeners是指NotificationListeners对象

4 NotificationListeners.notifyPostedLocked

[-> NMS.java]

public class NotificationListeners extends ManagedServices {
    ...
    public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
        notifyPostedLocked(r, old, true);
    }
    ...
    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);
        // 遍历整个ManagedServices中的所有ManagedServiceInfo
        for (final ManagedServiceInfo info : getServices()) {
            boolean sbnVisible = isVisibleToListener(sbn, info);
            boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
            // 此通知尚未出现,仍然不可见,则忽略。
            if (!oldSbnVisible && !sbnVisible) {
                continue;
            }

            // 如果通知是隐藏的,则不要通知目标已发布的侦听器。(< P)
            // 相反,当取消隐藏通知时,这些侦听器将收到notifyPosted。
            if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
                continue;
            }

            // 如果我们不通知所有侦听器,则意味着通知的隐藏状态已更改。 不要通知已发布的侦听器定位。( >= P)
            // 相反,这些侦听器将收到notifyRankingUpdate。
            if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
                continue;
            }

            final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
            
            // 通知变得不可见,则移除老的通知
            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;
            }

            // Grant access before listener is notified
            final int targetUserId = (info.userid == UserHandle.USER_ALL)
                    ? UserHandle.USER_SYSTEM : info.userid;
            updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);

            final StatusBarNotification sbnToPost = trimCache.ForListener(info);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    notifyPosted(info, sbnToPost, update); //【见小节5】
                }
            });
        }
    }
    ...
}

5 NotificationListeners.notifyPosted

private void notifyPosted(final ManagedServiceInfo info,
        final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    final INotificationListener listener = (INotificationListener) info.service;
    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
    try {
        // 【见小节6】
        listener.onNotificationPosted(sbnHolder, rankingUpdate);
    } catch (RemoteException ex) {
        Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
    }
}

此处的 listener 来自于ManagedServiceInfo的service成员变量,listener 数据类型是NotificationListenerWrapper的代理对象,详见第三大节。 此处 sbnHolder 的数据类型为StatusBarNotificationHolder,继承于IStatusBarNotificationHolder.Stub对象,经过binder调用进入到 systemui 进程的 便是IStatusBarNotificationHolder.Stub.Proxy对象。

6 NotificationListenerWrapper.onNotificationPosted

[-> NotificationListenerService.java]

protected class NotificationListenerWrapper extends INotificationListener.Stub {
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
            NotificationRankingUpdate update) {
        StatusBarNotification sbn;
        try {
            sbn = sbnHolder.get(); //向 system_server 进程来获取sbn对象
        } catch (RemoteException e) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
            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;
                //【见小节7】
                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();
            }
        }

    }
    ...
}

此时运行在systemui进程,sbnHolder 是IStatusBarNotificationHolder的代理端。 此处mHandler = new MyHandler(getMainLooper()),也就是运行在systemui主线程的handler

7 MyHandler

[-> NotificationListenerService.java]

private final class MyHandler extends Handler {
    ...
    public void handleMessage(Message msg) {
        ...
        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;
    		...
        }
    }
}

此处调用NotificationListenerService实例对象的onNotificationPosted()

8 NLS.onNotificationPosted

[SystemUI -> StatusBar.java -> NotificationListener.java]

public class NotificationListener extends NotificationListenerWithPlugins {
    ...
    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 {
                    //【9】
                    mEntryManager.addNotification(sbn, rankingMap);
                }
            });
        }
    }
    ...
}

9 addNotification

[SystemUI -> NotificationEntryManager.java -> ForegroundServiceController.java]

public void addNotification(StatusBarNotification notification,
        NotificationListenerService.RankingMap ranking) {
    try {
        addNotificationInternal(notification, ranking);
    } catch (InflationException e) {
        handleInflationException(notification, e);
    }
}
private void addNotificationInternal(StatusBarNotification notification,
        NotificationListenerService.RankingMap ranking) throws InflationException {
    String key = notification.getKey();
    if (DEBUG) Log.d(TAG, "addNotification key=" + key);

    mNotificationData.updateRanking(ranking); //更新排序
    //创建通知视图【9.1】
    NotificationData.Entry shadeEntry = createNotificationViews(notification);
    boolean isHeadsUped = shouldPeek(shadeEntry);
    if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
        if (shouldSuppressFullScreenIntent(shadeEntry)) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
            }
        } else if (mNotificationData.getImportance(key)
                < NotificationManager.IMPORTANCE_HIGH) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: not important enough: "
                        + key);
            }
        } else {
            // Stop screensaver if the notification has a fullscreen intent.
            // (like an incoming phone call)
            SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();

            // not immersive & a fullscreen alert should be shown
            if (DEBUG)
                Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
            try {
                EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                        key);
                notification.getNotification().fullScreenIntent.send();
                shadeEntry.notifyFullScreenIntentLaunched();
                mMetricsLogger.count("note_fullscreen", 1);
            } catch (PendingIntent.CanceledException e) {
            }
        }
    }
    abortExistingInflation(key);

    mForegroundServiceController.addNotification(notification,
            mNotificationData.getImportance(key));

    mPendingNotifications.put(key, shadeEntry);
    mGroupManager.onPendingEntryAdded(shadeEntry);
}

[ForegroundServiceControllerImpl.java]

public class ForegroundServiceControllerImpl
        implements ForegroundServiceController {
    ...
    public void addNotification(StatusBarNotification sbn, int importance) {
        updateNotification(sbn, importance);
    }
    ...
    public void updateNotification(StatusBarNotification sbn, int newImportance) {
        synchronized (mMutex) {
            UserServices userServices = mUserServices.get(sbn.getUserId());
            if (userServices == null) {
                userServices = new UserServices();
                mUserServices.put(sbn.getUserId(), userServices);
            }

            if (isDungeonNotification(sbn)) {
                final Bundle extras = sbn.getNotification().extras;
                if (extras != null) {
                    final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
                    userServices.setRunningServices(svcs, sbn.getNotification().when);
                }
            } else {
                userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
                if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) {
                    if (newImportance > NotificationManager.IMPORTANCE_MIN) {
                        userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey());
                    }
                    final Notification.Builder builder = Notification.Builder.recoverBuilder(
                            mContext, sbn.getNotification());
                    if (builder.usesStandardHeader()) {
                        userServices.addStandardLayoutNotification(
                                sbn.getPackageName(), sbn.getKey());
                    }
                }
            }
        }
    }
    ...
    /**
     * Struct to track relevant packages and notifications for a userid's foreground services.
     */
    private static class UserServices {
        ...
        public void addImportantNotification(String pkg, String key) {
            addNotification(mImportantNotifications, pkg, key);
        }
        ...
        public void addStandardLayoutNotification(String pkg, String key) {
            addNotification(mStandardLayoutNotifications, pkg, key);
        }
        ...
        public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
                String key) {
            if (map.get(pkg) == null) {
                map.put(pkg, new ArraySet<>());
            }
            map.get(pkg).add(key);
        }
        ...
    }
}
  • 9.1 createNotificationViews

[SystemUI -> NotificationEntryManager.java]

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
        throws InflationException {
    if (DEBUG) {
        Log.d(TAG, "createNotificationViews(notification=" + sbn);
    }
    NotificationData.Entry entry = new NotificationData.Entry(sbn);
    Dependency.get(LeakDetector.class).trackInstance(entry);
    entry.createIcons(mContext, sbn);
    // Construct the expanded view.
    inflateViews(entry, mListContainer.getViewParentForNotification(entry));
    return entry;
}
  •  

SystemUI

1 startOtherServices

[-> SystemServer.java]

private void startOtherServices() {
    ...
    traceBeginAndSlog("StartSystemUI");
    try {
        startSystemUi(context, windowManagerF);
    } catch (Throwable e) {
        reportWtf("starting System UI", e);
    }
    traceEnd();
    ...
}
  •  

2 startSystemUi

[-> SystemServer.java]

static final void startSystemUi(Context context, WindowManagerService windowManager) {
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.android.systemui",
                "com.android.systemui.SystemUIService"));
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    //【见3】
    context.startServiceAsUser(intent, UserHandle.SYSTEM);
    windowManager.onSystemUiStarted();
}

启动服务SystemUIService,运行在进程com.android.systemui,接下来进入systemui进程

3 SystemUIService

[-> SystemUIService.java]

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        //【见小节4】
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
        ...
    }
    ...
}
  •  

服务启动后,先执行其onCreate()方法

4 startServicesIfNeeded

[-> SystemUIApplication.java]

public void startServicesIfNeeded() {
    String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
    startServicesIfNeeded(names);
}
  •  

[-> ./res/values/config.xml]

    <!-- SystemUI Services: The classes of the stuff to start. -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.Dependency</item>
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.SystemBars</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
    </string-array>
  • 此处以SystemBars为例来展开

5 startServicesIfNeeded

[-> SystemUIApplication.java]

private void startServicesIfNeeded(String[] services) {
    if (mServicesStarted) {
        return;
    }
    mServices = new SystemUI[services.length];

    if (!mBootCompleted) {
        // check to see if maybe it was already completed long before we began
        // see ActivityManagerService.finishBooting()
        if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
            mBootCompleted = true;
            if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
        }
    }

    Log.v(TAG, "Starting SystemUI services for user " +
            Process.myUserHandle().getIdentifier() + ".");
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
            Trace.TRACE_TAG_APP);
    log.traceBegin("StartServices");
    final int N = services.length;
    for (int i = 0; i < N; i++) {
        String clsName = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + clsName);
        log.traceBegin("StartServices" + clsName);
        long ti = System.currentTimeMillis();
        Class cls;
        try {
            //初始化对象
            cls = Class.forName(clsName);
            mServices[i] = (SystemUI) cls.newInstance();
        } catch(ClassNotFoundException ex){
            throw new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        }
        ...
        //【见小节6】
        mServices[i].start();
        log.traceEnd();

        // Warn if initialization of component takes too long
        ti = System.currentTimeMillis() - ti;
        if (ti > 1000) {
            Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
        }
        if (mBootCompleted) {
            mServices[i].onBootCompleted();
        }
    }
    log.traceEnd();
    ...
    mServicesStarted = true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 3

6 SystemBars.start

[-> SystemBars.java]

public class SystemBars extends SystemUI {
    ...
    @Override
    public void start() {
        if (DEBUG) Log.d(TAG, "start");
        createStatusBarFromConfig();
    }
    ...
    private void createStatusBarFromConfig() {
        if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
        //config_statusBarComponent 是指 phone.StatusBar
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
        if (clsName == null || clsName.length() == 0) {
            throw andLog("No status bar component configured", null);
        }
        Class<?> cls = null;
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            mStatusBar = (SystemUI) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        //【见小节7】
        mStatusBar.start();
        if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
    }
    ...
}
  •  

config_statusBarComponent的定义位于文件config.xml中,其值为PhoneStatusBar。

7 StatusBar

[-> StatusBar.java]

public void start() {
    ...
    mNotificationListener =  Dependency.get(NotificationListener.class);
    ...
    createAndAddWindows(); //添加状态栏
    ...
    //【见小节8】
    mNotificationListener.setUpWithPresenter(this, mEntryManager);
    ...
}
  •  

8 NotificationListener

[-> NotificationListener.java]

public void setUpWithPresenter(NotificationPresenter presenter,
        NotificationEntryManager entryManager) {
    mPresenter = presenter;
    mEntryManager = entryManager;

    try {
        //安装通知的初始化状态【9】
        registerAsSystemService(mContext,
                new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                UserHandle.USER_ALL);
    } catch (RemoteException e) {
        Log.e(TAG, "Unable to register notification listener", e);
    }
}
  •  

9 NLS.registerAsSystemService

[-> NotificationListenerService.java]

public void registerAsSystemService(Context context, ComponentName componentName,
        int currentUser) throws RemoteException {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    mSystemContext = context;
    //获取NMS的接口代理对象
    INotificationManager noMan = getNotificationInterface();
    //运行在主线程的handler
    mHandler = new MyHandler(context.getMainLooper());
    mCurrentUser = currentUser;
    //经过binder调用,向system_server中的NMS注册监听器【10】
    noMan.registerListener(mWrapper, componentName, currentUser);
}
  •  

经过binder调用,向system_server中的NMS注册监听器

10 registerListener

[-> NMS.java]

private final IBinder mService = new INotificationManager.Stub() {{
    ...
    public void registerListener(final INotificationListener listener,
            final ComponentName component, final int userid) {
        enforceSystemOrSystemUI("INotificationManager.registerListener");
        //此处的INotificationListener便是NotificationListenerWrapper代理对象 【10.1】
        mListeners.registerService(listener, component, userid);
    }
    ...
}
  •  

mListeners的对象类型为ManagedServices。此处的INotificationListener便是NotificationListenerWrapper的代理对象

10.1 registerService

[-> ManagedServices.java]

public void registerService(IInterface service, ComponentName component, int userid) {
    checkNotNull(service);
    //【10.2】
    ManagedServiceInfo info = registerServiceImpl(service, component, userid);
    if (info != null) {
        onServiceAdded(info);
    }
}
  •  

10.2 registerService

[-> ManagedServices.java]

private ManagedServiceInfo registerServiceImpl(final IInterface service,
        final ComponentName component, final int userid) {
    //将NotificationListenerWrapper对象保存到ManagedServiceInfo.service
    ManagedServiceInfo info = newServiceInfo(service, component, userid,
            true /*isSystem*/, null /*connection*/, Build.VERSION_CODES.LOLLIPOP);
    //【10.3】
    return registerServiceImpl(info);
}

11.3 registerServiceImpl

[-> ManagedServices.java]

private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
    synchronized (mMutex) {
        try {
            info.service.asBinder().linkToDeath(info, 0);
            mServices.add(info);
            return info;
        } catch (RemoteException e) {
            // already dead
        }
    }
    return null;
}

可见,前面的 listener 的对端便是运行在 systemui 中的NotificationListenerWrapper的代理对象。

参考文献

Google 通知概览
Gityuan: NotificationManagerService原理分析
Android 7.0 NotificationManagerService源码分析(应用层App,Fragmework中Service层,SystemUI系统App)

上一篇:FreeRTOS --(17)任务通知浅析


下一篇:[Flutter]NotificationListener处理滑动事件分发