Android探索之旅 | 為應用添加角標(Badge)
– 簡書作者 謝恩銘 轉載請注明出處
需求簡介
角標是什么意思呢?
看下圖即可明了:
可以看到圖中的樂購這個app右上角的紅色的圓圈,里面有10這個數字的,就是一種角標。
角標,英語是badge,也就是“徽章,像章,獎章; 象征,標記”的意思。
一般來說,應用的角標是用來標記有多少條提醒(Notification)沒讀(unread),一旦點擊提示進應用閱讀了,角標也會消失。
Android角標起源
角標原本是蘋果的iOS中的東西,Android原生并不支持角標,因為Google的意思是讓大家用Notification(提示欄)即可,角標實在大有讓處女座“跳崖”的風險。幸好我不是…
最近公司的項目中,客戶的一個新需求是在我們的加密信息應用上加上角標功能,因為我們的合作伙伴是三星(可以參看我的這篇文章:程序員在法國 | 我被法國國防部盯上了!),因此我就去網上找相關資料。
找的時候,才知道上面所說的Android原生不支持角標一事。不過無妨,厲害的Android第三方廠商可以通過在自定義的Launcher(啟動器)中操作來實現添加角標。
我在第一時間當然是去找三星的移動設備如何添加角標,不過卻有幸找到了Github上的比較普適的項目。
把我導向Github的自然是Stack Overflow,而把我導向Stack Overflow的就是Google,因此我會說:為什么程序員一定要會用Google和Stack Overflow? 。
不錯的github項目
- 一般來說,現在被引用最多的Android添加和去除角標的Github項目是這位中國人寫的:https://github.com/leolin310148/ShortcutBadger這個項目挺不錯,雖然更新不是特別勤快,但最近一次更新是在2016年10月31日,也就是兩個月前,還可以接受。
- 《Android群英傳》和《Android群英傳:神兵利器》的作者 徐宣生 也在自己的Github上建了一個項目:https://github.com/xuyisheng/ShortcutHelper ,挺有意思,里面還有號稱“瘋狂模式”的為所有在手機桌面上的應用加上99的角標數的功能,當然了,去除的代碼也有,不然處女座豈不是要暈了~
添加角標的原理就是發送一個Broadcast(廣播),在廣播的Intent中指定需要被添加角標的應用的packageName(包名),className(類名),count(角標數目)。當然了,不同廠商的手機的角標操作的Intent的action是不一樣的。
因此,我們如果要給自己的手機里的應用添加角標,只需要簡單的利用上面兩個項目中的代碼即可,一般不需要把全部項目搬過來。當然了,如果你要適配所有手機,那么可以全盤引用項目。
比如我要給三星的手機的應用添加角標,那么我只需要做以下的幾步即可:
在AndroidManifest.xml中添加讀取和寫入角標的權限:
- <uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
- <uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
自己寫一個類,隨便取名字,比如叫做 BadgeUtils,在類中添加如下內容:
- public class BadgeUtils {
- private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE";
- private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count";
- private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name";
- private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name";
- public static void setBadgeCount(Context context, ComponentName componentName, int badgeCount) {
- Intent intent = new Intent(INTENT_ACTION);
- intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount);
- intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName());
- intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName());
- context.sendBroadcast(intent);
- }
- }
使用上面的代碼時,只需要傳入三個參數,也就是:
- Context : 應用的Context。簡單。
- ComponentName :組件名,略有點麻煩。可以這樣來獲取(applicationContext就是應用的Context) :
- applicationContext.getPackageManager()
- .getLaunchIntentForPackage(applicationContext.getPackageName())
- .getComponent()
- badgeCount :角標的數目,例如10。簡單。
當然了,如果你不想要傳入三個參數這么麻煩,你也可以再寫一個方法getLauncherClassName,就只需要傳入兩個參數即可。BadgeUtils中的代碼變為:
- public class BadgeUtils {
- private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE";
- private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count";
- private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name";
- private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name";
- public static void setBadgeCount(Context context, int badgeCount) {
- String launcherClassName = getLauncherClassName(context);
- if (launcherClassName == null) {
- return;
- }
- Intent intent = new Intent(INTENT_ACTION);
- intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount);
- intent.putExtra(INTENT_EXTRA_PACKAGENAME, context.getPackageName());
- intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, launcherClassName);
- context.sendBroadcast(intent);
- }
- private static String getLauncherClassName(Context context) {
- PackageManager pm = context.getPackageManager();
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
- for (ResolveInfo resolveInfo : resolveInfos) {
- String pkgName = resolveInfo.activityInfo.applicationInfo.packageName;
- if (pkgName.equalsIgnoreCase(context.getPackageName())) {
- String className = resolveInfo.activityInfo.name;
- return className;
- }
- }
- return null;
- }
- }
使用時傳入兩個參數即可:
- Context : 應用的Context。
- badgeCount :角標的數目,例如10。
清除角標
要清除應用的角標就很簡單了,給badgeCount傳入0即可。
- BadgeUtils.setBadgeCount(context,
- context.getPackageManager()
- .getLaunchIntentForPackage(context.getPackageName())
- .getComponent(),
- 0);
或者
- BadgeUtils.setBadgeCount(context, 0);
小問題糾錯
上面的 https://github.com/leolin310148/ShortcutBadger 這個項目中,基本已經包含了大多數可以定制角標的Android生產廠商的添加角標的代碼實現,不過它也提到:
三星和LG(這兩個難兄難弟)的代碼有很多類似,連角標處理的廣播的Intent中的action也是一樣的,都是:
- "android.intent.action.BADGE_COUNT_UPDATE"
但是作者在三星和LG的兩個角標操作實現類中寫了注釋:
- // Deprecated, Samsung devices will use DefaultBadger
- // Deprecated, LG devices will use DefaultBadger
意思是“三星和LG的實現代碼已經Deprecated(失效了),請用DefaultBadger類”。
因此,這兩個需要用 https://github.com/leolin310148/ShortcutBadger/blob/master/ShortcutBadger/src/main/java/me/leolin/shortcutbadger/impl/DefaultBadger.java 中的實現:
- private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE";
- private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count";
- private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name";
- private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name";
- @Override
- public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
- Intent intent = new Intent(INTENT_ACTION);
- intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount);
- intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName());
- intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName());
- if (BroadcastHelper.canResolveBroadcast(context, intent)) {
- context.sendBroadcast(intent);
- } else {
- throw new ShortcutBadgeException("unable to resolve intent: " + intent.toString());
- }
- }
不過上面的代碼有一個小問題,就是那句
- if (BroadcastHelper.canResolveBroadcast(context, intent)) {
在有些設備(比如Samsung Galaxy S5)上會拋出異常(Exception),找不到處理”android.intent.action.BADGE_COUNT_UPDATE”這個Intent的BroadcastReceiver,很奇怪。
但有些設備(比如Samsung Galaxy A5)上又運行正常,沒有拋出異常。
解決辦法是去除這一個檢測,把
- if (BroadcastHelper.canResolveBroadcast(context, intent)) {
- context.sendBroadcast(intent);
- } else {
- throw new ShortcutBadgeException("unable to resolve intent: " + intent.toString());
- }
替換為簡單的
- context.sendBroadcast(intent);
就可以了。
也就是我上面自己實作時的代碼。
總結
- Android的角標添加和移除畢竟是基于各大手機廠商的Launcher的定制,因此不是正統的Android技巧,隨著廠商的Launcher的改變,也許你的代碼未來就不一定有用了,因此需要不斷修改,“推陳出新”。
- 不過正所謂“生命在于折騰”,而這也是我們喜歡Android系統的原因。這個萌萌的機器人可以經得起我們隨意折騰,在嵌入式領域的應用前途也是很不錯的。
- 大家在平時學習編程的時候,也可以把自己的代碼或經驗匯總到Github項目,一來惠己利人,二來提高自己的業界知名度。