前言
应用发送一个显示在状态栏上的通知,对于移动设备来说是很常见的一种功能需求,本篇文章我们将会结合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对象如下所示。
public class NotificationManagerService extends SystemService {
private NotificationListeners mListeners;
private GroupHelper mGroupHelper;
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;
}
}
}
}
}
}
}
5、如果验证通过,还会调用buzzBeepBlinkLocked方法触发震动、铃声、呼吸灯
public class NotificationManagerService extends SystemService {
//触发振动/铃声/呼吸灯
void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false;//震动
boolean beep = false;//铃声
boolean blink = false;//呼吸灯
final Notification notification = record.sbn.getNotification();
final String key = record.getKey();
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold =
record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
// Remember if this notification already owns the notification channels.
boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
// These are set inside the conditional if the notification is allowed to make noise.
boolean hasValidVibrate = false;
boolean hasValidSound = false;
boolean sentAccessibilityEvent = false;
// If the notification will appear in the status bar, it should send an accessibility
// event
if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
sendAccessibilityEvent(notification, record.sbn.getPackageName());
sentAccessibilityEvent = true;
}
if (aboveThreshold && isNotificationForCurrentUser(record)) {
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
long[] vibration = record.getVibration();
// Demote sound to vibration if vibration missing & phone in vibration mode.
if (vibration == null
&& hasValidSound
&& (mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_VIBRATE)
&& mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
vibration = mFallbackVibrationPattern;
}
hasValidVibrate = vibration != null;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
if (!sentAccessibilityEvent) {
sendAccessibilityEvent(notification, record.sbn.getPackageName());
sentAccessibilityEvent = true;
}
if (DBG) Slog.v(TAG, "Interrupting!");
if (hasValidSound) {
mSoundNotificationKey = key;
if (mInCall) {
playInCallNotification();
beep = true;
} else {
//播放铃声
beep = playSound(record, soundUri);
}
}
final boolean ringerModeSilent =
mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT;
if (!mInCall && hasValidVibrate && !ringerModeSilent) {
mVibrateNotificationKey = key;
//震动
buzz = playVibration(record, vibration, hasValidSound);
}
}
}
}
// If a notification is updated to remove the actively playing sound or vibrate,
// cancel that feedback now
if (wasBeep && !hasValidSound) {
clearSoundLocked();
}
if (wasBuzz && !hasValidVibrate) {
clearVibrateLocked();
}
// light
// release the light
// 呼吸灯
boolean wasShowLights = mLights.remove(key);
if (record.getLight() != null && aboveThreshold
&& ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) == 0)) {
mLights.add(key);
updateLightsLocked();
if (mUseAttentionLight) {
mAttentionLight.pulse();
}
blink = true;
} else if (wasShowLights) {
updateLightsLocked();
}
//应用请求了振动/铃声/呼吸灯,输出notification_alert日志
if (buzz || beep || blink) {
record.setInterruptive(true);
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)));
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
}
}
}