Android通知服务及相关概念

本文基于Android 14源码

1 NotificationManagerService的启动

1.1 添加服务

和其他系统服务一样,NotificationManagerService也是在SystemServer中启动的。

java 复制代码
//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()来启动服务。

java 复制代码
//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

java 复制代码
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:

java 复制代码
void loadDefaultApprovedServices(int userId) {
    mListeners.loadDefaultsFromConfig();

    mConditionProviders.loadDefaultsFromConfig();

    mAssistants.loadDefaultsFromConfig();
}

loadDefaultApprovedServices主要是加载一些默认的配置。

再来看allowDefaultApprovedServices。

java 复制代码
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中获取的

java 复制代码
String defaultListenerAccess = mContext.getResources().getString(
                    R.string.config_defaultListenerAccessPackages);

不过,源码里面这个值是空的

xml 复制代码
<!-- Colon separated list of package names that should be granted Notification Listener access -->
<string name="config_defaultListenerAccessPackages" translatable="false"></string>

但是在GMS里有配置

xml 复制代码
<string name="config_defaultListenerAccessPackages" translatable="false">com.android.launcher3:com.google.android.projection.gearhead</string>

再来看readPolicyXml

java 复制代码
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

java 复制代码
/**
 * 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,因此可以跨进程传递。

再来按他的成员变量:

java 复制代码
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

java 复制代码
/**
 * 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的。

再来看他的一些变量和函数。

java 复制代码
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获取

java 复制代码
private Uri calculateSound() {
    final Notification n = getSbn().getNotification();

    // No notification sounds on tv
    if (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获取

java 复制代码
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

java 复制代码
/**
 * 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是一堆唯一的表示组合到一起的字符串.

java 复制代码
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类似。

java 复制代码
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 one
        return key;
    }
    return user.getIdentifier() + "|" + pkg + "|" +
        (group == null
         ? "c:" + notification.getChannelId()
         : "g:" + group);
}

2.2.3 isGroup

java 复制代码
/**
 * 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

java 复制代码
/**
 * 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服务。

java 复制代码
/**
 * Private API used by NotificationManagerService.
 */
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
    @Override
    public void onCameraLaunchGestureDetected(int source) {
        if (mBar != null) {
            try {
                mBar.onCameraLaunchGestureDetected(source);
            } catch (RemoteException e) {
            }
        }
    }

    @Override
    public void setDisableFlags(int displayId, int flags, String cause) {
        StatusBarManagerService.this.setDisableFlags(displayId, flags, cause);
    }

    @Override
    public void toggleSplitScreen() {
        enforceStatusBarService();
        if (mBar != null) {
            try {
                mBar.toggleSplitScreen();
            } catch (RemoteException ex) {}
        }
    }
}
相关推荐
NotesChapter39 分钟前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快2 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl2 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江2 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-3 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记
佛系小嘟嘟3 小时前
Android Studio不显示需要的tag日志解决办法《All logs entries are hidden by the filter》
android·ide·android studio
mariokkm3 小时前
Django一分钟:django中收集关联对象关联数据的方法
android·django·sqlite
长亭外的少年4 小时前
如何查看 Android 项目的依赖结构树
android
深海呐5 小时前
Android 从本地选择视频,用APP播放或进行其他处理
android·音视频·从本地选择视频,用app播放·从本地选择视频,并拿到信息·跳转到本地视频列表
深海呐5 小时前
Android Google登录接入
android·google登录接入·android 谷歌登录接入·google登录·android google