Android通知欄介紹與適配總結

因爲歷史緣由,Android在發佈之初對通知欄Notification的設計至關簡單,而現在面對各式各樣的通知欄玩法,谷歌也不得不對其進行更新迭代調整,增長新功能的同時,也在不斷地改變樣式,試圖迎合更多人的口味。本文總結了Android通知欄的版本迭代過程,在通知欄開發過程當中所遇到的各類各樣的坑,以及一些解決技巧,特別的,對於大衆期盼的Android 7.0的到來,通知欄又會發生怎樣的改變呢?接下來一一進行介紹。html

Android通知欄發展歷史
首先來看一張各個Android版本通知欄消息的全家福。java

Drawingandroid

(點擊查看大圖)api

Android通知欄從最初的Android1.1系統一直到現在的7.X版本,發生了翻天覆地的變化。從圖中能夠看出,1.X-2.2版本的通知欄採用了白色背景和黑色字體;2.3-4.X版本,默認背景變成了黑色,而主標題採用白色字體,內容爲灰色字體。從Android5.0開始,又更改成白色背景和黑色字體。固然,這只是原生的Android系統通知欄默認顏色,許多廠商對每一個Android的版本都嘗試了各式各樣的修改,在此不一一介紹。緩存

下面分別介紹每一個版本的更新和修改記錄。微信

Android 1.X 修改記錄^1
Android 1.X版本也就是第一個Android誕生的版本。從Android1.1版本開始,提供基本的通知欄消息功能,包含小圖標、主標題、副標題和時間這四個元素。右上角有一個清除通知欄消息的按鈕。須要說明的是,Android從一開始就提供了清除通知欄消息的功能而且保留至今,而iOS到如今都沒有提供清除按鈕。app

Android 2.X 修改記錄^2
Android 2.X版本的通知欄消息功能上並未發生變化,右上角的「clear notifications」縮減爲了「clear」。2.2版本之前沿用了1.5的通知欄樣式,從2.3版本開始從新設計,改爲了暗色背景。ide

Android 3.X 修改記錄^3
Android 3.X版本是專爲Pad而設計的系統。通知欄消息帶來了一些新的功能。佈局

非永久的通知欄消息的右邊增長了「X」按鈕,點擊後該條通知能夠當即清除。
增長了RemoteControlClient,即遠程控制媒體應用的功能。
增長了LargeIcon,可使用大圖展現通知欄消息。
Android 4.1 修改記錄^4
Android 4.1版本的通知欄在3.X版本的基礎上進行了大量修改。增長了很多新功能。測試

增長了Style
增長了通知欄按鈕
支持通知欄展現的優先級配置
通知欄背景改成黑色透明
通知欄樣式
Android 4.1通知欄最大的變化就是增長了豐富多樣的Style樣式。經過設置樣式,能夠展現更大區域的通知消息,如展現大圖和多行文字,也能夠展現相似郵箱收發信的樣式,同時支持自定義按鈕並增長點擊事件。但須要注意的是,只有最頂部的那條通知欄消息能夠默認展現Style樣式,其餘消息默認是以普通樣式展現。Style能夠經過Notification.Builder.setStyle(Style)進行設置。具體支持的樣式有:

Notification.BigPictureStyle
大圖樣式,即除了普通的通知欄消息內容外,能夠在通知欄消息下方展現一張大圖,最大高度支持256dp。

Notification.BigTextStyle
多行文字樣式,能夠支持多行文字的展現。經測試,在不一樣手機上可以支持的行數不同,測試過的機子,最大支持12行。

Notification.InboxStyle
收件箱樣式。支持展現具備一串消息內容的會話樣式,適用於短信、郵件、IM等。

通知欄按鈕
通知欄消息無論是普通樣式仍是Style樣式,都支持兩個按鈕同時出如今一條通知欄消息的底部,經過這兩個按鈕,能夠自定義一系列動做,包括回覆信息和郵件,點贊等。經過Notification.Builder.addAction(Action)添加按鈕。

通知欄優先級
Android 4.1通知欄增長了優先級的配置,優先級高的消息能夠展現在最上方。谷歌設計優先級的初衷是根據不一樣的優先級來防止用戶成天被各類莫名其妙的通知欄消息騷擾,重要的通知則應該適當提升優先級,使得用戶能夠快速地看到並回應,不重要的通知則下降優先級,防止用戶被打擾。優先級一共有5個級別,分別是:

// 默認優先級
public static final int PRIORITY_DEFAULT = 0;
// 低優先級
public static final int PRIORITY_LOW = -1;
// 最低優先級
public static final int PRIORITY_MIN = -2;
// 高優先級
public static final int PRIORITY_HIGH = 1;
// 最高優先級
public static final int PRIORITY_MAX = 2;
Android 4.3 修改記錄^6
Android 4.3通知欄沒有發生大的變化。主要增長了兩個小功能。

