当前位置: 首页 > news >正文

Android 9.0系统源码_Notification(一)应用发送状态栏通知的流程

前言

应用发送一个显示在状态栏上的通知,对于移动设备来说是很常见的一种功能需求,本篇文章我们将会结合Android9.0系统源码具体来分析一下,应用调用notificationManager触发通知栏通知的功能的源码流程。

一、应用触发状态栏通知

应用可以通过调用如下notity方法可以发送一个显示在状态栏上的通知。

    /**
     * 发送通知(支持8.0+)
     */
    public void nofify() {
        // 1. Set the notification content - 创建通知基本内容
        // https://developer.android.google.cn/training/notify-user/build-notification.html#builder
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("My notification")
                // 这是单行
                //.setContentText("Much longer text that cannot fit one line...")
                // 这是多行
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText("Much longer text that cannot fit one line..." +
                                "Much longer text that cannot fit one line..." +
                                "Much longer text that cannot fit one line..."))
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true);

        // 2. Create a channel and set the importance - 8.0后需要设置Channel
        // https://developer.android.google.cn/training/notify-user/build-notification.html#builder
        createNotificationChannel();
        // 3. Set the notification's tap action - 创建一些点击事件,比如点击跳转页面
        // https://developer.android.google.cn/training/notify-user/build-notification.html#click
        
        // 4. Show the notification - 展示通知
        // https://developer.android.google.cn/training/notify-user/build-notification.html#notify
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

		// 5.调用notificationManager的notify方法
        // notificationId is a unique int for each notification that you must define
        notificationManager.notify((int) System.currentTimeMillis(), builder.build());
    }

    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        CharSequence name = getString(R.string.app_name);
        String description = getString(R.string.app_name);
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
        channel.setDescription(description);
        // Register the channel with the system; you can't change the importance
        // or other notification behaviors after this
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }

notify方法先是构建Notification对象,然后调用NotificationManager的notify方法发送构建的这个对象。

二、NotificationManager的相关源码

1、NotificationManager的notify方法如下所示:

frameworks/base/core/java/android/app/NotificationManager.java

public class NotificationManager {

  //如果应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。
  public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }
 
   //应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。   
    public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, mContext.getUser());
    }
}

2、notify方法最终都会进一步调用notifyAsUser。

public class NotificationManager {
    /**
     * @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);
        ...代码省略...
    }
}

notifyAsUser方法首先获取NotificationManagerService服务,然后调用了Notification的addFieldsFromContext方法。

3、Notification的addFieldsFromContext方法如下所示:

frameworks/base/core/java/android/app/Notification.java

public class Notification implements Parcelable
{
    public Bundle extras = new Bundle();
    /**
     * @hide
     */
    public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
    /**
     * @hide
     */
    public static void addFieldsFromContext(Context context, Notification notification) {
        addFieldsFromContext(context.getApplicationInfo(), notification);
    }
    /**
     * @hide
     */
    public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
    }
}

主要是在Notification对象中类型为Bundle的属性变量extras中保存了当前应用所对应的ApplicationInfo对象。

4、继续往下看NotificationManager的notifyAsUser方法

