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

以下代碼以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)

      • channelIdbuild時(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源碼找到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判斷條件滿足马绝,導(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è)通知管理頁面:
        右邊明顯很漂釀啊有木有
        右側(cè)的設(shè)計(jì)很好看亩冬,使用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沒有設(shè)置為exported鉴未,有空了看看源碼是否有破解方法...
  • 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的編譯版本了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祟峦,一起剝皮案震驚了整個(gè)濱河市罚斗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宅楞,老刑警劉巖针姿,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異厌衙,居然都是意外死亡距淫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門婶希,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榕暇,“玉大人,你說我怎么就攤上這事喻杈⊥啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵筒饰,是天一觀的道長缴啡。 經(jīng)常有香客問我,道長龄砰,這世上最難降的妖魔是什么盟猖? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮换棚,結(jié)果婚禮上式镐,老公的妹妹穿的比我還像新娘。我一直安慰自己固蚤,他們只是感情好娘汞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夕玩,像睡著了一般你弦。 火紅的嫁衣襯著肌膚如雪惊豺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天禽作,我揣著相機(jī)與錄音尸昧,去河邊找鬼。 笑死旷偿,一個(gè)胖子當(dāng)著我的面吹牛烹俗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萍程,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼幢妄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茫负?” 一聲冷哼從身側(cè)響起蕉鸳,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忍法,沒想到半個(gè)月后潮尝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缔赠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年衍锚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗤堰。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戴质,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出踢匣,到底是詐尸還是另有隱情告匠,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布离唬,位于F島的核電站后专,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏输莺。R本人自食惡果不足惜戚哎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫂用。 院中可真熱鬧型凳,春花似錦、人聲如沸嘱函。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疏唾,卻和暖如春蓄氧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槐脏。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工喉童, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顿天。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓泄朴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親露氮。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348