增長了Notification AccessApi,容許可穿戴設備遠程控制通知欄消息。
增長了NotificationListenerService,容許接收到系統通知欄列表的變化
Android 5.X 修改記錄^7
Android 5.X系統相較於之前的版本,能夠說是一個真正能夠和iOS抗衡的系統。材料設計給Android系統注入了新的活力,相應的通知欄消息也相較於上一個版本進行了改版。所發生的變化有:

通知欄修改成白色背景,暗色字體,以適應材料設計風格。
系統會忽略全部non-alpha通道的圖標,包括按鈕圖標和主圖標。
能夠經過setColor()方法在圖標後設置一個背景色。
通知消息的聲音將經過STREAM_RING或者STREAM_NOTIFICATION控制,之前是經過STREAM_MUSIC控制。
鎖屏狀態下,能夠控制通知欄消息的隱私程度。
移除了RemoteControlClient,更改成NotificationCompat.MediaStyle實現。
增長了Heads-up通知,即經過狀態欄浮動窗口展現通知消息。
Android 6.X 修改記錄^8
移除了Notification.setLatestEventInfo()方法,經過持有Notification.Builder,而後使用build()方法能夠更新同一個通知欄實例。
容許用戶控制應用通知的優先級。
加入了免打擾模式(Do Not Disturb)。
增長了getActiveNotifications()方法獲取當前展現的通知消息。
Android 7.X 修改記錄
通知欄樣式全面改版,小圖標在左上角,大圖標在右邊,小圖標、App應用名、副標題、數量和時間在第一行,第二行是主標題,第三行是內容。
增長了Notification.DecoratedCustomViewStyle()和Notification.DecoratedMediaCustomViewStyle(),幫助更好的裝飾帶有RemoteViews的通知欄消息。
須要動態設置Builder.setShowWhen(true)纔會顯示時間。
支持Action的直接回復,經過RemoteInput實現,且回覆的消息內容支持當即添加到通知欄。
支持通知消息組,類似的消息在達到必定數量後會按照消息組來顯示。
增長了NotificationManager.areNotificationsEnabled告知應用是否開啓了通知權限。
Android通知欄踩坑與填坑指南
魅族5.X手機,大圖顯示問題
問題詳情
Flyme系統對原生Android源碼作了修改,採用BigPictureStyle方式顯示大圖通知欄的時候,消息與大圖重合了,以下圖。

Drawing

解決方案
首先說一下爲何會有解決方案。展現大圖這個功能開發完成後,拿去給產品演示。碰巧產品的機型就是一魅族手機T_T,結果固然是不能接受的,而後又一個巧合的事情出現了,那就是產品的手機裏,京東App推了一條帶大圖的廣告,他們竟然可以解決這個問題!因而,我開始研究解決方案。

首先,經過BigPictureStyle來實現大圖功能確定是走不通的,由於事實就擺着行不通的嘛。京東的App確定是經過RemoteViews來實現的。因而,開始走彎路,嘗試經過RemoteViews來展現大圖。可是谷歌規定,自定義佈局展現的通知欄消息最大高度是64dp。那麼,京東的App是怎麼實現的?在嘗試了各類方法之後,最後又是經過投機取巧的方式解決了問題:

private void showBigPictureNotificationWithMZ(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context);
Notification notification = generateNotification(builder);
notification.bigContentView = mRemoteViews;
notificationManager.notify(notifyId, notification);
}
須要先生成Notification的實例,而後手動給notification.bigContentView賦值,再notify,就能夠了

頂部狀態欄(StatusBar)小圖標顯示異常
問題詳情
當通知來的時候,若是不在通知欄瀏覽,會在頂部狀態欄出現一個向上翻滾動畫的通知消息,這條通知消息左邊是一個小圖標。部分系統這個小圖標顯示異常,是一個純灰色的正方形,以下圖。

Drawing

解決方案
首先產生灰色圖標的緣由就是5.0系統引入了材料設計,谷歌強制使用帶有alpha通道的圖標,而且RGB的alpha值必須是0(實測不爲0也是能夠的,但系統會忽略全部RGB值)。所以,使用JPG的圖片是不行的,最好的代替方案就是一張背景透明的PNG圖片。

Android 7.X機型,通知欄小圖標顯示成灰色
問題詳情
這個問題跟第二個有點相似,在7.0系統及以上,有部分應用的小圖標是灰色的,大圖能夠正常顯示。碰巧的是,顯示異常的小圖標,顏色都是灰色的。

Drawing

解決方案
與小圖標顯示異常解決方案相似,將小圖標替換爲透明背景的PNG圖片。

RemoteViews顯示異常
問題詳情
因爲系統提供的通知欄消息類型有時候不能知足要求,部分通知欄消息採用自定義RemoteViews來實現。採用RemoteViews,特別是手動生成Bitmap而後直接傳給一個自定義Layout,再經過setContentView方式設置通知欄消息時,會存在各類各樣的坑。

