网站建设开发心得,wordpress初始设置密码,网站优化排名公司,安阳网站建设前言
应用发送一个显示在状态栏上的通知#xff0c;对于移动设备来说是很常见的一种功能需求#xff0c;本篇文章我们将会结合Android9.0系统源码具体来分析一下#xff0c;应用调用notificationManager触发通知栏通知功能的源码流程。
一、应用触发状态栏通知
应用可以通…前言
应用发送一个显示在状态栏上的通知对于移动设备来说是很常见的一种功能需求本篇文章我们将会结合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#builderNotificationCompat.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#buildercreateNotificationChannel();// 3. Set the notifications 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#notifyNotificationManagerCompat notificationManager NotificationManagerCompat.from(this);// 5.调用notificationManager的notify方法// notificationId is a unique int for each notification that you must definenotificationManager.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 libraryCharSequence 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 cant change the importance// or other notification behaviors after thisNotificationManager 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 {Overridepublic 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);// 校验UIDfinal 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;};Overridepublic 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());//获取是否存在相同的NotificationRecordNotificationRecord old mNotificationsByKey.get(n.getKey());if (old ! null) {// Retain ranking information from previous recordr.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 summaryif (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 notificationif (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;}Overridepublic void run() {synchronized (mNotificationLock) {try {NotificationRecord r null;//从类型为ArrayListNotificationRecord的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();// 判断是否是已经发送过此notificationint 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 dont 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() {Overridepublic 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() {Overridepublic 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// notificationsSlog.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// eventif (!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 nowif (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);}}
}四、总结