Android O適配---NotificationChannel

一,直觀來(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)源碼几晤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市植阴,隨后出現(xiàn)的幾起案子蟹瘾,更是在濱河造成了極大的恐慌,老刑警劉巖掠手,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憾朴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡喷鸽,警方通過(guò)查閱死者的電腦和手機(jī)众雷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人砾省,你說(shuō)我怎么就攤上這事鸡岗。” “怎么了编兄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵轩性,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我狠鸳,道長(zhǎng)揣苏,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任件舵,我火速辦了婚禮舒岸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芦圾。我一直安慰自己蛾派,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布个少。 她就那樣靜靜地躺著洪乍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夜焦。 梳的紋絲不亂的頭發(fā)上壳澳,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音茫经,去河邊找鬼巷波。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卸伞,可吹牛的內(nèi)容都是我干的抹镊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼荤傲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垮耳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起遂黍,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤终佛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后雾家,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铃彰,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年芯咧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牙捉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竹揍。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鹃共,靈堂內(nèi)的尸體忽然破棺而出鬼佣,到底是詐尸還是另有隱情,我是刑警寧澤霜浴,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布晶衷,位于F島的核電站,受9級(jí)特大地震影響阴孟,放射性物質(zhì)發(fā)生泄漏晌纫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一永丝、第九天 我趴在偏房一處隱蔽的房頂上張望锹漱。 院中可真熱鬧,春花似錦慕嚷、人聲如沸哥牍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嗅辣。三九已至,卻和暖如春挠说,著一層夾襖步出監(jiān)牢的瞬間澡谭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工损俭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛙奖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓杆兵,卻偏偏與公主長(zhǎng)得像雁仲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拧咳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容