Android通知欄的背景色有幾種狀況,白色、暗色、暗色透明和黑色。若是生成的Bitmap帶背景色,這個背景色就很難選擇。若是選擇黑色背景,那麼在白色通知欄的機型上就很難看。所以不能徹底在各個系統上面完美展現出來。若是不帶背景色,那麼字體顏色也面臨一樣的困惑。試想,若是在白色的背景上顯示白色的文字,用戶看到白茫茫一片,是什麼感覺?

Drawing

另外一方面,大部分廠商對原生的Android系統都會有各類各樣的改造,通知欄的樣式也不例外。若是按照原生的樣式來設計,那麼在大部分國內廠商的機子上顯示都和正常的普統統知欄消息不同。例如華爲6.0系統的機子,原生系統的時間線在右上角,華爲的在左邊,這樣會給用戶帶來錯覺。

Drawing

解決方案
詳見RemoteViews適配一節。

大尺寸小圖標在部分機型上顯示不正確
問題詳情
這個問題主要在部分機型的4.X系統上碰見,小圖標大小沒有按照24dp裁剪,而是採用了桌面圖標同樣的大小96dp。具體適配不正常的機型有HTC Desire 820、Lenovo A320T。

Drawing

解決方案
按照標準來,小圖標大小爲24dp,大圖標爲桌面icon圖標大小96dp。具體可參考這裏^14

部分機型不支持Style
具體機型見下圖以及後面統計的表格。順便提下,小米是其中之一,不知道他們爲何不支持額外的這些Style。

Drawing

(點擊查看大圖)

通知欄更新頻率
問題詳情
每一個應用基本都有自更新的邏輯,App開機的時候提示用戶升級,點擊升級按鈕後在Notification出現一個下載帶進度條的通知。應用通常是在開啓一個工做線程在後臺下載,而後在下載的過程當中經過回調更新通知欄中的進度條。咱們知道,下載進度的快慢是不可控的,若是每次下載中的回調都去更新通知欄,那麼可能幾百毫秒、幾十毫秒、甚至幾毫秒就更新一次通知欄,應用可能就會ANR,甚至崩潰。

解決方案
控制通知欄更新頻率,通常控制在0.5s或者1s就能夠了。在某一個更新時間間隔內下載的進度回調直接丟棄,須要注意的是下載完成的回調,須要實時回調通知欄消息顯示下載完成。

噁心的後臺通知和「守護」通知
問題詳情
這個坑我不肯多介紹,只說結果。但凡存在後臺通知或者「守護」通知的應用,在7.0系統之後都會原形畢露。尚未適配7.0的應用,可長點心兒吧~

Drawing Drawing

解決方案
請棄坑。

小米推送SDK接入問題
問題詳情
爲了提高推送到達,考拉接入了小米推送的SDK。小米推送分爲通知欄消息和透傳消息,通知欄消息屬於系統級推送,在MIUI的機子上能夠在進程被殺死的狀況下也能收到應用推送。然而有個問題,小米認爲應用在前臺時,不會回調任何方法;小米認爲應用在後臺的時候,收到通知欄消息的同時,會回調onNotificationMessageArrived方法。這時候就要當心翼翼地處理這條消息了。由於若是你的應用先後臺判斷邏輯和小米的不同,那麼就有可能小米幫你發了一條通知欄消息,你本身又發了一遍,形成通知欄消息的重複發送(這個坑考拉踩過T_T)。另外一方面,在7.0系統的機子上,主標題和小圖標的顏色是能夠改變的,目前小米推送SDK沒有開放這個接口供調用方定製。

解決方案
目前只能解決第一個問題——先後臺判斷的問題。應用是否在後臺能夠根據如下代碼進行判斷。在Android 5.0以上,能夠經過ActivityManager.RunningAppProcessInfo判斷,Android 5.0及如下版本經過ActivityManager.RunningTaskInfo判斷。經測試,這個方案在Android 4.4以上結果是能夠徹底匹配的。

public static boolean isAppInBackgroundInternal(Context context) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();
if (!ListUtils.isEmpty(runningProcesses)) {
for (ActivityManager.RunningAppProcessInfo runningProcess : runningProcesses) {
if (runningProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return false;
}
}
}
} else {
List<ActivityManager.RunningTaskInfo> task = manager.getRunningTasks(1);
if (!ListUtils.isEmpty(task)) {
ComponentName info = task.get(0).topActivity;
if (null != info) {
return !isKaolaProcess(info.getPackageName());
}
}
}
return true;
}
Android通知欄適配
RemoteViews適配
因爲系統自帶的通知欄消息樣式不能徹底知足產品們腦洞大開的需求,有時候咱們須要自定義佈局樣式展現通知欄消息。Android系統能夠將自定義佈局經過setContent(7.X系統推薦使用setCustomContentView)設置到Notification.Builder中,來實現樣式的更變。setContent方法須要傳入一個RemoteViews對象,它是一個普通的數據類型,不是View,做用是供其餘進程展現視圖。RemoteViews只支持4種基本的佈局^9:

