以下代碼以compileVersion=28
作為示例來演示
-
添加NotificationChannel(必需)
當(dāng)
compileVersion>=26
且Notification沒有設(shè)置channelId時(shí),8.0的系統(tǒng)上通知不會彈出材蛛,在logcat的error級別顯示NotificationService提示日志:No Channel found for pkg=aaa, channelId=null,...notification=Notification(channel=null ...)
可以使用兩種方式給Notification對象添加channelId:
NotificationCompat.Builder(context, channelId)...build()
或者build.setChannelId(channelId)...
-
NotificationChannel
創(chuàng)建: NotificationChannel(String channelId, CharSequence name, @Importance int importance)-
channelId
build時(shí)設(shè)置給Notification的id -
name
顯示在通知管理里的標(biāo)題 -
importance
此通道的重要性,5個(gè)等級范圍
-
在通知被
notify(notification)
之前必須確保通知的NotificationChannel已經(jīng)被注冊怎抛,api: createNotificationChannel(channel)
-
機(jī)型適配
-
有的手機(jī)在添加channel后仍然無法彈出通知卑吭。追蹤logcat發(fā)現(xiàn)有這么一句:
E/NotificationService: Suppressing notification from package by user request.
用戶請求抑制此通知?追蹤notify源碼找到
NotificationManagerService
的enqueueNotificationInternal(......)
方法: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 (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r)) {return;} ... mHandler.post(new EnqueueNotificationRunnable(userId, r)); } private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,NotificationRecord r) { ...// blocked apps if (isBlocked(r, mUsageStats)) {return false;} return true; } protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) { final String pkg = r.sbn.getPackageName(); final int callingUid = r.sbn.getUid(); final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid); if (isPackageSuspended) { Slog.e(TAG, "Suppressing notification from package due to package suspended by administrator."); usageStats.registerSuspendedByAdmin(r); return isPackageSuspended; } final boolean isBlocked = mRankingHelper.getImportance(pkg, callingUid) == NotificationManager.IMPORTANCE_NONE || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE; if (isBlocked) { Slog.e(TAG, "Suppressing notification from package by user request."); usageStats.registerBlocked(r); } return isBlocked; }
最終的
isBlocked
判斷條件滿足马绝,導(dǎo)致notify操作被中斷return豆赏。 -
目前為止國產(chǎn)rom現(xiàn)狀是:
- 通知總權(quán)限在華為EMUI/魅族flyme/原生系統(tǒng)上默認(rèn)是打開的,MIUI/VIVO/OPPO是默認(rèn)關(guān)閉的
-
渠道開關(guān)在OPPO手機(jī)上是默認(rèn)關(guān)閉的富稻,在開啟總權(quán)限后還需要開啟相關(guān)的類別(對應(yīng)channel的name)才能正常使用掷邦。而測試的其他手機(jī)在開啟總開關(guān)后自動開啟channelId的通知開關(guān)。
-
這么檢測通知權(quán)限:
public static boolean isNotificationEnabled(Context context,String channelId) { NotificationManagerCompat managerCompat = NotificationManagerCompat.from(context); NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); boolean returnValue = managerCompat.areNotificationsEnabled(); if(manager == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.O){ return returnValue; } NotificationChannel channel = manager.getNotificationChannel(channelId); if(channel == null){ channel = new NotificationChannel(channelId,"我的推送類別",NotificationManager.IMPORTANCE_HIGH); manager.createNotificationChannel(channel); 下面的獲取操作必需椭赋,創(chuàng)建的channel和獲取到的channel的IMPORTANCE可能不一樣抚岗,OPPO默認(rèn)IMPORTANCE_NONE。 channel = manager.getNotificationChannel(channelId); } return returnValue && channel.getImportance() != NotificationManager.IMPORTANCE_NONE; }
-
跳轉(zhuǎn)通知管理頁面的代碼:
boolean isNotifyEnable = NotificationManagerCompat.from(context).areNotificationsEnabled(); boolean isChannelEnable = true; if (Build.VERSION.SDK_INT >= 26) { isChannelEnable = channel.getImportance() != NotificationManager.IMPORTANCE_NONE; } if (isNotifyEnable && isChannelEnable) { manager.notify(notifyId, notification); 正常notify } else if (!isNotifyEnable) { Intent intent = new Intent(); if(!(context instanceOf Activity)){ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } if (Build.VERSION.SDK_INT >= 26) { intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); context.startActivity(intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())); }else if (Build.VERSION.SDK_INT >= 21) { intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); context.startActivity(intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName())); } else if (Build.VERSION.SDK_INT >= 9) { intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.fromParts("package", getPackageName(), null)); context.startActivity(intent); } else {低于9沒有適配必要} }else{ 只打開了通知開關(guān),但是關(guān)閉了當(dāng)前channel的通知,開發(fā)者需要根據(jù)通知重要性,自行決定如何提示用戶 }
- 查看api21與api26的源碼發(fā)現(xiàn)
Settings. ACTION_APP_NOTIFICATION_SETTINGS
的值其實(shí)就是"android.settings. APP_NOTIFICATION_SETTINGS"
,只是在26前這個(gè)常量是隱藏的肚逸。因此上述代碼可簡化掉api26那部分。 -
創(chuàng)建的NotificationChannel和notify時(shí)獲取的NotificationChannel可能是不同的胚委。因?yàn)樵趕ervice層保存的實(shí)現(xiàn)方法國產(chǎn)rom做了更改。以防萬一叉信,請使用
manager.getNotificationChannel(channelId)
生成的NotificationChannel
對象 - 測試時(shí)發(fā)現(xiàn)vivo X21有兩個(gè)通知管理頁面:
dumpsys activity | grep -i run
找到落地頁(包名:com.android.systemui Activity名:com.vivo.systemui.statusbar.notification.settings.channels.NotificationSettingsActivity) 跑了下崩了,提示Permission Denial: starting Intent{...}not exported from uid 10023
硼身。activity沒有設(shè)置為exported鉴未,有空了看看源碼是否有破解方法...
- 查看api21與api26的源碼發(fā)現(xiàn)
-
-
CompileSdkVersion<26時(shí)的機(jī)型適配
- NotificationChannel這個(gè)API是在安卓8.0引入枢冤,所以當(dāng)編譯版本低于26時(shí),不能加入channel铜秆,但是經(jīng)過測試在vivo的安卓8.0手機(jī)上提示
Suppressing notification from package by user request
通知無法彈出 - areNotificationsEnabled 在安卓7.0(api24)加入,對應(yīng)的support支持包最低v7:24.0.0讶迁。如果編譯版本不低于api24连茧,做如下適配:
boolean isNotifyEnable = NotificationManagerCompat.from(this).areNotificationsEnabled(); if(isNotifyEnable){ manager.notify(notifyId,notification); }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { startActivity(new Intent("android.settings.APP_NOTIFICATION_SETTINGS") .putExtra("android.provider.extra.APP_PACKAGE", getPackageName())); } else/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) */{ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.fromParts("package", getPackageName(), null)); startActivity(intent); } 考慮到目前app沒人再適配android 2.x,因此最后一個(gè)else省略
- 如果編譯版本低于24,可參考高版本api自行實(shí)現(xiàn)巍糯,這里就不貼出了啸驯。目前各應(yīng)用商店都在強(qiáng)制提升targetVersion,以后如果是上應(yīng)用市場的app不會再有低于26的編譯版本了
- NotificationChannel這個(gè)API是在安卓8.0引入枢冤,所以當(dāng)編譯版本低于26時(shí),不能加入channel铜秆,但是經(jīng)過測試在vivo的安卓8.0手機(jī)上提示