中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

工信部外國網(wǎng)站備案/b站在線觀看人數(shù)在哪

工信部外國網(wǎng)站備案,b站在線觀看人數(shù)在哪,七寶做網(wǎng)站,網(wǎng)站建設(shè)公司的以下NotificationManagerService簡稱 NMS 1. 通知的發(fā)送: NotificationManager.notify(int id, Notification notification) 開始. 源碼路徑: /frameworks/base/core/java/android/app/NotificationManager.java/***發(fā)布通知以顯示在狀態(tài)欄中。 如果通知帶有* 相同的 ID 已被…

以下NotificationManagerService簡稱 NMS

1. 通知的發(fā)送: NotificationManager.notify(int id, Notification notification) 開始.

源碼路徑: /frameworks/base/core/java/android/app/NotificationManager.java/***發(fā)布通知以顯示在狀態(tài)欄中。 如果通知帶有* 相同的 ID 已被您的應(yīng)用程序發(fā)布且尚未被取消,它將被更新信息取代。** @param id 此通知的標(biāo)識符在您的系統(tǒng)中是唯一的應(yīng)用。* @param notification  描述向用戶顯示的內(nèi)容。 一定不為空。*        */public void notify(int id, Notification notification){notify(null, id, notification);}

這里繼續(xù)調(diào)用 notify(), 其中 tag = null;