FrameLayout
LinearLayout
RelativeLayout
GridLayout
這些佈局下面只支持幾種視圖控件:

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
只能經過上述組合生成一個RemoteViews。

自定義佈局與視圖
除了上面提到的佈局與控件,有沒有辦法自定義佈局與視圖呢?咱們知道,任何一個View,均可以生成一個Bitmap對象,支持的視圖控件裏有ImageView,能夠經過ImageView.setBitmapResource()將自定義視圖設置到一個ImageView中,而後再隨便放到一個佈局上,就能夠實現通知欄消息的任意佈局。理想是美好的,但現實是殘酷的。使用這種方式自定義的佈局,會存在與原生的通知欄消息樣式不一致的可能,包括小圖標/大圖標的大小,字體的大小與顏色,時間的顯示方式(不一樣版本的時間顯示位置和樣式都不同)。下面解決一個最關鍵,也最致命的問題——字體顏色。若是字體顏色和背景顏色同樣,那這條通知欄消息就無法看了,如RemoteViews顯示異常一節介紹的同樣。

解決字體顏色和背景顏色同樣的問題有三種解決方案,分別是:

背景色固定不透明,字體顏色與背景色造成反差。(360和京東的作法)
背景色透明,字體顏色採用系統原生的notification_style。
背景色透明,經過特殊方式拿到通知欄字體顏色和字體大小。
Drawing

其中,第一種方案簡單,可以兼容全部廠商機型。例如京東固定背景色爲黑色,字體爲紅色。這種方式的惟一缺陷是樣式上不能與普統統知欄消息重合,在白色背景的通知欄上極爲顯眼。第二種方式,經過閱讀源碼可知,系統的通知欄標題和內容採用的顏色分別是@android:color/primary_text_dark和@android:color/secondary_text_dark,但踩過坑以後發現並不是全部的機型默認都是這兩個顏色,有可能獲取不到值。所以這種方案只能做爲參考,不能用於實際環境中。最後詳細介紹一下第三種方式。

Android默認字體顏色獲取
這種方案有一點投機取巧,是網上尋找代替方案時在簡書上找到的,做者是hackware。思路就是經過Notification.Builder生成一條空的Notification,但不調用notify()方法,而後經過這條Notification想辦法獲取裏面的佈局元素,經過遍歷,就能拿到對應的字體和顏色了。具體看代碼:

private static final String NOTIFICATION_TITLE = "notification_title";
public static final int INVALID_COLOR = -1; // 無效顏色
private static int notificationTitleColor = INVALID_COLOR; // 獲取到的顏色緩存
/**

  • 獲取系統通知欄主標題顏色,根據Activity繼承自AppCompatActivity或FragmentActivity採起不一樣策略。
  • @param context 上下文環境
  • @return 系統主標題顏色
    */
    public static int getNotificationColor(Context context) {
    try {
    if (notificationTitleColor == INVALID_COLOR) {
    if (context instanceof AppCompatActivity) {
    notificationTitleColor = getNotificationColorCompat(context);
    } else {
    notificationTitleColor = getNotificationColorInternal(context);
    }
    }
    } catch (Exception ignored) {
    }
    return notificationTitleColor;
    }
    /**
  • 經過一個空的Notification拿到Notification.contentView,經過{@link RemoteViews#apply(Context, ViewGroup)}方法返回通知欄消息根佈局實例。
  • @param context 上下文
  • @return 系統主標題顏色
    */
    private static int getNotificationColorInternal(Context context) {
    Notification.Builder builder = new Notification.Builder(context);
    builder.setContentTitle(NOTIFICATION_TITLE);
    Notification notification = builder.build();
    try {
    ViewGroup root = (ViewGroup) notification.contentView.apply(context, new FrameLayout(context));
    TextView titleView = (TextView) root.findViewById(android.R.id.title);
    if (null == titleView) {
    iteratorView(root, new Filter() {
    @Override
    public void filter(View view) {
    if (view instanceof TextView) {
    TextView textView = (TextView) view;
    if (NOTIFICATION_TITLE.equals(textView.getText().toString())) {
    notificationTitleColor = textView.getCurrentTextColor();
    }
    }
    }
    });
    return notificationTitleColor;
    } else {
    return titleView.getCurrentTextColor();
    }
    } catch (Exception e) {
    DebugLog.e(e.getMessage());
    return getNotificationColorCompat(context);
    }
    }
    /**
  • 使用getNotificationColorInternal()方法,Activity不能繼承自AppCompatActivity(實測5.0如下機型能夠,5.0及以上機型不行),
  • 大體的緣由是默認通知佈局文件中的ImageView(largeIcon和smallIcon)被替換成了AppCompatImageView,
  • 而在5.0及以上系統中,AppCompatImageView的setBackgroundResource(int)未被標記爲RemotableViewMethod,致使apply時拋異常。
  • @param context 上下文
  • @return 系統主標題顏色
    */
    private static int getNotificationColorCompat(Context context) {
    try {
    Notification.Builder builder = new Notification.Builder(context);
    Notification notification = builder.build();
    int layoutId = notification.contentView.getLayoutId();
    ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null);
    TextView titleView = (TextView) root.findViewById(android.R.id.title);
    if (null == titleView) {
    return getTitleColorIteratorCompat(root);
    } else {
    return titleView.getCurrentTextColor();
    }
    } catch (Exception e) {
    }
    return INVALID_COLOR;
    }
    private static void iteratorView(View view, Filter filter) {
    if (view == null || filter == null) {
    return;
    }
    filter.filter(view);
    if (view instanceof ViewGroup) {
    ViewGroup viewGroup = (ViewGroup) view;
    for (int i = 0; i < viewGroup.getChildCount(); i++) {
    View child = viewGroup.getChildAt(i);
    iteratorView(child, filter);
    }
    }
    }
    private static int getTitleColorIteratorCompat(View view) {
    if (view == null) {
    return INVALID_COLOR;
    }
    List textViews = getAllTextViews(view);
    int maxTextSizeIndex = findMaxTextSizeIndex(textViews);
    if (maxTextSizeIndex != Integer.MIN_VALUE) {
    return textViews.get(maxTextSizeIndex).getCurrentTextColor();
    }
    return INVALID_COLOR;
    }
    private static int findMaxTextSizeIndex(List textViews) {
    float max = Integer.MIN_VALUE;
    int maxIndex = Integer.MIN_VALUE;
    int index = 0;
    for (TextView textView : textViews) {
    if (max < textView.getTextSize()) {
    // 找到字號最大的字體,默認把它設置爲主標題字號大小
    max = textView.getTextSize();
    maxIndex = index;
    }
    index++;
    }
    return maxIndex;
    }
    /**
  • 實現遍歷View樹中的TextView,返回包含TextView的集合。
  • @param root 根節點
  • @return 包含TextView的集合
    */
    private static List getAllTextViews(View root) {
    final List textViews = new ArrayList<>();
    iteratorView(root, new Filter() {
    @Override
    public void filter(View view) {
    if (view instanceof TextView) {
    textViews.add((TextView) view);
    }
    }
    });
    return textViews;
    }

