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

以下代碼以compileVersion=28作為示例來演示

  • 添加NotificationChannel(必需)

    • 當(dāng)compileVersion>=26且Notification沒有設(shè)置channelId時,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è)置給Notification的id
      • name顯示在通知管理里的標(biāo)題
      • importance此通道的重要性,5個等級范圍
    • 在通知被notify(notification)之前必須確保通知的NotificationChannel已經(jīng)被注冊,api: createNotificationChannel(channel)

  • 機型適配

    • 有的手機在添加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手機上是默認(rèn)關(guān)閉的,在開啟總權(quán)限后還需要開啟相關(guān)的類別(對應(yīng)channel的name)才能正常使用。而測試的其他手機在開啟總開關(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 的值其實就是 "android.settings. APP_NOTIFICATION_SETTINGS",只是在26前這個常量是隱藏的。因此上述代碼可簡化掉api26那部分。
      • 創(chuàng)建的NotificationChannel和notify時獲取的NotificationChannel可能是不同的。因為在service層保存的實現(xiàn)方法國產(chǎn)rom做了更改。以防萬一,請使用manager.getNotificationChannel(channelId)生成的NotificationChannel對象
      • 測試時發(fā)現(xiàn)vivo X21有兩個通知管理頁面:
        右邊明顯很漂釀啊有木有
        右側(cè)的設(shè)計很好看,使用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時的機型適配

    • NotificationChannel這個API是在安卓8.0引入,所以當(dāng)編譯版本低于26時,不能加入channel,但是經(jīng)過測試在vivo的安卓8.0手機上提示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,因此最后一個else省略
      
    • 如果編譯版本低于24,可參考高版本api自行實現(xiàn),這里就不貼出了。目前各應(yīng)用商店都在強制提升targetVersion,以后如果是上應(yīng)用市場的app不會再有低于26的編譯版本了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容