Badge红点最初来自于IOS的UX设计之中,早期版本的Android原生并没有这个设计,从Android O(SDK 26)开始,Google才开始提供官方的API。在这之前我们看到的各种手机系统上的Badge实际上是各个手机厂商的Rom中,自己对Launcher添加了这个Feature,因此也造成了一些问题,比如碎片化严重,没有统一的API。APP如果要使用Badge需要针对不同厂商的ROM进行定向开发。下面是目前主流的手机厂商Badge功能的具体实现。
在API 26之前,由于没有统一的系统API,需要对判断手机型号,并定向处理。主要的方式有三种:第一种是通过反射调用厂商自己定义的framework方法(如小米),第二种是发送Badge的对应广播(如三星),第三种是通过ContentProvider实现(如华为)。有的手机ROM甚至还要添加特定的权限才能够生效(如华为)。根据测试结果,实际还是有很多手机上无法生效。一方面可能是因为一些手机ROM中没有Badge的Feature,另一方面是厂商对这一权限有比较严格的限制,比如设置白名单,默认禁止通知等。
@Unreliable
public class NotificationBadgeUtil {
public static final String TAG = "NotificationBadgeUtil";
private static final String SYSTEM_XIAOMI = "XIAOMI";
private static final String SYSTEM_SAMSUNG = "SAMSUNG";
private static final String SYSTEM_HUAWEI_HONOR = "HONOR";
private static final String SYSTEM_HUAWEI = "HUAWEI";
private static final String SYSTEM_NOVA = "NOVA";
private static final String SYSTEM_SONY = "SONY";
private static final String SYSTEM_VIVO = "VIVO";
private static final String SYSTEM_OPPO = "OPPO";
private static final String SYSTEM_LG = "LG";
private static final String SYSTEM_ZUK = "ZUK";
private static final String SYSTEM_HTC = "HTC";
private static boolean hasInit = false;
private static String OSName = null;
/**
* The static method to show a badge while using notification.
*
* Google provide system API for badges works in launcher since Android O,
* if SDK version is older than Android O, you can using variant phone custom
* launcher badge ways like dealing in this method. But some specific phone
* models seems don`t support system API even though API over Android O, like
* my HUAWEI P9 Android O.
*
* @param context Context for notification dependence.
* @param notification Notification object correspond to badge.
* @param NOTIFICATION_ID The notification channel id.
* @param num The count of badges. Cancel notification if count is 0.
*/
public static void showBadge(Context context, Notification notification, int NOTIFICATION_ID, int num) {
if (!hasInit) {
init();
}
OSName = Build.BRAND.trim().toUpperCase();
if (notification != null) {
if (num < 0) num = 0;
if (num > 99) num = 99;
Log.e("system_name", OSName);
if (OSName != null) {
if (OSName.equals(SYSTEM_XIAOMI)) {
setBadgeOfXiaomi(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_SAMSUNG) || OSName.equals(SYSTEM_LG)) {
setBadgeOfSamsung(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_HUAWEI_HONOR) || OSName.equals(SYSTEM_HUAWEI)) {
setBadgeOfHuaWei(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_SONY)) {
setBadgeOfSony(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_VIVO)) {
setBadgeOfVIVO(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_OPPO)) {
setBadgeOfOPPO(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_ZUK)) {
setBadgeOfZUK(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_HTC)) {
setBadgeOfHTC(context, notification, NOTIFICATION_ID, num);
} else if (OSName.equals(SYSTEM_NOVA)) {
setBadgeOfNOVA(context, notification, NOTIFICATION_ID, num);
} else {
setBadgeOfDefault(context, notification, NOTIFICATION_ID, num);
}
}
}
}
private static void init() {
OSName = android.os.Build.BRAND.trim().toUpperCase();
hasInit = true;
}
private static void setBadgeOfXiaomi(final Context context, final Notification notification, final int NOTIFICATION_ID, final int num) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
Field field = notification.getClass().getDeclaredField("extraNotification");
Object extraNotification = field.get(notification);
Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
method.invoke(extraNotification, num);
} catch (Exception e) {
e.printStackTrace();
Log.e("Xiaomi" + " Badge error", "set Badge failed");
}
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
}
}, 550);
}
private static void setBadgeOfSamsung(Context context, Notification notification, int NOTIFICATION_ID, int num) {
String launcherClassName = getLauncherClassName(context);
if (launcherClassName == null) {
return;
}
try {
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", num);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", launcherClassName);
context.sendBroadcast(intent);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("SAMSUNG" + " Badge error", "set Badge failed");
}
}
/*
Need permission for HUAWEI models:
*/
private static void setBadgeOfHuaWei(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
Bundle localBundle = new Bundle();
localBundle.putString("package", context.getPackageName());
localBundle.putString("class", getLauncherClassName(context));
localBundle.putInt("badgenumber", num);
context.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, localBundle);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("HUAWEI" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfSony(Context context, Notification notification, int NOTIFICATION_ID, int num) {
String numString = "";
String activityName = getLauncherClassName(context);
if (activityName == null) {
return;
}
Intent localIntent = new Intent();
boolean isShow = true;
if (num < 1) {
numString = "";
isShow = false;
} else if (num > 99) {
numString = "99";
}
try {
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", isShow);
localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE");
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", activityName);
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", numString);
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.getPackageName());
context.sendBroadcast(localIntent);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("SONY" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfVIVO(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
Intent localIntent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM");
localIntent.putExtra("packageName", context.getPackageName());
localIntent.putExtra("className", getLauncherClassName(context));
localIntent.putExtra("notificationNum", num);
context.sendBroadcast(localIntent);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("VIVO" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfOPPO(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
if (num == 0) {
num = -1;
}
Intent intent = new Intent("com.oppo.unsettledevent");
intent.putExtra("pakeageName", context.getPackageName());
intent.putExtra("number", num);
intent.putExtra("upgradeNumber", num);
if (canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent);
} else {
try {
Bundle extras = new Bundle();
extras.putInt("app_badge_count", num);
context.getContentResolver().call(Uri.parse("content://com.android.badge/badge"), "setAppBadgeCount", null, extras);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Throwable th) {
Log.e("OPPO" + " Badge error", "unable to resolve intent: " + intent.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
Log.e("OPPO" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfZUK(Context context, Notification notification, int NOTIFICATION_ID, int num) {
final Uri CONTENT_URI = Uri.parse("content://com.android.badge/badge");
try {
Bundle extra = new Bundle();
extra.putInt("app_badge_count", num);
context.getContentResolver().call(CONTENT_URI, "setAppBadgeCount", null, extra);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("ZUK" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfHTC(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
Intent intent1 = new Intent("com.htc.launcher.action.SET_NOTIFICATION");
intent1.putExtra("com.htc.launcher.extra.COMPONENT", context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().flattenToShortString());
intent1.putExtra("com.htc.launcher.extra.COUNT", num);
Intent intent = new Intent("com.htc.launcher.action.UPDATE_SHORTCUT");
intent.putExtra("packagename", context.getPackageName());
intent.putExtra("count", num);
if (canResolveBroadcast(context, intent1) || canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent1);
context.sendBroadcast(intent);
} else {
Log.e("HTC" + " Badge error", "unable to resolve intent: " + intent.toString());
}
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("HTC" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfNOVA(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
ContentValues contentValues = new ContentValues();
contentValues.put("tag", context.getPackageName() + "/" + getLauncherClassName(context));
contentValues.put("count", num);
context.getContentResolver().insert(Uri.parse("content://com.teslacoilsw.notifier/unread_count"), contentValues);
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("NOVA" + " Badge error", "set Badge failed");
}
}
private static void setBadgeOfDefault(Context context, Notification notification, int NOTIFICATION_ID, int num) {
try {
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", num);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", getLauncherClassName(context));
if (canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent);
} else {
Log.e("Default" + " Badge error", "unable to resolve intent: " + intent.toString());
}
NotificationManager notifyMgr = (NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE));
if (num != 0) notifyMgr.notify(NOTIFICATION_ID, notification);
else notifyMgr.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
Log.e("Default" + " Badge error", "set Badge failed");
}
}
private static String getLauncherClassName(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setPackage(context.getPackageName());
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (info == null) {
info = packageManager.resolveActivity(intent, 0);
}
return info.activityInfo.name;
}
private static boolean canResolveBroadcast(Context context, Intent intent) {
PackageManager packageManager = context.getPackageManager();
List receivers = packageManager.queryBroadcastReceivers(intent, 0);
return receivers != null && receivers.size() > 0;
}
}
Google在Android O中终于添加了Badge的API。只需要在mShowBadge为true的NotificationChannel中发送notification即可实现Badge。官方文档地址:https://developer.android.com/training/notify-user/badges
val mChannel = NotificationChannel(_channelId, packageName, NotificationManager.IMPORTANCE_LOW).apply {
description = ""
setShowBadge(true)
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
.....................
val notification = NotificationCompat.Builder(this@BadgeTestActivity, _channelId)
.setContentTitle("New Messages")
.setContentText("You've received 3 new messages.")
.setSmallIcon(R.drawable.notification_icon_background)
.setNumber(count)
.build()
.......................
val notifyMgr = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notifyMgr.notify(0, notification)
但是需要注意的是,并非所有的Android O以上的系统都可以使用Google提供的API来实现Badge。我手边的两台Android O手机中,三星S9是可以的,而华为P9则不行。华为P9即使在8.0系统上,依旧需要使用Android O之前的方法来实现Badge,Google的API并不会生效。猜测原因是华为的Launcher Badge并没有去支持Google的API,而是依旧使用自己ROM里面之前的实现方法。
Demo地址(存放在我的Framework库中,参见NotificationBadgeUtil.java):
https://github.com/Haocxx/HaocxxFrameworkForAndroid
参考资料:
http://www.androidchina.net/4093.html
https://github.com/Cedric-Xuan/SNSNotificationBadge