private interface Filter {
void filter(View view);
}
使用這種方法,咱們統計並測試了大廠商的部分機型,獲得以下表格:

廠商 機型 系統通知標題顏色 背景主題 是否支持大圖/多行文字 系統版本 標題大小 可否獲取App推送是否打開 備註
華爲 榮耀V8 未知 暗色 支持 6.0 14dp/19dp 能 通知欄顯示須要受權
華爲 P8 -637534209 暗色透明 支持 5.0.1 能
華爲 榮耀4A -637534209 黑色,透明度不高 支持 5.0.1 能
華爲 榮耀7 -637534209 黑色,透明度不高 支持 5.0.2 能
華爲 MATE8 -637534209 黑色 支持/支持12 6 14dp/16dp+ 能
華爲 G7plus -637534209 黑色 支持 5.1 14dp 能
華爲 榮耀4X -637534209 黑色,透明度不高 支持 5.0.2 14dp/16dp+ 能
小米 3 -452984832 灰色,不透明 不支持/不支持 4.4.4 能 工做線程獲取系統顏色時,會產生java.lang.reflect.InvocationTargetException,帶emoji表情時,系統顯示顏色和普通推送同樣
小米 5s -452984832 白色,不透明 不支持 6.0.1 13.33dp/18dp 能
紅米 NOTE -1 灰色,不透明 不支持 4.4.4 能 帶emoji表情時,系統顯示顏色和普通推送同樣
紅米 NOTE3 -1 灰色,透明 不支持 5.1.1 能 帶emoji表情時,系統顯示顏色和普通推送同樣,但樣式與普通推送稍有不一樣
三星 S4 -16777216 白色,不透明 支持 5.0.1 17dp/17dp 能 大圖須要下拉才能顯示
三星 S5 -16777216 白色,不透明 支持 5 能 大圖須要下拉才能顯示
三星 S6 -14342875 白色,不透明 支持 6.0.1 17dp/17dp 能 大圖不須要能夠顯示,系統版本6.0.1
三星 Note2 -1644826 黑色 支持 能
三星 S6+ -14342875 白色,不透明 支持/支持 5.1.1 16dp 能
三星 SM-N9100 -16777216 白色,不透明 支持 17dp/18dp+ 能 5.0.1
魅族 MX5 -1 灰色,不透明 支持/支持6 5.1 18dp/21dp 能 小通知和大圖重疊;帶emoji表情時,系統顯示內容顏色爲Android自帶顏色,與魅族的系統推送顏色不太同樣
魅族 MX3 4.3 不能 系統是聯通定製的,版本也比較低,估計還不支持該API。
魅藍 NOTE2 -1 灰色,透明度不高 支持 5.1 18dp/21dp 能 小通知和大圖重疊;帶emoji表情時,統顯示內容顏色爲Android自帶顏色,與魅族的系統推送顏色不太同樣
HTC Desire820 -1 黑色,不透明 支持 4.4.4 未測試 有不可清除的系統消息存在時,大圖默認不顯示;普通推送小icon圖標顯示不正確;自定義emoji表情跟MX5相似;工做線程獲取系統顏色時,會產生java.lang.reflect.InvocationTargetException
HTC M9w -570425344 白色 支持 未測試
OPPO R7plus -1 黑色,透明度不高 不支持 5 16dp/16dp 能 帶emoji表情時,系統顯示顏色和普通推送同樣
OPPO R3 黑色,透明度不高 不支持 4.3 18dp/18dp 未測試
一加 ONE -570425344 白色 支持 5.1.1 16dp/16dp 能
一加 One plus 3 白色 支持/支持13 7.0 能
LG Nexus5 -570425344 白色 支持/支持12 6.0.1 16dp/16dp 能
樂視 MAX -1 暗色,透明 支持 6.0.1 16dp/16dp 能
ViVO X5L -1 暗色 支持 4.4.2 未測試
ViVO X5M -1 暗色 支持 5.0.2 能
錘子 T1 黑色 不支持 4.4.2 未測試
金立 S5.1 -1 黑色,不透明 支持 4.3 未測試 工做線程獲取系統顏色時,會產生java.lang.reflect.InvocationTargetException
聯想 K910 -1 暗色 支持 4.2.2 不能
聯想 A320T -1 黑色,不透明 支持 4.4.4 未測試
能夠看到,通知欄存在各式各樣的背景色,字體大小和顏色也不盡相同。經過上述方法,有一部分機型是拿不到系統通知欄顏色的,但經過觀察能夠發現,全部拿不到字體顏色的機型都是暗色或黑色背景(實測7.0此經驗失效),所以可使用白色字體。

