Android O适配---NotificationChannel

一,直观来看

android-o上对通知做了更细粒度的管理。根据app的业务场景将通知分类。用户可以在设置中选择接收自己感兴趣的通知。同时也可以屏蔽不喜欢的通知。先上图看看新特性。

入口:手机桌面长按(android 原生8.0)app图标,右上角出现提示“应用信息”,点击进入应用详情。如下图一:我们可以看见”应用通知“

《Android O适配---NotificationChannel》

图一

点击应用通知进入应用通知管理详情页,如下图二中类别所示:本次测试创建了三种NotificationChannel:下载,聊天,推送。每种NotificationChannel设置了不同的属性,如:重要性,是否振动等等

《Android O适配---NotificationChannel》

图二

点击图二中的下载NotificationChannel,进入NotificationChannel详情页,如图三,可以看到下载NotificationChannel的详细信息

《Android O适配---NotificationChannel》

图三

总之,android-o上对通知的管理更加细粒度,通过”管道“对通知分类,用户可以根据自己对通知的喜好选择接收特定管道的通知。

二,从源码分析

一,关键类

1.NotificationManagerService

NotificationManagerService继承SystemService,是系统级别的service.

NotificationManagerService在init初始化的时候会加载/data/system/notification_policy.xml中的通知信息到内存数据结构,该文件中保存所有package注册的所有NotificationChannel。如下内容为xml文件部分内容。可以看到不同package下创建的channel以及channel相关属性。

</package>
<package name="com.android.providers.downloads" show_badge="true" uid="1010013">
<channel id="active" name="下载中" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="complete" name="下载完成" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="waiting" name="等待中" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="insufficient_space" name="空间不足" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
</package>
<package name="com.android.providers.downloads" show_badge="true" uid="10013">
<channel id="active" name="下载中" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="complete" name="下载完成" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="waiting" name="等待中" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
<channel id="insufficient_space" name="空间不足" importance="2" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" show_badge="true" />
</package>

NotificationManagerService主要职能是对通知的管理和调度。具体实现不列举了,本次只讨论android-O上的新特性

2.NotificationChannel

android-o上app发送的每个通知必须依附于一个channel.即,每个notification对象必须发送到指定的NotificationChannel.如果找不到channel,就会报错,如下

NotificationService: No Channel found for pkg=com.miui.securitycore,

channelId=testNotificationChannel, id=10000, tag=null, opPkg=com.miui.securitycore,

callingUid=1000, userId=10, incomingUserId=10,

notificationUid=1001000, notification=Notification(channel=testNotificationChannel pri=0

contentView=null vibrate=null sound=null tick defaults=0x0 flags=0x2

color=0x00000000 vis=PRIVATE)

发通知:

1.创建Notification对象

        /**
         * Constructs a new Builder with the defaults:
         *
         * @param context
         *            A {@link Context} that will be used by the Builder to construct the
         *            RemoteViews. The Context will not be held past the lifetime of this Builder
         *            object.
         * @param channelId
         *            The constructed Notification will be posted on this
         *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
         *            created using {@link NotificationManager#createNotificationChannel}.
         */
        public Builder(Context context, String channelId) {
            this(context, (Notification) null);
            mN.mChannelId = channelId;
        }

2.创建NotificationChannel对象(不必每次创建)

    /**
     * Creates a notification channel.
     *
     * @param id The id of the channel. Must be unique per package. The value may be truncated if
     *           it is too long.
     * @param name The user visible name of the channel. You can rename this channel when the system
     *             locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
     *             broadcast. The recommended maximum length is 40 characters; the value may be
     *             truncated if it is too long.
     * @param importance The importance of the channel. This controls how interruptive notifications
     *                   posted to this channel are.
     */
    public NotificationChannel(String id, CharSequence name, @Importance int importance) {
        this.mId = getTrimmedString(id);
        this.mName = name != null ? getTrimmedString(name.toString()) : null;
        this.mImportance = importance;
    }

3.发送

    /**
     * @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) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

总结:1.channel有很多属性可以设置。channel相当一个大的类别,该类别的通知具有相同特征,如振动,闪灯等等

    @Override
    public String toString() {
        return "NotificationChannel{" +
                "mId='" + mId + '\'' +
                ", mName=" + mName +
                ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") +
                ", mImportance=" + mImportance +
                ", mBypassDnd=" + mBypassDnd +
                ", mLockscreenVisibility=" + mLockscreenVisibility +
                ", mSound=" + mSound +
                ", mLights=" + mLights +
                ", mLightColor=" + mLightColor +
                ", mVibration=" + Arrays.toString(mVibration) +
                ", mUserLockedFields=" + mUserLockedFields +
                ", mVibrationEnabled=" + mVibrationEnabled +
                ", mShowBadge=" + mShowBadge +
                ", mDeleted=" + mDeleted +
                ", mGroup='" + mGroup + '\'' +
                ", mAudioAttributes=" + mAudioAttributes +
                ", mBlockableSystem=" + mBlockableSystem +
                '}';
    }

2.创建notification时候需要关联channel,没有channel会报错。

    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);

        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);

        // Fix the notification as best we can.
        try {
            final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
            Notification.addFieldsFromContext(ai, notification);
        } catch (NameNotFoundException e) {
            Slog.e(TAG, "Cannot create a context for sending app", e);
            return;
        }

        mUsageStats.registerEnqueuedByApp(pkg);

        // setup local book-keeping
        String channelId = notification.getChannelId();
        if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
            channelId = (new Notification.TvExtender(notification)).getChannelId();
        }
        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
                notificationUid, channelId, false /* includeDeleted */);
        if (channel == null) {
            final String noChannelStr = "No Channel found for "
                    + "pkg=" + pkg
                    + ", channelId=" + channelId
                    + ", id=" + id
                    + ", tag=" + tag
                    + ", opPkg=" + opPkg
                    + ", callingUid=" + callingUid
                    + ", userId=" + userId
                    + ", incomingUserId=" + incomingUserId
                    + ", notificationUid=" + notificationUid
                    + ", notification=" + notification;
            Log.e(TAG, noChannelStr);
            doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                    "Failed to post notification on channel \"" + channelId + "\"\n" +
                    "See log for more details");
            return;
        }

        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);

        if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r)) {
            return;
        }

        // Whitelist pending intents.
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
                final ActivityManagerInternal am = LocalServices
                        .getService(ActivityManagerInternal.class);
                final long duration = LocalServices.getService(
                        DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
                                WHITELIST_TOKEN, duration);
                    }
                }
            }
        }

        mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }

三,多用户下的NotificationChannel

NotificationChannel不同用户空间下是通过uid来区分的,所以在多用户下系统在寻找channel时是以pkg + “|” + uid为key查找的。具体过程见源码。

<package name="com.miui.securitycore" show_badge="true" uid="1000">
<channel id="com_miui_securitycore_channel_id" name="com_miui_securitycore_channel_name" importance="4" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" vibration_enabled="true" show_badge="true" />
</package>

<package name="com.miui.securitycore" show_badge="true" uid="1001000">
<channel id="com_miui_securitycore_channel_id" name="com_miui_securitycore_channel_name" importance="4" sound="content://settings/system/notification_sound" usage="5" content_type="4" flags="0" vibration_enabled="true" show_badge="true" />
</package>

    原文作者:米同学
    原文地址: https://blog.csdn.net/misiyuan/article/details/78384819
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