源碼路徑: /frameworks/base/core/java/android/app/NotificationManager.java 
public void notify(String tag, int id, Notification notification){notifyAsUser(tag, id, notification, mContext.getUser());}/*** @hide*/@UnsupportedAppUsagepublic void notifyAsUser(String tag, int id, Notification notification, UserHandle user){INotificationManager service = getService();//獲取binder對象,實(shí)現(xiàn)跨進(jìn)程通信String pkg = mContext.getPackageName(); //獲取發(fā)送通知應(yīng)用的包名try {//跨進(jìn)程調(diào)用,即調(diào)用NMS中的enqueueNotificationWithTag(),請看分析4//在這之前會先調(diào)用fixNotification()方法,提前做一些優(yōu)化,請看分析2service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(),tag, id,fixNotification(notification), user.getIdentifier())} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

2.? 優(yōu)化通知 , fixNotification(notification) 代碼如下:

源碼路徑: /frameworks/base/core/java/android/app/NotificationManager.javaprivate Notification fixNotification(Notification notification) {String pkg = mContext.getPackageName();//這里把ApplicationInfo保存到Notificaiton.extras參數(shù)中, 請看2.(1)Notification.addFieldsFromContext(mContext, notification);//如果設(shè)置了通知鈴聲,這里獲取鈴聲的uriif (notification.sound != null) {notification.sound = notification.sound.getCanonicalUri();if (StrictMode.vmFileUriExposureEnabled()) {notification.sound.checkFileUriExposed("Notification.sound");}}fixLegacySmallIcon(notification, pkg);//smallIcon版本兼容處理,請看2.(2)//Android 5.1 后要求必須設(shè)置setSmallIcon(),否則拋出異常if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {if (notification.getSmallIcon() == null) {throw new IllegalArgumentException("Invalid notification (no valid small icon): "+ notification);}}notification.reduceImageSizes(mContext);//按比例壓縮圖片,請看2.(3)return Builder.maybeCloneStrippedForDelivery(notification);}

(1) 保存ApplicationInfo 對象到通知中,addFieldsFromContext()?,源碼如下:

源碼路徑: /frameworks/base/core/java/android/app/Notification.java 
/*** @hide*/public static void addFieldsFromContext(Context context, Notification notification) {addFieldsFromContext(context.getApplicationInfo(), notification);}/*** @hide*/public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {//保存ApplicationInfo對象到通知中,屬性名為EXTRA_BUILDER_APPLICATION_INFOnotification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);}

(2)?smallIcon版本兼容處理?

老版本中定義的通知smallIcon為資源int型,新版本中換成Icon 類型,為了兼容舊版本,這里做了轉(zhuǎn)換,即把 int 型轉(zhuǎn)化成Icon型,并設(shè)置到通知中,fixLegacySmallIcon(notification, pkg)源碼如下:

源碼路徑: /frameworks/base/core/java/android/app/NotificationManager.java
private void fixLegacySmallIcon(Notification n, String pkg) {if (n.getSmallIcon() == null && n.icon != 0) {//n.setSmallIcon(Icon icon), 而 n.icon 為 int 型,這里調(diào)用了createWithResource()轉(zhuǎn)換n.setSmallIcon(Icon.createWithResource(pkg, n.icon));}}源碼路徑: frameworks/base/graphics/java/android/graphics/drawable/Icon.java/*** 創(chuàng)建Icon對象* @param resPackage 包名* @param resId 資源ID*/public static Icon createWithResource(String resPackage, @DrawableRes int resId) {if (resPackage == null) {throw new IllegalArgumentException("Resource package name must not be null.");}final Icon rep = new Icon(TYPE_RESOURCE);rep.mInt1 = resId;rep.mString1 = resPackage;return rep;}

(3) 壓縮圖片?reduceImageSizes(mContext)

源碼如下:

源碼路徑: /frameworks/base/core/java/android/app/Notification.java
/*** 把圖片縮小成給定的尺寸* @hide*/void reduceImageSizes(Context context) {if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {return;}boolean isLowRam = ActivityManager.isLowRamDeviceStatic();//判斷設(shè)備是否為低內(nèi)存if (mLargeIcon != null || largeIcon != null) {Resources resources = context.getResources();Class<? extends Style> style = getNotificationStyle();//不管是否為低內(nèi)存,maxSize=48dp,源碼定義在:/frameworks/base/core/res/res/values/dimens.xmlint maxSize = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_right_icon_size_low_ram: R.dimen.notification_right_icon_size);if (mLargeIcon != null) {//壓縮圖片mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);}if (largeIcon != null) {//壓縮圖片largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);}}//對RemotView中的圖片按規(guī)定的尺寸進(jìn)行壓縮reduceImageSizesForRemoteView(contentView, context, isLowRam);reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);reduceImageSizesForRemoteView(bigContentView, context, isLowRam);extras.putBoolean(EXTRA_REDUCED_IMAGES, true);}/***對RemotView中的圖片按規(guī)定的尺寸進(jìn)行壓縮
*/private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,boolean isLowRam) {if (remoteView != null) {Resources resources = context.getResources();int maxWidth = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_custom_view_max_image_width_low_ram //294dp: R.dimen.notification_custom_view_max_image_width);//450dpint maxHeight = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_custom_view_max_image_height_low_ram //208dp: R.dimen.notification_custom_view_max_image_height); //284dpremoteView.reduceImageSizes(maxWidth, maxHeight);}}源碼路徑: frameworks/base/graphics/java/android/graphics/drawable/Icon.java/*** 將位圖縮小到給定的最大寬度和最大高度。 縮放將以統(tǒng)一的方式完成* @param bitmap 要縮小的位圖* @param maxWidth 允許的最大寬度* @param maxHeight 允許的最大高度** @如果需要?jiǎng)t返回縮放后的位圖,如果不需要縮放則返回原始位圖* @hide*/public static Bitmap scaleDownIfNecessary(Bitmap bitmap, int maxWidth, int maxHeight) {int bitmapWidth = bitmap.getWidth();int bitmapHeight = bitmap.getHeight();if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {float scale = Math.min((float) maxWidth / bitmapWidth,(float) maxHeight / bitmapHeight);bitmap = Bitmap.createScaledBitmap(bitmap,Math.max(1, (int) (scale * bitmapWidth)),Math.max(1, (int) (scale * bitmapHeight)),true /* filter */);}return bitmap;}

以上只是列舉了壓縮largeIcon 的例子,Notificaiton.java中,針對通知中的各種圖片都做個(gè)指定尺寸的壓縮.通知前期的優(yōu)化完畢,繼續(xù)看通知在NMS中的處理.

3. NMS 中保存通知的一些數(shù)據(jù)結(jié)構(gòu)說明

源碼路徑: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java// 服務(wù)端維護(hù)的 已排序 的通知final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();// 服務(wù)端維護(hù)的 未排序 的通知final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();// 入隊(duì)通知: 保存所有入隊(duì)的通知,當(dāng)通知成功發(fā)送后則移除,即該列表記錄的是所有入隊(duì)成功且沒有被發(fā)送出去的通知final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();// 維護(hù)系統(tǒng)自動(dòng)成組后的父通知final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();// 服務(wù)端根據(jù)groupKey,維護(hù)著所有用戶主動(dòng)成組的父通知 final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();

4. 通知到達(dá)enqueueNotificationWithTag()@NMS

enqueueNotificationWithTag()里調(diào)用了 enqueueNotificationInternal(),所以直接從enqueueNotificationInternal()開始學(xué)習(xí),源碼如下:

(1)?enqueueNotificationInternal()

 源碼路徑: frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javavoid enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,final int callingPid, final String tag, final int id, final Notification notification,int incomingUserId, boolean postSilently) {......checkRestrictedCategories(notification);//檢查通知是否屬于僅限系統(tǒng)使用的類別類型,// 優(yōu)化通知,請看4.(2)try {fixNotification(notification, pkg, tag, id, userId);} catch (Exception e) {if (notification.isForegroundService()) {throw new SecurityException("Invalid FGS notification", e);}Slog.e(TAG, "Cannot fix notification", e);return;}// 檢查setForegroundService()是否有FLAG_FOREGROUND_SERVICE權(quán)限final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(notification, tag, id, pkg, userId);if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {if (!isNotificationShownInternal(pkg, tag, id, userId)) {reportForegroundServiceUpdate(false, notification, id, pkg, userId);return;}}mUsageStats.registerEnqueuedByApp(pkg);//把通知封裝成StatusBarNotification對象,即一條通知對應(yīng)一個(gè)StatusBarNotification對象,主要面對App端final StatusBarNotification n = new StatusBarNotification(pkg, opPkg, id, tag, notificationUid, callingPid, notification,user, null, System.currentTimeMillis());// 創(chuàng)建channelId,String channelId = notification.getChannelId();if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {channelId = (new Notification.TvExtender(notification)).getChannelId();}String shortcutId = n.getShortcutId();//Android8.0之后就需要為通知設(shè)置Channel,這里做了判斷,否則無法發(fā)送通知final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(pkg, notificationUid, channelId, shortcutId,true /* parent ok */, 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;Slog.e(TAG, noChannelStr);//獲取通知的重要性boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)== NotificationManager.IMPORTANCE_NONE;if (!appNotificationsOff) {doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +"Failed to post notification on channel \"" + channelId + "\"\n" +"See log for more details");}return;}//把通知封裝成NotificationRecord對象,即一條通知就是一個(gè)NotificationRecord對象,主要面對Service端final NotificationRecord r = new NotificationRecord(getContext(), n, channel);r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));r.setPostSilently(postSilently);r.setFlagBubbleRemoved(false);r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {final boolean fgServiceShown = channel.isFgServiceShown();if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0|| !fgServiceShown)&& (r.getImportance() == IMPORTANCE_MIN|| r.getImportance() == IMPORTANCE_NONE)) {//提高通知的重要性if (TextUtils.isEmpty(channelId)|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {r.setSystemImportance(IMPORTANCE_LOW);} else {channel.setImportance(IMPORTANCE_LOW);r.setSystemImportance(IMPORTANCE_LOW);if (!fgServiceShown) {channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);channel.setFgServiceShown(true);}mPreferencesHelper.updateNotificationChannel(pkg, notificationUid, channel, false);r.updateNotificationChannel(channel);}} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {channel.setFgServiceShown(true);r.updateNotificationChannel(channel);}}ShortcutInfo info = mShortcutHelper != null? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user): null;if (notification.getShortcutId() != null && info == null) {Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");}r.setShortcutInfo(info);r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));r.userDemotedAppFromConvoSpace(mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));//進(jìn)一步過濾不符合規(guī)定的通知,限制通知速率和通知數(shù)量,請看4.(3)if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,r.getSbn().getOverrideGroupKey() != null)) {return;}if (info != null) {// 緩存快捷方式mShortcutHelper.cacheShortcut(info, user);}// 暫時(shí)允許應(yīng)用程序在啟動(dòng)待處理意圖時(shí)執(zhí)行額外的工作,if (notification.allPendingIntents != null) {final int intentCount = notification.allPendingIntents.size();if (intentCount > 0) {final long duration = LocalServices.getService(DeviceIdleInternal.class).getNotificationAllowlistDuration();for (int i = 0; i < intentCount; i++) {PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);if (pendingIntent != null) {mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),ALLOWLIST_TOKEN, duration,TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,REASON_NOTIFICATION_SERVICE,"NotificationManagerService");mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER| FLAG_SERVICE_SENDER));}}}}// 需要升級權(quán)限才能獲得包重要性final long token = Binder.clearCallingIdentity();boolean isAppForeground;try {isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;} finally {Binder.restoreCallingIdentity(token);}//經(jīng)過上面的一步一步過濾后,現(xiàn)在通知post到線程里,請看5分析mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));}

(2) 第二次優(yōu)化通知, fixNotification(notification, pkg, tag, id, userId)

 源碼路徑: frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javaprotected void fixNotification(Notification notification, String pkg, String tag, int id,int userId) throws NameNotFoundException {final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);//保存ApplicationInfo對象,請看分析2.(2)Notification.addFieldsFromContext(ai, notification);//檢查權(quán)限,通知是否能著色,即通知中的 setColorized(boolean)int canColorize = mPackageManagerClient.checkPermission(android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);if (canColorize == PERMISSION_GRANTED) {notification.flags |= Notification.FLAG_CAN_COLORIZE;} else {notification.flags &= ~Notification.FLAG_CAN_COLORIZE;}//檢查全屏通知的權(quán)限,如果在Android Q(29)及以上給通知設(shè)置了fullScreenIntent,同時(shí)還//需要設(shè)置android.Manifest.permission.USE_FULL_SCREEN_INTENT權(quán)限,否則通知的//fullScreenIntent將被系統(tǒng)始終為null,即無效      if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {int fullscreenIntentPermission = mPackageManagerClient.checkPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);if (fullscreenIntentPermission != PERMISSION_GRANTED) {//權(quán)限不足,該屬性設(shè)置為nullnotification.fullScreenIntent = null;//fullScreenIntent無效日志Slog.w(TAG, "Package " + pkg +": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission");}}// 檢查 Style 樣式中的action事件if (notification.isStyle(Notification.CallStyle.class)) {Notification.Builder builder =Notification.Builder.recoverBuilder(getContext(), notification);Notification.CallStyle style = (Notification.CallStyle) builder.getStyle();List<Notification.Action> actions = style.getActionsListWithSystemActions();notification.actions = new Notification.Action[actions.size()];actions.toArray(notification.actions);}// 檢查RemoteView中的contentView,bigcontentView,headsUpContentView等是否超過checkRemoteViews(pkg, tag, id, notification);}/*** 檢查RemouteView 的大小,是否超過了指定的大小
*/private boolean removeRemoteView(String pkg, String tag, int id, RemoteViews contentView) {if (contentView == null) {return false;}//獲取當(dāng)前RemoteView的大小final int contentViewSize = contentView.estimateMemoryUsage();//其中 mWarnRemoteViewsSizeBytes = 2000000 bytes , mStripRemoteViewsSizeBytes = 5000000 bytesif (contentViewSize > mWarnRemoteViewsSizeBytes&& contentViewSize < mStripRemoteViewsSizeBytes) {Slog.w(TAG, "RemoteViews too large on pkg: " + pkg + " tag: " + tag + " id: " + id+ " this might be stripped in a future release");}// contentViewSize >= 5000000 bytesif (contentViewSize >= mStripRemoteViewsSizeBytes) {mUsageStats.registerImageRemoved(pkg);Slog.w(TAG, "Removed too large RemoteViews (" + contentViewSize + " bytes) on pkg: "+ pkg + " tag: " + tag + " id: " + id);return true;}return false;}

(3) 限制通知速率和通知數(shù)量:?checkDisqualifyingFeatures()

 源碼路徑: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
涉及的源碼路徑:
速率的計(jì)算: /frameworks/base/services/core/java/com/android/server/notification/RateEstimator.java
保存不發(fā)送的通知:/frameworks/base/services/core/java/com/android/server/notification/NotificationUsageStats.java/*** 檢查是否可以發(fā)布通知。 檢查速率限制器、暫停助手和阻止。* 如果通知檢查不合格,則返回 false,* 應(yīng)用速率不能超過5000毫秒,通知總數(shù)不能超過50條*/boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,NotificationRecord r, boolean isAutogroup) {Notification n = r.getNotification();final String pkg = r.getSbn().getPackageName();//是否為系統(tǒng)通知final boolean isSystemNotification =isUidSystemOrPhone(uid) || ("android".equals(pkg));//是否為通知監(jiān)聽器final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);// 限制除 android 之外的任何給定包的通知數(shù)量if (!isSystemNotification && !isNotificationFromListener) {final int callingUid = Binder.getCallingUid();if (mNotificationsByKey.get(r.getSbn().getKey()) == null&& isCallerInstantApp(callingUid, userId)) {// 臨時(shí)應(yīng)用程序?qū)νㄖ幸恍┨厥獾南拗啤?/ 他們不被允許創(chuàng)建新的通知,但是他們被允許// 更新系統(tǒng)創(chuàng)建的通知(例如前臺服務(wù)通知)。throw new SecurityException("Instant app " + pkg+ " cannot create notifications");}//限制更新未完成進(jìn)度通知(即:進(jìn)度條通知還在更新進(jìn)度,當(dāng)前速度還未達(dá)到最大值)的速率,if (mNotificationsByKey.get(r.getSbn().getKey()) != null&& !r.getNotification().hasCompletedProgress()&& !isAutogroup) {//算出這條通知距離上一個(gè)通知的時(shí)間差,然后算出速率,過程請看下面文字分析final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);//如果這個(gè)通知的速率大于規(guī)定的最大值,其中mMaxPackageEnqueueRate=5fif (appEnqueueRate > mMaxPackageEnqueueRate) {//把違規(guī)超速率的通知數(shù)量做好統(tǒng)計(jì),保存在NotificationUsageStats.java中mUsageStats.registerOverRateQuota(pkg);final long now = SystemClock.elapsedRealtime();//這條通知的時(shí)間-上條通知的時(shí)間 > 5000 毫秒if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate+ ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);mLastOverRateLogTime = now;}return false;//速率不合格,直接返回false}}// 限制應(yīng)用程序可以擁有的非前臺服務(wù) 未完成通知記錄的數(shù)量if (!n.isForegroundService()) {//計(jì)算應(yīng)用通知的總數(shù),該總數(shù):發(fā)送成功的通知+發(fā)送不成功的通知int count = getNotificationCount(pkg, userId, id, tag);// 應(yīng)用總通知數(shù) >= 50 條if (count >= MAX_PACKAGE_NOTIFICATIONS) {//把超出總數(shù)的通知保存在NotificationUsageStats.java中mUsageStats.registerOverCountQuota(pkg);Slog.e(TAG, "Package has already posted or enqueued " + count+ " notifications.  Not showing more.  package=" + pkg);return false;//通知總數(shù)不合格,直接返回false}}}// 氣泡或內(nèi)聯(lián)回復(fù)是不可變的?if (n.getBubbleMetadata() != null&& n.getBubbleMetadata().getIntent() != null&& hasFlag(mAmi.getPendingIntentFlags(n.getBubbleMetadata().getIntent().getTarget()),PendingIntent.FLAG_IMMUTABLE)) {throw new IllegalArgumentException(r.getKey() + " Not posted."+ " PendingIntents attached to bubbles must be mutable");}if (n.actions != null) {for (Notification.Action action : n.actions) {if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)&& hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),PendingIntent.FLAG_IMMUTABLE)) {throw new IllegalArgumentException(r.getKey() + " Not posted."+ " PendingIntents attached to actions with remote"+ " inputs must be mutable");}}}if (r.getSystemGeneratedSmartActions() != null) {for (Notification.Action action : r.getSystemGeneratedSmartActions()) {if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)&& hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),PendingIntent.FLAG_IMMUTABLE)) {throw new IllegalArgumentException(r.getKey() + " Not posted."+ " PendingIntents attached to contextual actions with remote inputs"+ " must be mutable");}}}if (n.isStyle(Notification.CallStyle.class)) {boolean isForegroundService = (n.flags & FLAG_FOREGROUND_SERVICE) != 0;boolean hasFullScreenIntent = n.fullScreenIntent != null;if (!isForegroundService && !hasFullScreenIntent) {throw new IllegalArgumentException(r.getKey() + " Not posted."+ " CallStyle notifications must either be for a foreground Service or"+ " use a fullScreenIntent.");}}// 不發(fā)送snoozed類型的通知,當(dāng)用戶在設(shè)置中設(shè)置了不允許顯示某個(gè)應(yīng)用的通知(blocked)時(shí),不再發(fā)送if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {MetricsLogger.action(r.getLogMaker().setType(MetricsProto.MetricsEvent.TYPE_UPDATE).setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));mNotificationRecordLogger.log(NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,r);if (DBG) {Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());}mSnoozeHelper.update(userId, r);handleSavePolicyFile();return false;}// blocked appsif (isBlocked(r, mUsageStats)) {return false;}return true;}

5 . EnqueueNotificationRunnable@NMS

到此,通知經(jīng)過優(yōu)化后,最終進(jìn)入到線程,下面是該線程的run() 方法 ,源碼如下:

 源碼路徑: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 
public void run() {synchronized (mNotificationLock) {final Long snoozeAt =mSnoozeHelper.getSnoozeTimeForUnpostedNotification(r.getUser().getIdentifier(),r.getSbn().getPackageName(), r.getSbn().getKey());final long currentTime = System.currentTimeMillis();if (snoozeAt.longValue() > currentTime) {(new SnoozeNotificationRunnable(r.getSbn().getKey(),snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);return;}final String contextId =mSnoozeHelper.getSnoozeContextForUnpostedNotification(r.getUser().getIdentifier(),r.getSbn().getPackageName(), r.getSbn().getKey());if (contextId != null) {(new SnoozeNotificationRunnable(r.getSbn().getKey(),0, contextId)).snoozeLocked(r);return;}//把通知添加到List中,即入隊(duì)通知,指入隊(duì)但未發(fā)送出去的通知,分析請看3mEnqueuedNotifications.add(r);scheduleTimeoutLocked(r);final StatusBarNotification n = r.getSbn();NotificationRecord old = mNotificationsByKey.get(n.getKey());//查看通知List中,是否已經(jīng)存在該通知(通知的唯一標(biāo)識為key),if (old != null) {// 保留以前記錄的排名信息r.copyRankingInformation(old);}//表明該通知之前不存在,是一個(gè)新的通知,final int callingUid = n.getUid();final int callingPid = n.getInitialPid();final Notification notification = n.getNotification();final String pkg = n.getPackageName();final int id = n.getId();final String tag = n.getTag();// 更新氣泡通知updateNotificationBubbleFlags(r, isAppForeground);// 處理分組通知,詳細(xì)介紹請看分析9handleGroupedNotificationLocked(r, old, callingUid, callingPid);if (n.isGroup() && notification.isGroupChild()) {mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());}if (mAssistants.isEnabled()) {mAssistants.onNotificationEnqueuedLocked(r);//處理完之后,延遲post到PostNotificationRunnable線程,請看分析6mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),DELAY_FOR_ASSISTANT_TIME);} else {//處理完之后,post到PostNotificationRunnable線程,請看分析6mHandler.post(new PostNotificationRunnable(r.getKey()));}}}}

上面是把通知添加到 ArrayList<NotificationRecord> mEnqueuedNotifications 列表中,該列表保存了所有待處理的通知,如果通知被取消、超時(shí)、處理完成后也會從該列表移除.

6. PostNotificationRunnable@NMS

(1) 繼續(xù)分析 PostNotificationRunnable 的 run() 方法,該方法主要是通知發(fā)送前的一些處理,

 源碼路徑: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javapublic void run() {synchronized (mNotificationLock) {try {NotificationRecord r = null;int N = mEnqueuedNotifications.size();//遍歷待處理通知列表,如果傳遞過來的key能在列表中存在,則把通知賦值給r,for (int i = 0; i < N; i++) {final NotificationRecord enqueued = mEnqueuedNotifications.get(i);if (Objects.equals(key, enqueued.getKey())) {r = enqueued;break;}}//如果列表中不存在該key通知,就returnif (r == null) {return;}//如果用戶設(shè)置了不接收該通知,也return if (isBlocked(r)) {return;}//判斷應(yīng)用是否被系統(tǒng)限制了,即應(yīng)用程序當(dāng)前是否已暫停。final boolean isPackageSuspended =isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());r.setHidden(isPackageSuspended);if (isPackageSuspended) {//統(tǒng)計(jì)被限制的通知的數(shù)量mUsageStats.registerSuspendedByAdmin(r);}NotificationRecord old = mNotificationsByKey.get(key);final StatusBarNotification n = r.getSbn();final Notification notification = n.getNotification();if (old == null || old.getSbn().getInstanceId() == null) {n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());} else {n.setInstanceId(old.getSbn().getInstanceId());}//判斷通知是新的,還是已存在的通知,主要是通過遍歷 待處理通知列表,如果存在則返回通知在列表的位置,如果是新的通知,則返回-1int index = indexOfNotificationLocked(n.getKey());if (index < 0) {//將新的通知添加到 mNotificaitonList 列表中,mNotificationList.add(r);mUsageStats.registerPostedByApp(r);r.setInterruptive(isVisuallyInterruptive(null, r));} else {//如果已存在該通知,則更新已存在的通知,即更新通知內(nèi)容,key值不變,通知排序也不變old = mNotificationList.get(index);  mNotificationList.set(index, r);mUsageStats.registerUpdatedByApp(r, old);//確保通知更新過程中前臺服務(wù)標(biāo)志丟失notification.flags |=old.getNotification().flags & FLAG_FOREGROUND_SERVICE;r.isUpdate = true;final boolean isInterruptive = isVisuallyInterruptive(old, r);r.setTextChanged(isInterruptive);r.setInterruptive(isInterruptive);}//把通知添加到 列表中,這個(gè)列表在后面有說明mNotificationsByKey.put(n.getKey(), r);//如果是前臺服務(wù)通知,不管應(yīng)用是否設(shè)置常駐標(biāo)志,系統(tǒng)都會強(qiáng)制加上FLAG_ONGOING_EVENT(常駐通知) 和 FLAG_NO_CLEAR(用戶手動(dòng)無法清除) 標(biāo)志,if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {notification.flags |= FLAG_ONGOING_EVENT| FLAG_NO_CLEAR;}mRankingHelper.extractSignals(r);mRankingHelper.sort(mNotificationList);final int position = mRankingHelper.indexOf(mNotificationList, r);int buzzBeepBlinkLoggingCode = 0;if (!r.isHidden()) {//處理通知的震動(dòng),音效和呼吸燈buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);}if (notification.getSmallIcon() != null) {StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;//*****發(fā)送通知,通知各個(gè)listeners,其中就包括了SystemUI,詳情請看分析7mListeners.notifyPostedLocked(r, old);if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))&& !isCritical(r)) {mHandler.post(new Runnable() {@Overridepublic void run() { //構(gòu)建父通知mGroupHelper.onNotificationPosted(n, hasAutoGroupSummaryLocked(n));}});} else if (oldSbn != null) {final NotificationRecord finalRecord = r;mHandler.post(() -> mGroupHelper.onNotificationUpdated(finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));}} else {//由于沒有設(shè)置smallIcon,通知無法發(fā)送,通知listeners移除該通知.if (old != null && !old.isCanceled) {mListeners.notifyRemovedLocked(r,NotificationListenerService.REASON_ERROR, r.getStats());mHandler.post(new Runnable() {@Overridepublic void run() {mGroupHelper.onNotificationRemoved(n);}});}}if (mShortcutHelper != null) {mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,false /* isRemoved */,mHandler);}maybeRecordInterruptionLocked(r);maybeRegisterMessageSent(r);maybeReportForegroundServiceUpdate(r, true);} finally {//該通知已被處理,應(yīng)該把該通知從 待處理通知列表中移除int N = mEnqueuedNotifications.size();for (int i = 0; i < N; i++) {final NotificationRecord enqueued = mEnqueuedNotifications.get(i);if (Objects.equals(key, enqueued.getKey())) {mEnqueuedNotifications.remove(i);break;}}}}}

? ? ? ? 這里從待處理通知 ArrayList<Notification> mEnqueuednotifications 取出通知,經(jīng)過一些列步驟,之后把該通知添加到列表 ArrayMap<String,NotificationRecord> mNotificationsByKey 中, 該列表保存了服務(wù)端中未排序的所有通知,用于確定該通知是更新舊通知還是新類型的通知.最后, mListeners.notifyPostedLocked(r, old); 通知各個(gè)監(jiān)聽通知的listeners 通知更新了, 其中 mListeners 指 NotificationListeners, 它是NotificationManagerService的內(nèi)部類,下面繼續(xù)分析.

7. 通知監(jiān)聽者,通知發(fā)生變化: mListeners.notifyPostedLocked()?

源碼路徑: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javaprivate NotificationListeners mListeners; public class NotificationListeners extends ManagedServices {......private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,boolean notifyAllListeners) {try {StatusBarNotification sbn = r.getSbn();StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;TrimCache trimCache = new TrimCache(sbn);//過濾部分listener,如:不可見用戶,Android P 以下hidden類型的通知 for (final ManagedServiceInfo info : getServices()) {boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);boolean oldSbnVisible = (oldSbn != null)&& isVisibleToListener(oldSbn, old.getNotificationType(), info);//如果通知不可見,則忽略if (!oldSbnVisible && !sbnVisible) {continue;}if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {continue;}//過濾不通知所有監(jiān)聽者,并且版本大于Android P if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {continue;}//構(gòu)建通知映射表,分析請看分析8final NotificationRankingUpdate update = makeRankingUpdateLocked(info);// 移除舊以前可見,現(xiàn)在不可見的通知if (oldSbnVisible && !sbnVisible) {final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();mHandler.post(() -> notifyRemoved(info, oldSbnLightClone, update, null, REASON_USER_STOPPED));continue;}//授權(quán)final int targetUserId = (info.userid == UserHandle.USER_ALL)? UserHandle.USER_SYSTEM : info.userid;updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);final StatusBarNotification sbnToPost = trimCache.ForListener(info);//通知各個(gè)監(jiān)聽器,之后各個(gè)監(jiān)聽器就能收到通知,并對通知做處理了mHandler.post(() -> notifyPosted(info, sbnToPost, update));}} catch (Exception e) {Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);}}

到此, 通過 ?mHandler.post(() -> notifyPosted(info, sbnToPost, update)) 方法將通知傳遞到各個(gè)監(jiān)聽器,其中,在發(fā)送通知給監(jiān)聽器之前,會對通知進(jìn)行排序,然后構(gòu)建通知Map, SystemUI 會根據(jù)這個(gè)map 對通知進(jìn)行排序.

8. 通知發(fā)送前對通知進(jìn)行排序

    /*** 僅對監(jiān)聽器可見的通知進(jìn)行排序,構(gòu)建通知map,* key = StatusBarNotification.getKey();* value = NotificationListenerService.Ranking*/@GuardedBy("mNotificationLock")NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {final int N = mNotificationList.size();final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>();for (int i = 0; i < N; i++) {NotificationRecord record = mNotificationList.get(i);if (isInLockDownMode(record.getUser().getIdentifier())) {continue;}//過濾掉當(dāng)前用戶不可見的通知if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) {continue;}//獲取通知關(guān)鍵字keyfinal String key = record.getSbn().getKey();//根據(jù)每個(gè)關(guān)鍵字對應(yīng)一個(gè) NotificationListenerService.Ranking, 即構(gòu)成通知ArrayMapfinal NotificationListenerService.Ranking ranking =new NotificationListenerService.Ranking();//將通知的關(guān)鍵信息添加到ranking中ranking.populate(key,rankings.size(),!record.isIntercepted(),record.getPackageVisibilityOverride(),record.getSuppressedVisualEffects(),record.getImportance(),record.getImportanceExplanation(),record.getSbn().getOverrideGroupKey(),record.getChannel(),record.getPeopleOverride(),record.getSnoozeCriteria(),record.canShowBadge(),record.getUserSentiment(),record.isHidden(),record.getLastAudiblyAlertedMs(),record.getSound() != null || record.getVibration() != null,record.getSystemGeneratedSmartActions(),record.getSmartReplies(),record.canBubble(),record.isTextChanged(),record.isConversation(),record.getShortcutInfo(),record.getRankingScore() == 0? RANKING_UNCHANGED: (record.getRankingScore() > 0 ?  RANKING_PROMOTED : RANKING_DEMOTED),record.getNotification().isBubbleNotification(),record.getProposedImportance());rankings.add(ranking);}return new NotificationRankingUpdate(rankings.toArray(new NotificationListenerService.Ranking[0]));}

9. 通知的分組

通知組簡介

繼續(xù)分析 4標(biāo)題中 handleGroupedNotificationLocked() 系統(tǒng)處理分組的源碼如下:

    /*** 確保分組通知得到特殊處理** 如果新通知導(dǎo)致組丟失其摘要,則取消組子項(xiàng)。** <p>Updates mSummaryByGroupKey.</p>*/@GuardedBy("mNotificationLock")private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,int callingUid, int callingPid) {StatusBarNotification sbn = r.getSbn();Notification n = sbn.getNotification();if (n.isGroupSummary() && !sbn.isAppGroup())  {// 沒有組的通知不應(yīng)該是摘要,否則自動(dòng)成組可能會導(dǎo)致錯(cuò)誤,分析請看9.(1)n.flags &= ~Notification.FLAG_GROUP_SUMMARY;}String group = sbn.getGroupKey();boolean isSummary = n.isGroupSummary();Notification oldN = old != null ? old.getSbn().getNotification() : null;String oldGroup = old != null ? old.getSbn().getGroupKey() : null;boolean oldIsSummary = old != null && oldN.isGroupSummary();//更新 mSummaryByGroupKey,分析請看3if (oldIsSummary) {NotificationRecord removedSummary = mSummaryByGroupKey.remove(oldGroup);if (removedSummary != old) {String removedKey =removedSummary != null ? removedSummary.getKey() : "<null>";Slog.w(TAG, "Removed summary didn't match old notification: old=" + old.getKey() +", removed=" + removedKey);}}if (isSummary) {mSummaryByGroupKey.put(group, r);}FlagChecker childrenFlagChecker = (flags) -> {if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {return false;}return true;};// 如果更新導(dǎo)致組摘要消失,則清除舊通知的組子項(xiàng)。當(dāng)舊通知是摘要而新通知不是摘要時(shí),// 或者當(dāng)舊通知是摘要并且其groupKey發(fā)生更改時(shí),則原來父通知下的所有子通知會被移除if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,childrenFlagChecker, REASON_APP_CANCEL, SystemClock.elapsedRealtime());}}

(1) 如果 setGroupSummary(boolean isGroupSummary)設(shè)置了Notification.FLAG_GROUP_SUMMARY這個(gè)flag,但是沒有調(diào)用setGroup(String groupKey)設(shè)置對應(yīng)的groupKey, 則Notification.FLAG_GROUP_SUMMARY這個(gè)flag會被去掉,否則會導(dǎo)致后續(xù)系統(tǒng)的自動(dòng)成組導(dǎo)致出錯(cuò)。

10. 使用規(guī)則更新通知屬性值(排序前更新)

源碼路徑:frameworks/base/services/core/java/com/android/server/notification/RankingConfig.javapublic interface RankingConfig {void setImportance(String packageName, int uid, int importance);int getImportance(String packageName, int uid);void setShowBadge(String packageName, int uid, boolean showBadge);boolean canShowBadge(String packageName, int uid);boolean badgingEnabled(UserHandle userHandle);int getBubblePreference(String packageName, int uid);boolean bubblesEnabled(UserHandle userHandle);boolean isMediaNotificationFilteringEnabled();boolean isGroupBlocked(String packageName, int uid, String groupId);boolean canShowNotificationsOnLockscreen(int userId);boolean canShowPrivateNotificationsOnLockScreen(int userId);Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,int uid);void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,boolean fromTargetApp);ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess);void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,boolean fromUser);NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,boolean includeDeleted);NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId, String conversationId,  boolean returnParentIfNoConversationChannel,boolean includeDeleted);boolean deleteNotificationChannel(String pkg, int uid, String channelId);void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);void permanentlyDeleteNotificationChannels(String pkg, int uid);ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,boolean includeDeleted);
}

上面是規(guī)則接口類,下面分析該接口的實(shí)現(xiàn)類,舉例通知圓點(diǎn)進(jìn)行說明:

源碼路徑: frameworks/base/services/core/java/com/android/server/notification/PreferencesHelper.javapublic class PreferencesHelper implements RankingConfig {......@Overridepublic boolean canShowBadge(String packageName, int uid) {synchronized (mPackagePreferences) {return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;}}//設(shè)置某個(gè)應(yīng)用的通知圓點(diǎn)開關(guān),開啟或者關(guān)閉@Overridepublic void setShowBadge(String packageName, int uid, boolean showBadge) {synchronized (mPackagePreferences) {getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge;}updateConfig();//更新屬性配置}......(1) 兩個(gè)方法中都調(diào)用了同一個(gè)方法 getOrCreatePackagePreferencesLocked(),private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,@UserIdInt int userId, int uid, int importance, int priority, int visibility,boolean showBadge, int bubblePreference) {final String key = packagePreferencesKey(pkg, uid);PackagePreferences r = (uid == UNKNOWN_UID)? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId)): mPackagePreferences.get(key);if (r == null) {r = new PackagePreferences();r.pkg = pkg;r.uid = uid;r.importance = importance;r.priority = priority;r.visibility = visibility;r.showBadge = showBadge;r.bubblePreference = bubblePreference;if (mOemLockedApps.containsKey(r.pkg)) {List<String> channels = mOemLockedApps.get(r.pkg);if (channels == null || channels.isEmpty()) {r.oemLockedImportance = true;} else {r.oemLockedChannels = channels;}}try {createDefaultChannelIfNeededLocked(r);} catch (PackageManager.NameNotFoundException e) {Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);}if (r.uid == UNKNOWN_UID) {mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);} else {mPackagePreferences.put(key, r);}}return r;}(2) 該方法返回 PackagePreferences 對象,它是PreferencesHelper.java的內(nèi)部類,接著看下該對象有哪些屬性:private static class PackagePreferences {String pkg;int uid = UNKNOWN_UID;int importance = DEFAULT_IMPORTANCE;//通知重要性int priority = DEFAULT_PRIORITY; //通知優(yōu)先級int visibility = DEFAULT_VISIBILITY; //通知可見性boolean showBadge = DEFAULT_SHOW_BADGE; //通知原點(diǎn)int bubblePreference = DEFAULT_BUBBLE_PREFERENCE; //通知?dú)馀輎nt lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;List<String> oemLockedChannels = new ArrayList<>();boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;boolean hasSentInvalidMessage = false;boolean hasSentValidMessage = false;boolean userDemotedMsgApp = false;Delegate delegate = null;ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();public boolean isValidDelegate(String pkg, int uid) {return delegate != null && delegate.isAllowed(pkg, uid);}}(3)該內(nèi)部類對象保存了通知的一些屬性,是通知屬性的封裝類,如上面兩個(gè)方法中,
都用到了getOrCreatePackagePreferencesLocked(packageName, uid).showBadge 
來獲取通知是否開啟通知原點(diǎn)功能, 該方法相當(dāng)于是通過 PackagePreferences.showBadge 
獲取屬性值,之后便可以通過PreferencesHelper 來獲取通知最新的屬性.

通過 設(shè)置 或者 桌面快捷方式 可以打開通知圓點(diǎn)功能,請求會從 設(shè)置 跨進(jìn)程發(fā)送到NotificationManagerService(NMS), NMS 會通過setShowBadge()@PreferencesHelper來更新屬性,并把最新屬性值保存到PreferencesHelper對象中.

http://www.risenshineclean.com/news/268.html

相關(guān)文章:

  • wordpress 時(shí)尚主題/駐馬店百度seo
  • 嘉興網(wǎng)站排名優(yōu)化價(jià)格/北京網(wǎng)站
  • 網(wǎng)站建設(shè)部門/網(wǎng)站軟件下載
  • 建立收費(fèi)網(wǎng)站/網(wǎng)絡(luò)銷售面試問題有哪些
  • 做搞基視頻網(wǎng)站/網(wǎng)絡(luò)營銷的案例有哪些
  • 網(wǎng)站怎么做下載連接/百度長尾關(guān)鍵詞挖掘
  • 網(wǎng)站彈窗客服代碼/刷推廣鏈接
  • 政府網(wǎng)站公眾號建設(shè)方案/谷歌瀏覽器 安卓下載2023版
  • wordpress tutorial/seo優(yōu)化排名服務(wù)
  • 網(wǎng)站被谷歌降權(quán)/廣州seo招聘網(wǎng)
  • 網(wǎng)站聊天怎么做/最新軍事報(bào)道
  • 成都微信端網(wǎng)站建/蘇州seo按天扣費(fèi)
  • 網(wǎng)站沒有百度快照/全網(wǎng)絡(luò)品牌推廣
  • 制作企業(yè)網(wǎng)站需要注意的事項(xiàng)/地推是什么
  • 資料填寫網(wǎng)站類型怎么做/新聞發(fā)稿公司
  • 免費(fèi)建網(wǎng)站撫順/win10優(yōu)化大師有用嗎
  • 萬盛網(wǎng)站建設(shè)公司/當(dāng)下最流行的營銷方式
  • 下載好看影視大全極速版/seo是什么工作內(nèi)容
  • 重慶響應(yīng)式網(wǎng)站建設(shè)公司/哪個(gè)軟件可以自動(dòng)排名
  • python源碼分享網(wǎng)站/深度搜索
  • 龍華網(wǎng)站建設(shè)方案表/免費(fèi)海報(bào)模板網(wǎng)站
  • 關(guān)鍵詞seo優(yōu)化/優(yōu)化大師官方免費(fèi)下載
  • 百度指數(shù) 網(wǎng)站/杭州優(yōu)化公司哪家好
  • 哈爾濱市建設(shè)網(wǎng)站/寧波網(wǎng)絡(luò)推廣產(chǎn)品服務(wù)
  • 湛江網(wǎng)站建設(shè)哪家好/網(wǎng)絡(luò)營銷公司全網(wǎng)推廣公司
  • 個(gè)人可以做淘寶客網(wǎng)站嗎/網(wǎng)絡(luò)營銷首先要進(jìn)行
  • 免費(fèi)制作單頁的網(wǎng)站/媒體推廣
  • 嘉興網(wǎng)站搭建/軟文發(fā)布平臺哪個(gè)好
  • 專做品牌的網(wǎng)站/seo專員招聘
  • 怎么在網(wǎng)站里做關(guān)鍵詞優(yōu)化/小程序開發(fā)多少錢