考拉RemoteViews適配方案
通過上述調研與測試,咱們的適配方案以下:

獲取系統通知標題顏色,若是可以獲取到,那麼標題、內容和時間的顏色都設置爲標題顏色。
獲取不到的狀況下,遍歷系統通知裏的全部文字,取字號最大的那條文字的顏色做爲標題、內容和時間的顏色。
以上兩個步驟的實如今getNotificationColor()方法裏。若是還獲取不到,那麼標題和內容採用Android原生系統提供的,其中標題是@android:color/primary_text_dark,內容是@android:color/secondary_text_dark。
有一點須要說明的是,以上適配只適合在Android 7.0如下系統。Android 7.0+修改了Notification,採用@android:color/primary_text_dark和@android:color/secondary_text_dark已經獲取不到顏色值了,考慮到7.0所採用的通知欄主色調是白色,所以目前暫時的解決方案是遇到7.0的系統採用黑色字體。面對衆多廠商的源碼修改,目前測試有ZUK的7.0系統爲暗色背景,暫時的解決方案是根據機型適配。
Emoji表情適配^12
對於Android emoji表情的適配,我想只有體驗過的人才知道這裏面有多少坑。我試圖經過谷歌瞭解Android在每個版本對應的emoji表情的支持狀況,最終發現沒有一篇文章或一個網頁可以徹底列出emoji表情在Android上的修改歷史。因而我只能本身動手,構建一張Android emoji表情支持版本對比的表格。

Android系統版本 emoji版本 Unicode版本 Unicode emoji發佈日期 Android支持版本日期 emoji表情數 新增表情連接/更新日誌 備註
7.1 4 9.0 2016.07.21 2016.10.20 2374 http://emojipedia.org/google/android-7.1/new/ http://blog.emojipedia.org/android-7-1-emoji-changelog/ 成爲首個支持Unicode 9標準的系統!
7.0 3 9.0 2016.07.21 2016.08.22 1791 http://emojipedia.org/google/android-7.0/new/ http://blog.emojipedia.org/android-7-0-emoji-changelog/
6.0.1 1 8.0 2015.07.17 2015.12.07 1294 http://emojipedia.org/google/android-6.0.1/new/ http://blog.emojipedia.org/android-6-0-1-emoji-changelog/
6.0 1 7.0 2014.07.16
5.0 1 6.1 2012.02 2014.11.03 1090 http://emojipedia.org/google/android-5.0/new/ http://blog.emojipedia.org/android-50-emoji-changelog/
4.4 1 6.0 2010.1 2014.11.01 850 http://emojipedia.org/google/android-4.4/new/ 首個支持彩色Emoji表情的Android系統
4.3 1 6.0 2010.1 2013.07.24 717 http://emojipedia.org/google/android-4.3/new/ 首個支持Emoji表情的Android系統,但顏色是黑白的
首先須要說明的是爲何會有Emoji版本和Unicode版本?Emoji實際上能夠說是Unicode下的一個子集,Unicode的版本更新,除了Emoji表情發生變化之外,還有許多其餘的字符集定義發生變化,Emoji版本是跟隨着Unicode版本的更新而逐漸迭代更新的。能夠看到,基本上一個Unicode版本對應着一個Emoji版本。目前最新的Unicode版本規劃是Unicode 11.0^10,最新的Emoji版本規劃是Emoji 6.0^11,實際待發布版本是Unicode 10.0和Emoji 5.0,將在2017年中旬發佈。

