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) {}
        }
    }
}
相关推荐
老哥不老1 分钟前
MySQL安装教程
android·mysql·adb
xcLeigh1 小时前
html实现好看的多种风格手风琴折叠菜单效果合集(附源码)
android·java·html
图王大胜2 小时前
Android SystemUI组件(07)锁屏KeyguardViewMediator分析
android·framework·systemui·锁屏
aqi004 小时前
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
android·ffmpeg·音视频·直播·流媒体
Leoysq4 小时前
Unity实现原始的发射子弹效果
android
起司锅仔4 小时前
ActivityManagerService Activity的启动流程(2)
android·安卓
猿小蔡5 小时前
Android Bitmap 和Drawable的区别
android
峥嵘life5 小时前
Android14 手机蓝牙配对后阻塞问题解决
android·智能手机
猿小蔡5 小时前
Android混淆不要怕--一文搞定
android