一,直觀來(lái)看
android-o上對(duì)通知做了更細(xì)粒度的管理皆看。根據(jù)app的業(yè)務(wù)場(chǎng)景將通知分類(lèi)韭寸。用戶(hù)可以在設(shè)置中選擇接收自己感興趣的通知。同時(shí)也可以屏蔽不喜歡的通知喷楣。先上圖看看新特性趟大。
入口:手機(jī)桌面長(zhǎng)按(android 原生8.0)app圖標(biāo),右上角出現(xiàn)提示“應(yīng)用信息”抡蛙,點(diǎn)擊進(jìn)入應(yīng)用詳情护昧。如下圖一:我們可以看見(jiàn)”應(yīng)用通知“
圖一
點(diǎn)擊應(yīng)用通知進(jìn)入應(yīng)用通知管理詳情頁(yè),如下圖二中類(lèi)別所示:本次測(cè)試創(chuàng)建了三種NotificationChannel:下載,聊天,推送粗截。每種NotificationChannel設(shè)置了不同的屬性惋耙,如:重要性,是否振動(dòng)等等
圖二
點(diǎn)擊圖二中的下載NotificationChannel,進(jìn)入NotificationChannel詳情頁(yè)绽榛,如圖三湿酸,可以看到下載NotificationChannel的詳細(xì)信息
圖三
總之,android-o上對(duì)通知的管理更加細(xì)粒度灭美,通過(guò)”管道“對(duì)通知分類(lèi)推溃,用戶(hù)可以根據(jù)自己對(duì)通知的喜好選擇接收特定管道的通知。
二届腐,從源碼分析
一铁坎,關(guān)鍵類(lèi)
1.NotificationManagerService
NotificationManagerService繼承SystemService,是系統(tǒng)級(jí)別的service.
NotificationManagerService
在init初始化的時(shí)候會(huì)加載/data/system/notification_policy.xml中的通知信息到內(nèi)存數(shù)據(jù)結(jié)構(gòu),該文件中保存所有
package注冊(cè)的所有NotificationChannel犁苏。如下內(nèi)容為xml文件部分內(nèi)容硬萍。可以看到不同package下創(chuàng)建的channel以
及channel相關(guān)屬性围详。
NotificationManagerService主要職能是對(duì)通知的管理和調(diào)度朴乖。具體實(shí)現(xiàn)不列舉了,本次只討論android-O上的新特性
2.NotificationChannel
android-o上app發(fā)送的每個(gè)通知必須依附于一個(gè)channel.即助赞,每個(gè)notification對(duì)象必須發(fā)送到指定的NotificationChannel.如果找不到channel买羞,就會(huì)報(bào)錯(cuò),如下
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)
發(fā)通知:
1.創(chuàng)建Notification對(duì)象
/**
* 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.創(chuàng)建NotificationChannel對(duì)象(不必每次創(chuàng)建)
/**
* 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.發(fā)送
/**
* @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();
}
}
總結(jié):1.channel有很多屬性可以設(shè)置。channel相當(dāng)一個(gè)大的類(lèi)別,該類(lèi)別的通知具有相同特征纤控,如振動(dòng),閃燈等等
@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.創(chuàng)建notification時(shí)候需要關(guān)聯(lián)channel漠嵌,沒(méi)有channel會(huì)報(bào)錯(cuò)。
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-keepingString 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));? ? }
三盖呼,多用戶(hù)下的NotificationChannel
NotificationChannel不同用戶(hù)空間下是通過(guò)uid來(lái)區(qū)分的,所以在多用戶(hù)下系統(tǒng)在尋找channel時(shí)是以pkg + "|" + uid為key查找的化撕。具體過(guò)程見(jiàn)源碼几晤。