固然這只是官方Android系統所支持的emoji版本,面對衆多的廠商對源碼大刀闊斧的修改,結果又是怎樣呢?咱們拿了33個表情來進行測試,其中大部分表情是Unicode 6.0標準,後面幾個表情是Unicode8.0標準。最終獲得了以下結果。

Drawing
(點擊查看大圖)

實際測試結果與上面的表格基本匹配。特別表揚一下魅族,在5.X系統上就已經支持了Unicode 8.0標準!樂視的系統在6.0.1系統上的表現指明支持的是Unicode 7.0標準,實際上Android原生已經支持Unicode 8.0標準了。

所以,emoji表情的適配其實相對較簡單,就是根據不一樣的系統版本實現不一樣的支持。固然,若是須要簡化那麼讓只須要讓運營配置Unicode 6版本的emoji表情就可以適配4.4+版本的系統了!至於4.4如下版本,能夠把經常使用的Emoji表情放到資源文件中,遇到文本中包含Emoji字符時,手動替換成資源文件中的Emoji圖片,再經過上述RemoteViews方式來顯示。

Android Nougat+適配
從上面的介紹中,你們能夠發現,Android 7.0系統之後通知欄消息改版了。援引官方在Notifications public deck中介紹的一張圖,

Drawing

除了基本的樣式發生變化,在7.0中也作了部分接口上的修改。其中,咱們須要「擁抱變化」的內容有:

使用non-alpha圖標
在5.0修改記錄中,有一條系統會忽略全部non-alpha通道的圖標,包括按鈕圖標和主圖標。這句話是什麼意思呢?實際上,Android從5.0系統開始,對於通知欄圖標的設計進行了修改。如今Google要求,全部應用程序的通知欄圖標,應該只使用alpha圖層來進行繪製,而不該該包括RGB圖層。通俗點來說,就是讓咱們的通知欄圖標不要帶顏色就能夠了^13。這也是上面的截圖中爲何這麼多應用都顯示不出icon圖標,而是顯示成灰色的正方形。緣由就是他們用了帶背景的圖片。

Notification.Builder.setColor()方法
5.0加入的Notification.Builder.setColor()方法,本來是渲染小圖標的背景色的,7.0之後,改爲了渲染的是通知欄消息第一行的顏色。

Drawing
Drawing

7.0系統默認不顯示時間
7.0系統之後須要顯式調用Notification.Builder.setShownWhen(true)纔會顯示時間,而且格式調整爲具體發佈時間相差N小時/N天的形式(見上圖)。

RemoteViews樣式調整
若是要適配7.0之後的樣式,可使用如下兩個包裹的Style,將RemoteViews封裝在內容區域。

Notification.DecoratedCustomViewStyle()
Notification.DecoratedMediaCustomViewStyle()
Drawing

圖中是未包裹與包裹時候的展現效果。能夠說適配RemoteViews是很是蛋疼的一件事,應用能夠根據須要來選擇是否使用DecoratedCustomViewStyle。

若是不是必要,建議不要使用RemoteViews。考拉以前是爲了兼容在不一樣手機廠商上展現的emoji表情不一致,以及兼容低版本系統,而在包含emoji表情的消息推送中使用了RemoteViews。通過測試,前者的兼容徹底沒有必要,在下一版的平常版本發佈中會移除RemoteViews相關內容,改用原生通知欄消息實現。

Android O通知欄新特性一覽
就在筆者即將發佈這篇文章的時候,Android O系統發佈了預覽版!由上面的討論可知,幾乎每一個Android版本都修改了Notification,相信Android O也不例外。筆者火燒眉毛地查看了新系統特性以及修改歷史,果不其然,Android O的通知欄消息又雙叒叕改版了!關於新系統的通知欄消息改變以下:^15

Notification channels
Android O 還引入了通知渠道,這是全新的由應用定義的通知內容類別。藉助渠道,開發者可讓用戶對不一樣種類的通知進行精細控制,用戶能夠單獨攔截或更改每一個渠道的行爲,而不是統一管理應用的全部通知。^16

簡單說就是增長了應用級別的通知欄消息分組功能。舉個例子,用戶能夠分別控制微信羣組和微信我的在通知欄的顯示級別,羣組消息混雜,能夠調整較低的顯示級別;而我的消息相對重要,能夠調整爲較高的級別。

Snoozing
有點相似鬧鐘的打盹兒功能。用戶可讓一條打盹兒了的通知欄消息再次出如今通知欄上。開發者能夠移除或更新一條打盹兒消息,但更新這條消息不會讓已經處於打盹兒狀態的通知欄消息再次展現到通知欄上。

