Android通知服务及相关概念
本文基于Android 14源码
1 NotificationManagerService的启动
1.1 添加服务
和其他系统服务一样,NotificationManagerService也是在SystemServer中启动的。
//framework/base/services/java/com/android/server/SystemServer.java
private void run() {t.traceBegin("StartServices");startBootstrapServices(t);startCoreServices(t);startOtherServices(t);startApexServices(t);
}private void startOtherServices(@NonNull TimingsTraceAndSlog t) {t.traceBegin("StartNotificationManager");mSystemServiceManager.startService(NotificationManagerService.class);SystemNotificationChannels.removeDeprecated(context);SystemNotificationChannels.createAll(context);notification = INotificationManager.Stub.asInterface(ServiceManager.getService(Context.NOTIFICATION_SERVICE));t.traceEnd();
}
NotificationManagerService是在startOtherServices中启动的,调用SystemServiceManager的startService之后,SystemServiceManager会通过反射创建NotificationManagerService实例对象,然后调用它的onStart()来启动服务。
//framework/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void onStart() {SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {try {if (DBG) {Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn);}enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn,false /* byForegroundService */);} catch (Exception e) {Slog.e(TAG, "Cannot un-snooze notification", e);}}, mUserProfiles);final File systemDir = new File(Environment.getDataDirectory(), "system");mRankingThread.start();WorkerHandler handler = new WorkerHandler(Looper.myLooper());mShowReviewPermissionsNotification = getContext().getResources().getBoolean(R.bool.config_notificationReviewPermissions);init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),AppGlobals.getPackageManager(), getContext().getPackageManager(),getLocalService(LightsManager.class),new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,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(ActivityTaskManagerInternal.class),LocalServices.getService(UsageStatsManagerInternal.class),LocalServices.getService(DevicePolicyManagerInternal.class),UriGrantsManager.getService(),LocalServices.getService(UriGrantsManagerInternal.class),getContext().getSystemService(AppOpsManager.class),getContext().getSystemService(UserManager.class),new NotificationHistoryManager(getContext(), handler),mStatsManager = (StatsManager) getContext().getSystemService(Context.STATS_MANAGER),getContext().getSystemService(TelephonyManager.class),LocalServices.getService(ActivityManagerInternal.class),createToastRateLimiter(), new PermissionHelper(getContext(),AppGlobals.getPackageManager(),AppGlobals.getPermissionManager()),LocalServices.getService(UsageStatsManagerInternal.class),getContext().getSystemService(TelecomManager.class),new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),getContext().getSystemService(PermissionManager.class),getContext().getSystemService(PowerManager.class),new PostNotificationTrackerFactory() {});publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);publishLocalService(NotificationManagerInternal.class, mInternalService);}
1.2 加载policy
在NotificationManagerService的init里面会指定notification policy保存路径,也就是/data/system/notification_policy.xml
// Persistent storage for notification policy
private AtomicFile mPolicyFile;
new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy")
然后将其保存到mPolicyFile,接着是加载policy文件。
mPolicyFile = policyFile;
loadPolicyFile();
来看loadPolicyFile
protected void loadPolicyFile() {if (DBG) Slog.d(TAG, "loadPolicyFile");synchronized (mPolicyFile) {InputStream infile = null;try {infile = mPolicyFile.openRead();readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);} catch (FileNotFoundException e) {// No data yet// Load default managed services approvals//第一次的话文件没找到,加载默认的允许的管理服务loadDefaultApprovedServices(USER_SYSTEM); //末尾会保存文件,之后就不会走到这个异常里了allowDefaultApprovedServices(USER_SYSTEM);} catch (IOException e) {Log.wtf(TAG, "Unable to read notification policy", e);} catch (NumberFormatException e) {Log.wtf(TAG, "Unable to parse notification policy", e);} catch (XmlPullParserException e) {Log.wtf(TAG, "Unable to parse notification policy", e);} finally {IoUtils.closeQuietly(infile);}}
}
loadPolicyFile主要工作是在readPolicyXml来解析xml。
不过,首次加载会走catch方法里,第一次文件肯定不存在,catch里会加载默认数据。
先来看loadDefaultApprovedServices:
void loadDefaultApprovedServices(int userId) {mListeners.loadDefaultsFromConfig();mConditionProviders.loadDefaultsFromConfig();mAssistants.loadDefaultsFromConfig();
}
loadDefaultApprovedServices主要是加载一些默认的配置。
再来看allowDefaultApprovedServices。
protected void allowDefaultApprovedServices(int userId) {ArraySet<ComponentName> defaultListeners = mListeners.getDefaultComponents();for (int i = 0; i < defaultListeners.size(); i++) {ComponentName cn = defaultListeners.valueAt(i);allowNotificationListener(userId, cn);}ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();for (int i = 0; i < defaultDnds.size(); i++) {allowDndPackage(userId, defaultDnds.valueAt(i));}setDefaultAssistantForUser(userId);
}
defaultListeners是从config_defaultListenerAccessPackages中获取的
String defaultListenerAccess = mContext.getResources().getString(R.string.config_defaultListenerAccessPackages);
不过,源码里面这个值是空的
<!-- Colon separated list of package names that should be granted Notification Listener access -->
<string name="config_defaultListenerAccessPackages" translatable="false"></string>
但是在GMS里有配置
<string name="config_defaultListenerAccessPackages" translatable="false">com.android.launcher3:com.google.android.projection.gearhead</string>
再来看readPolicyXml
void readPolicyXml(InputStream stream, boolean forRestore, int userId)throws XmlPullParserException, NumberFormatException, IOException {final TypedXmlPullParser parser;if (forRestore) {parser = Xml.newFastPullParser();parser.setInput(stream, StandardCharsets.UTF_8.name());} else {parser = Xml.resolvePullParser(stream);}XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);boolean migratedManagedServices = false;UserInfo userInfo = mUmInternal.getUserInfo(userId);boolean ineligibleForManagedServices = forRestore &&(userInfo.isManagedProfile() || userInfo.isCloneProfile());int outerDepth = parser.getDepth();while (XmlUtils.nextElementWithin(parser, outerDepth)) {if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {mZenModeHelper.readXml(parser, forRestore, userId);} else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){mPreferencesHelper.readXml(parser, forRestore, userId);}if (mListeners.getConfig().xmlTag.equals(parser.getName())) {if (ineligibleForManagedServices) {continue;}mListeners.readXml(parser, mAllowedManagedServicePackages, forRestore, userId);migratedManagedServices = true;} else if (mAssistants.getConfig().xmlTag.equals(parser.getName())) {if (ineligibleForManagedServices) {continue;}mAssistants.readXml(parser, mAllowedManagedServicePackages, forRestore, userId);migratedManagedServices = true;} else if (mConditionProviders.getConfig().xmlTag.equals(parser.getName())) {if (ineligibleForManagedServices) {continue;}mConditionProviders.readXml(parser, mAllowedManagedServicePackages, forRestore, userId);migratedManagedServices = true;} else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) {mSnoozeHelper.readXml(parser, System.currentTimeMillis());}if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {if (forRestore && userId != UserHandle.USER_SYSTEM) {continue;}mLockScreenAllowSecureNotifications = parser.getAttributeBoolean(null,LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE, true);}}if (!migratedManagedServices) {mListeners.migrateToXml();mAssistants.migrateToXml();mConditionProviders.migrateToXml();handleSavePolicyFile();}mAssistants.resetDefaultAssistantsIfNecessary();
}
xml的解析的tag是从notification-policy开始的。
如果tag是zen,则调用mZenModeHelper.readXml(parser, forRestore, userId)
如果是ranking,调用mPreferencesHelper.readXml(parser, forRestore, userId)
然后将配置保存的对应的config对象里面。
2 重要类
2.1 Notification
/*** A class that represents how a persistent notification is to be presented to* the user using the {@link android.app.NotificationManager}.** <p>The {@link Notification.Builder Notification.Builder} has been added to make it* easier to construct Notifications.</p>** <div class="special reference">* <h3>Developer Guides</h3>* <p>For a guide to creating notifications, read the* <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>* developer guide.</p>* </div>*/
public class Notification implements Parcelable
从描述看,Notification是通过NotificationManager来组装通知,呈现给用户。它是App层创建Notification使用的数据结构。Notification包含title,icon,discription等内容。
通过使用Notification.Builder可以使创建通知更加简单。
Notification实现了Parcelable,因此可以跨进程传递。
再来按他的成员变量:
public long when;
public int icon;
public PendingIntent contentIntent;
public PendingIntent deleteIntent;
public PendingIntent fullScreenIntent;
public CharSequence tickerText;
public RemoteViews tickerView;
public RemoteViews contentView;
public RemoteViews bigContentView;
public RemoteViews headsUpContentView;
public Uri sound;
Notification的成变量和成员函数比较多,从代码看,主要是提供了描述通知的逻辑。
Notification还至少有一个Action,Action是以PendingIntent的形式关联到Notification中的,也可以通过NotificationCompat.Builder.addAction(int icon, CharSequence title, PendingIntent intent)函数设定其他的action。Action在UI中是以Button的形式体现的。
2.2 NotificationRecord
/*** Holds data about notifications that should not be shared with the* {@link android.service.notification.NotificationListenerService}s.** <p>These objects should not be mutated unless the code is synchronized* on {@link NotificationManagerService#mNotificationLock}, and any* modification should be followed by a sorting of that list.</p>** <p>Is sortable by {@link NotificationComparator}.</p>** {@hide}*/
public final class NotificationRecord {}
NotificationRecord是NotificationManagerService用来管理所有Notification的数据结构。包含Notification数据结构,package,userid,id,tag,statusBarKey等内容,其中package,userid,id,tag可以用来唯一标识一个NotificationRecord,statusBarKey是可以唯一标识StatusBarManagerService中StatusBarNotification的。
再来看他的一些变量和函数。
private final StatusBarNotification sbn;
NotificationUsageStats.SingleNotificationStats stats;
private final NotificationStats mStats;
private ArraySet<String> mPhoneNumbers;public NotificationRecord(Context context, StatusBarNotification sbn,NotificationChannel channel) {this.sbn = sbn;mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class).getPackageTargetSdkVersion(sbn.getPackageName());mAm = ActivityManager.getService();mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);mOriginalFlags = sbn.getNotification().flags;mRankingTimeMs = calculateRankingTimeMs(0L);mCreationTimeMs = sbn.getPostTime();mUpdateTimeMs = mCreationTimeMs;mInterruptionTimeMs = mCreationTimeMs;mContext = context;stats = new NotificationUsageStats.SingleNotificationStats();mChannel = channel;mPreChannelsNotification = isPreChannelsNotification();mSound = calculateSound();mVibration = calculateVibration();mAttributes = calculateAttributes();mImportance = calculateInitialImportance();mLight = calculateLights();mAdjustments = new ArrayList<>();mStats = new NotificationStats();calculateUserSentiment();calculateGrantableUris();
}
2.2.1 Sound获取
private Uri calculateSound() {final Notification n = getSbn().getNotification();// No notification sounds on tvif (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {return null;}Uri sound = mChannel.getSound();if (mPreChannelsNotification && (getChannel().getUserLockedFields()& NotificationChannel.USER_LOCKED_SOUND) == 0) {final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0;if (useDefaultSound) {sound = Settings.System.DEFAULT_NOTIFICATION_URI;} else {sound = n.sound;}}return sound;
}
主要是获取声音的uri,可以看到默认位置是Settings.System.DEFAULT_NOTIFICATION_URI,当然也可以从Notification创建的时候进行指定。
2.2.2 light获取
private Light calculateLights() {int defaultLightColor = mContext.getResources().getColor(com.android.internal.R.color.config_defaultNotificationColor);int defaultLightOn = mContext.getResources().getInteger(com.android.internal.R.integer.config_defaultNotificationLedOn);int defaultLightOff = mContext.getResources().getInteger(com.android.internal.R.integer.config_defaultNotificationLedOff);int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor(): defaultLightColor;Light light = getChannel().shouldShowLights() ? new Light(channelLightColor,defaultLightOn, defaultLightOff) : null;if (mPreChannelsNotification&& (getChannel().getUserLockedFields()& NotificationChannel.USER_LOCKED_LIGHTS) == 0) {final Notification notification = getSbn().getNotification();if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {light = new Light(notification.ledARGB, notification.ledOnMS,notification.ledOffMS);if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {light = new Light(defaultLightColor, defaultLightOn,defaultLightOff);}} else {light = null;}}return light;
}
也是有默认颜色和默认开关设置,当然也支持自定义设置。
2.3 StatusBarNotification
/*** Class encapsulating a Notification. Sent by the NotificationManagerService to clients including* the status bar and any {@link android.service.notification.NotificationListenerService}s.*/
public StatusBarNotification(String pkg, String opPkg, int id,String tag, int uid, int initialPid, Notification notification, UserHandle user,String overrideGroupKey, long postTime) {if (pkg == null) throw new NullPointerException();if (notification == null) throw new NullPointerException();this.pkg = pkg;this.opPkg = opPkg;this.id = id;this.tag = tag;this.uid = uid;this.initialPid = initialPid;this.notification = notification;this.user = user;this.postTime = postTime;this.overrideGroupKey = overrideGroupKey;this.key = key();this.groupKey = groupKey();
}
StatusBarNotification是StatusBarManagerService中notification的数据结构,包含package,userid,id,tag和Notification数据结构等。
NotificationManagerService会将其发送给客户端,如SystemUI,因此它也实现了Parcelable。
2.3.1 key
可以看到key是一堆唯一的表示组合到一起的字符串.
private String key() {String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;if (overrideGroupKey != null && getNotification().isGroupSummary()) {sbnKey = sbnKey + "|" + overrideGroupKey;}return sbnKey;
}
2.3.2 groupKey
groupKey和key类似。
private String groupKey() {if (overrideGroupKey != null) {return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;}final String group = getNotification().getGroup();final String sortKey = getNotification().getSortKey();if (group == null && sortKey == null) {// a group of onereturn key;}return user.getIdentifier() + "|" + pkg + "|" +(group == null? "c:" + notification.getChannelId(): "g:" + group);
}
2.2.3 isGroup
/*** Returns true if this notification is part of a group.*/
public boolean isGroup() {if (overrideGroupKey != null || isAppGroup()) {return true;}return false;
}/*** Returns true if application asked that this notification be part of a group.*/
public boolean isAppGroup() {if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {return true;}return false;
}
2.4 StatusBarManagerService
/*** A note on locking: We rely on the fact that calls onto mBar are oneway or* if they are local, that they just enqueue messages to not deadlock.*/
public StatusBarManagerService(Context context) {mContext = context;LocalServices.addService(StatusBarManagerInternal.class, mInternalService);// We always have a default display.final UiState state = new UiState();mDisplayUiState.put(DEFAULT_DISPLAY, state);final DisplayManager displayManager =(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);displayManager.registerDisplayListener(this, mHandler);mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);mTileRequestTracker = new TileRequestTracker(mContext);mSessionMonitor = new SessionMonitor(mContext);
}
StatusBarManagerService是StatusBar的后台管理服务,处理StatusBar相关事件,例如显示Notification,Buttery/Signal Status,System Time等。同时也会处理Notification显示,消去,并且点击Notification时发送PendingIntent等也都需要通过这个Service。StatusBarManagerService也会跟NotificationManagerService交互,处理Notification相关的动作。
StatusBarManagerService的构造函数中,添加了StatusBarManagerInternal服务。
/*** Private API used by NotificationManagerService.*/
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {@Overridepublic void onCameraLaunchGestureDetected(int source) {if (mBar != null) {try {mBar.onCameraLaunchGestureDetected(source);} catch (RemoteException e) {}}}@Overridepublic void setDisableFlags(int displayId, int flags, String cause) {StatusBarManagerService.this.setDisableFlags(displayId, flags, cause);}@Overridepublic void toggleSplitScreen() {enforceStatusBarService();if (mBar != null) {try {mBar.toggleSplitScreen();} catch (RemoteException ex) {}}}
}