安卓的通知适配(更新至9.0)

以下代码以compileVersion=28作为示例来演示

  • 添加NotificationChannel(必需)

    • compileVersion>=26且Notification没有设置channelId时,8.0的系统上通知不会弹出,在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创建: NotificationChannel(String channelId, CharSequence name, @Importance int importance)

      • channelIdbuild时设置给Notification的id
      • name显示在通知管理里的标题
      • importance此通道的重要性,5个等级范围
    • 在通知被notify(notification)之前必须确保通知的NotificationChannel已经被注册,api: createNotificationChannel(channel)

  • 机型适配

    • 有的手机在添加channel后仍然无法弹出通知。追踪logcat发现有这么一句:

      E/NotificationService: Suppressing notification from package by user request.
      

      用户请求抑制此通知?追踪notify源码找到NotificationManagerServiceenqueueNotificationInternal(......)方法:

      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判断条件满足,导致notify操作被中断return。

    • 目前为止国产rom现状是:

      • 通知总权限在华为EMUI/魅族flyme/原生系统上默认是打开的,MIUI/VIVO/OPPO是默认关闭的
      • 渠道开关在OPPO手机上是默认关闭的,在开启总权限后还需要开启相关的类别(对应channel的name)才能正常使用。而测试的其他手机在开启总开关后自动开启channelId的通知开关。
    • 这么检测通知权限:

      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);
      
              下面的获取操作必需,创建的channel和获取到的channel的IMPORTANCE可能不一样,OPPO默认IMPORTANCE_NONE。
              channel = manager.getNotificationChannel(channelId);
          }
          return returnValue && channel.getImportance() != NotificationManager.IMPORTANCE_NONE;
      }
      
    • 跳转通知管理页面的代码:

      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{
         只打开了通知开关,但是关闭了当前channel的通知,开发者需要根据通知重要性,自行决定如何提示用户
      }
      
      • 查看api21与api26的源码发现Settings. ACTION_APP_NOTIFICATION_SETTINGS 的值其实就是 "android.settings. APP_NOTIFICATION_SETTINGS",只是在26前这个常量是隐藏的。因此上述代码可简化掉api26那部分。
      • 创建的NotificationChannel和notify时获取的NotificationChannel可能是不同的。因为在service层保存的实现方法国产rom做了更改。以防万一,请使用manager.getNotificationChannel(channelId)生成的NotificationChannel对象
      • 测试时发现vivo X21有两个通知管理页面:
        右边明显很漂酿啊有木有
        右侧的设计很好看,使用shell 命令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没有设置为exported,有空了看看源码是否有破解方法...
  • CompileSdkVersion<26时的机型适配

    • NotificationChannel这个API是在安卓8.0引入,所以当编译版本低于26时,不能加入channel,但是经过测试在vivo的安卓8.0手机上提示Suppressing notification from package by user request 通知无法弹出
    • areNotificationsEnabled 在安卓7.0(api24)加入,对应的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,因此最后一个else省略
      
    • 如果编译版本低于24,可参考高版本api自行实现,这里就不贴出了。目前各应用商店都在强制提升targetVersion,以后如果是上应用市场的app不会再有低于26的编译版本了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359