Notification timeouts
建立一條通知欄消息時,支持設置消息有效期,超過有效期後通知欄消息會被系統取消。經過Notification.Builder.setTimeout()方法設置。

Notification dismissal
新系統提供了API,區分一條通知是被用戶移除或者被應用(即開發者)移除。經過NotificationListenerService.onNotificationRemoved()方法能夠監聽獲得。

Background colors
新系統提供了API設置通知欄消息的背景顏色。值得注意的是,應當謹慎使用這個API,只有當消息很是緊急,必須通知到用戶的時候,才須要設置背景色。例如,能夠爲一個正在導航的應用,或者來電設置一個背景色。能夠經過Notification.Builder.setColor()或者Notification.Builder.setColorized()設置。

因爲目前沒有真機/模擬器,筆者在這裏有一個疑惑。以前Notification.Builder.setColor()這個方法在Android N上設置的是通知欄消息第一行的顏色,包括圖標、應用名稱、副標題等。而在Android O上變成了修改整個消息的背景色?在真機/模擬器available的時候將測試一下。

Messaging style
設置了Messaging style風格的消息在新系統上可以展現更多的內容。消息導向(messaging-related)的通知欄消息應該使用MessageStyle風格代替原生消息。開發者也可使用新的addHistoricMessage()方法將消息添加到通知欄中,以便提供對話的上下文信息。

Notification channels
Android O系統引入了通知渠道,相似於分組的概念。通知渠道須要開發者手動建立,一個應用能夠建立多個通知渠道,用戶能夠分別管理應用的每一個通知渠道,管理頁面由系統提供統一的UI。全部分配到同一個渠道的消息,表現都同樣。當用戶修改了某個渠道的如下任一種行爲,都會同步到該渠道的任何一條消息:

Importance
Sound
Lights
Vibration
Show on lockscreen
Override do not disturb
一旦通知渠道被建立並提交到通知欄管理器(NotificationManager),那麼開發者就沒有權限修改通知渠道的任何配置了,全部的配置只能由用戶修改。用戶能夠到「設置」頁面,或者長按通知欄消息改變通知渠道的配置。

Notification Priority and Importance
Android O系統棄用了舊的通知欄優先級,並提出了通知欄消息重要性這個概念。通知欄上的消息展現順序再也不由優先級控制,也沒法使用重要性來控制。重要性能夠控制消息展現在什麼地方,例如默認級別IMPORTANCE_DEFAULT(3)能夠展現在任意地方,如通知欄、狀態欄、鎖屏,能夠發出通知聲音,但不直接展現給用戶,即不會彈出heads-up通知。重要性一共有6個級別:

IMPORTANCE_NONE(0)
IMPORTANCE_MIN(1)
IMPORTANCE_LOW(2)
IMPORTANCE_DEFAULT(3)
IMPORTANCE_HIGH(4)
IMPORTANCE_MAX(5)
開發者只能設置IMPORTANCE_NONE(0)至IMPORTANCE_HIGH(4)級別,最高級別不能經過代碼控制。重要性控制也是針對通知渠道級別的,具體有沒有全局性控制得看具體的模擬器設置。

總結
本文介紹了Android通知欄消息隨着系統的更新所發生的變化以及在各個版本的Android系統通知欄消息適配過程當中所產生的一些問題,並提供解決思路。隨着Android版本的逐漸迭代,能夠預見Android通知欄消息將會支持愈來愈多可配的樣式,也逐漸地把權限交給用戶控制,包括消息的展現以及隱私的設置。文中提到的後臺通知以及「守護」通知,是對付部分應用爲了常駐內存保持進程不被殺而採起的措施,這對於國內的Android生態來講無疑是利好的。

參考連接 https://arstechnica.com/gadgets/2016/10/building-android-a-40000-word-history-of-googles-mobile-os/8/#h1 https://arstechnica.com/gadgets/2016/10/building-android-a-40000-word-history-of-googles-mobile-os/10/#2.0eclair https://arstechnica.com/gadgets/2016/10/building-android-a-40000-word-history-of-googles-mobile-os/16/#honeycomb https://developer.android.com/about/versions/android-4.1.html http://www.androidpolice.com/2012/07/04/getting-to-know-android-4-1-part-2-the-glorious-new-notifications-size-matters/ https://developer.android.com/about/versions/android-4.3.html https://developer.android.com/about/versions/android-5.0-changes.html#BehaviorNotifications https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html https://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout http://emojipedia.org/unicode-11.0/ http://emojipedia.org/emoji-6.0/ http://emojipedia.org http://blog.csdn.net/guolin_blog/article/details/50945228 http://iconhandbook.co.uk/reference/chart/android/ https://developer.android.com/preview/api-overview.html http://developers.googleblog.cn/2017/03/android-o-developer-preview.html

相關文章
相關標籤/搜索