public class NotificationManager {
    /**
     * @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);
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        //通过包名获取对应包名的应用图标设置为通知的小图标
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
        	//Android5.1之后,会判断notification是否有small icon,没有则抛出异常
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
    	...代码省略...
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

会调用fixLegacySmallIcon方法通,过包名获取对应包名的应用图标设置为通知的小图标,在Android5.1之后的版本中,然后会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常,最终会调用NotificationManagerService的enqueueNotificationWithTag方法。

三、NotificationManagerService和发送通知相关的源码

1、NotificationManagerService的enqueueNotificationWithTag方法如下所示。

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

public class NotificationManagerService extends SystemService {
        @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);
        }
}

2、enqueueNotificationWithTag方法会继续调用了enqueueNotificationInternal方法,该方法发送通知的核心。

public class NotificationManagerService extends SystemService {

    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) {
        if (DBG) {
            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                    + " notification=" + notification);
        }
        checkCallerIsSystemOrSameApp(pkg);
        // 校验UID
        final int userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
        final UserHandle user = new UserHandle(userId);

        if (pkg == null || notification == null) {
            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                    + " id=" + id + " notification=" + notification);
        }

        // The system can post notifications for any package, let us resolve that.
        final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);

      	...代码省略...
      	
        //上面会进行一系列验证,验证之后,会将传递进来的Notification封装成一个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);

      	...代码省略...

        mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }
}

enqueueNotificationInternal会进行一些列验证,待验证完成之后,会调用Handler的post方法开启线程,发起异步操作,触发EnqueueNotificationRunnable对象。

3、EnqueueNotificationRunnable对象如下所示。

public class NotificationManagerService extends SystemService {
    protected 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) {
                //将当前通知相关的NotificationRecord对象放到集合中
                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();

                // Handle grouped notifications and bail out early if we
                // can to avoid extracting signals.
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);

                // if this is a group child, unsnooze parent summary
                if (n.isGroup() && notification.isGroupChild()) {
                    mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
                }

                // 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;
                    }
                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                            pkg, id, tag, userId, notification.toString(),
                            enqueueStatus);
                }

                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 {
                    //开启线程
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

}    

NotificationManagerService 的run方法会将当前NotificationRecord存放到类型为ArrayList的mEnqueuedNotifications集合中,最终会再次调用Handler的post方法开启线程,发起异步操作,触发PostNotificationRunnable对象。

4、PostNotificationRunnable对象如下所示。

    protected class PostNotificationRunnable implements Runnable {
        private final String key;

        PostNotificationRunnable(String key) {
            this.key = key;
        }

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                try {
                    NotificationRecord r = null;
                    //从类型为ArrayList<NotificationRecord>的mEnqueuedNotifications集合中
                    //取当前key所对应的NotificationRecord对象
                    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();
                    // 判断是否是已经发送过此notification
                    int index = indexOfNotificationLocked(n.getKey());
                    if (index < 0) {
                        //如果是新发送的notification,就走新增流程.
                        mNotificationList.add(r);
                        mUsageStats.registerPostedByApp(r);
                        r.setInterruptive(isVisuallyInterruptive(null, r));
                    } else {
                        //如果有发送过,就获取已经存在的NtificationRecord,
                        // 后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n)
                        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));
                    }
                    //将当前NotificationRecord对象以StatusBarNotification为键存放到mNotificationsByKey中
                    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) {
                        // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法,
                        // 通知有新的notification,传入的参数为上面的NotificationRecord对象
                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                        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 {
                        //移除已经存在的
                        Slog.e(TAG, "Not posting notification without small icon: " + notification);
                        if (old != null && !old.isCanceled) {
                            mListeners.notifyRemovedLocked(r,
                                    NotificationListenerService.REASON_ERROR, null);
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationRemoved(n);
                                }
                            });
                        }
                        // ATTENTION: in a future release we will bail out here
                        // so that we do not play sounds, show lights, etc. for invalid
                        // notifications
                        Slog.e(TAG, "WARNING: In a future release this will crash the app: "
                                + n.getPackageName());
                    }

                    if (!r.isHidden()) {
                        //如果不是隐藏,则触发振动/铃声/呼吸灯
                        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;
                        }
                    }
                }
            }
        }
    }

相关文章:

  • 【吉先生的Java全栈之路】
  • 无需登录复制网站文字的解决方案
  • ELK分布式日志收集快速入门-(二)kafka进阶-快速安装可视化管理界面-(单节点部署)
  • 超详细讲解线性表和顺序表!!
  • QT入门Input Widgets之QFontComboBox、QTextEdit、QPlainTextEdit、QDial、QKeySequenceEdit
  • 介绍一款HCIA、HCIP、HCIE的刷题软件
  • 别在用scroll去做懒加载了,交叉观察器轻松搞定
  • 【C++】C++入门
  • 牛客网Python篇数据分析习题(五)
  • greenDao的使用文档
  • ubuntu 安装支持GPU的Docker详细步骤
  • 考研复试机试 | C++
  • 结构体熟练掌握--实现通讯录
  • IDEA配置部署tomcat详细步骤(maven web 和Javaweb)
  • 软件测试面试准备——(一)Selenium(1)基础问题及自动化测试
  • 【Unity3D】Shader常量、变量、结构体、函数
  • Android 逆向工具大整理,碉堡了
  • 【java】springboot和springcloud区别
  • Linux(Linux各目录结构详解)
  • 二进制 k8s 集群下线 master 组件流程分析和实践
  • 大理杨徐邱再审后上诉案将于下周开庭:案发已逾32年,故意杀人罪去年被撤销
  • 能源央企资产重组大提速,专业化整合掀起新热潮
  • 广西旱情如何?农业厅:近半数农田墒情不足至干旱,本月降雨将渐增
  • 江西修水警方:一民房内发生刑案,犯罪嫌疑人已被抓获
  • 农文旅项目投资1700万后被告知是禁养区?南京浦口通报
  • 上海不重视民企?专家:此次26项措施消除了误会,信心比黄金重要