作者:小強 貝聊移動開發部 Android工程師
前言:本文主要講述了以下三方面:
- 怎么在Android系統下讓自家的應用圖標像iOS系統那樣支持數字角標的顯示?
- 在網上找不到現成的解決方案的情況下,該如何去尋找問題的突破口?
- 一種簡潔性和擴展性都比較高的封裝思路。
先放一個傳送門:GitHub傳送門
1.Android系統下如何支持應用桌面角標(BadgeNumber)的顯示
其實本來Android原生系統是不支持應用桌面角標(
BadgeNumber
)顯示的。我們目前看到的能支持應用桌面角標顯示的Android系統,都是第三方廠商自己定制的。通過實現一套自己的launcher
并且提供外部接口給第三方應用來調用即可。
我們公司的APP里涉及到IM的功能。所以經常會有用戶向客服反饋,為什么QQ、微信都支持應用桌面角標的顯示,但你們的APP卻不行......本著用戶就是上帝的原則,于是應用桌面角標顯示的優化就提上了日程。其實,測試部門在之前就已經跟我們提過這事了,只不過當時正忙于項目開發,沒時間優化。前段時間需求不多的時候,給公司的Android應用加上了桌面角標顯示的支持。現在將這個優化的過程總結一下。
目前已經存在的開源庫
如果大家有接觸過這方面的優化,應該很快就可以在搜索引擎上找到某個被推薦次數較多的開源庫 ShortcutBadger。
雖然這個庫適配的覆蓋機型貌似很多,但在實際的測試中發現,某些方法可能對于目前市面上的國產流行機型已經不奏效了。所以,不建議大家直接將這個開源項目用到項目中去。作為學習和參考倒是一個不錯的選擇。而且,在實際方案抉擇的過程中,我們發現,公司的APP主流機型排行榜中,前十的機型幾乎被OPPO、vivo、華為、小米這四個品牌屠榜了。所以,我們的優化目標暫時就先定下來了:先集中精力適配市面上的這四個主流品牌機型。其他的冷門機型,后面再慢慢完善。(其實實際上我們也找不來那么多冷門的機型進行測試,所以對于沒自身確認過奏效的方案,即使網上已經有人給出,出于謹慎還是先不采納)
國產主流機型應用角標的適配(OPPO、vivo、華為、小米)
在開始之前,先聲明一下。第一,不是所有的國產手機都能找到支持角標顯示的方案(即使理論上可以,可能人家只對QQ微信等一些國民級的應用開放設置應用角標的白名單)。第二,本文中涉及到的方案都是經過實際測試且奏效的了(因為測試手機有限,所以不敢說針對這四個品牌的手機機型百分百支持,但支持大部分的機型應該是沒問題的)。而且,有些品牌的手機適配方案很容易找到,有些品牌的適配方案則很難找到,這部分我會放到后面的章節來說。下面直接上適配方案:
華為:
先在AndroidManifest
文件里配置好下面的權限:
<!--華為手機更新應用桌面角標需要的權限-->
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
設置角標的方法如下:
public static void setBadgeNumber(Context context, int number) {
try {
if (number < 0) number = 0;
Bundle bundle = new Bundle();
bundle.putString("package", context.getPackageName());
String launchClassName = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().getClassName();
bundle.putString("class", launchClassName);
bundle.putInt("badgenumber", number);
context.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
OPPO:
public static void setBadgeNumber(Context context, int number) {
try {
if (number == 0) {
number = -1;
}
Intent intent = new Intent("com.oppo.unsettledevent");
intent.putExtra("pakeageName", context.getPackageName());
intent.putExtra("number", number);
intent.putExtra("upgradeNumber", number);
if (canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent);
} else {
try {
Bundle extras = new Bundle();
extras.putInt("app_badge_count", number);
context.getContentResolver().call(Uri.parse("content://com.android.badge/badge"), "setAppBadgeCount", null, extras);
} catch (Throwable t) {
t.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static boolean canResolveBroadcast(Context context, Intent intent) {
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> receivers = packageManager.queryBroadcastReceivers(intent, 0);
return receivers != null && receivers.size() > 0;
}
vivo:
public static void setBadgeNumber(Context context, int number) {
try {
Intent intent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM");
intent.putExtra("packageName", context.getPackageName());
String launchClassName = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().getClassName();
intent.putExtra("className", launchClassName);
intent.putExtra("notificationNum", number);
context.sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
小米:
小米的設置應用角標方式比較有個性,跟其他廠商的不太一樣,是跟Notification
綁定在一起的。而且小米系統還有個比較特殊的地方,如果在應用內直接調用設置角標的方法,設置角標會不生效,所以只能在應用在后臺并且收到推送的情況下進行角標的設置。另外,即使你設置了角標的顯示,只要用戶點擊應用圖標進入到應用內,應用的角標就會自動消失掉,即使應用內還存在新的未讀消息。所以,針對小米機型,建議在收到推送后并且進行notification的時機更新應用角標。
//在調用NotificationManager.notify(notifyID, notification)這個方法之前先設置角標顯示的數目
public static void setBadgeNumber(Notification notification, int number) {
try {
Field field = notification.getClass().getDeclaredField("extraNotification");
Object extraNotification = field.get(notification);
Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
method.invoke(extraNotification, number);
} catch (Exception e) {
e.printStackTrace();
}
}
2.在網上找不到現成的解決方案的情況下,該如何去尋找問題的突破口?
在上面的適配方案中,最容易找到而且奏效的就是華為和小米的適配方案。而OPPO的適配方案,即使找到了,在現有的測試機型上卻不奏效;vivo的是適配方案則是最難找的。既然在網上找不到,而QQ和微信貌似又是適配得最好的,這就說明,QQ和微信的源碼里肯定有現成的解決方案。那么,不如嘗試一下反編譯,看看能不能從這兩個超級APP中找到一些靈感?
在對QQ的apk進行反編譯后,在某各類下果然找到了設置應用角標的實現類:
從上圖可以看出,QQ對于各種廠商的適配算是比較完善的了。除了小米、華為、OPPO、vivo,還適配了聯想、三星、索尼等。
不同機型的適配方法也都有具體的實現:(下面是手Q對于OPPO和vivo的適配)
但是,我們也不能直接拷貝過來就使用。因為說不定有些方法只針對QQ才生效呢是吧?
在對微信的apk進行反編譯后,也能找到關于應用角標適配的代碼:
總之,對比了一下QQ和微信的源碼,在某些機型的適配方式上,可能兩邊會有些出入。實現方式可能也不太一樣。但不得不說,不虧是大廠的APP,看了源碼后,實現是學習了很多,特使是一些細節上的處理。
上面總結出的適配方案,其實就是在參考了網上各種資料以及QQ和微信的源碼之后總結出來的可行的適配方案。如果還不滿足大家的需求,大家可以去找一下QQ和微信的源碼來進行研讀,并總結出一套屬于自己的適配方案。
3.一種擴展性比較高的簡潔的封裝思路
看完了QQ和微信的源碼后,我發現兩邊都有一個共同點,那就是某個實現類里塞了很多適配的方法。估計也是可能涉及到不同的人在不同時期維護的歷史原因。但一個類里面的代碼太多了,可能會對查閱以及后續維護造成一些不便。
這里,我參考了Android源碼里面NotificationManagerCompat
這個類的實現方式。Android源碼中本身就涉及到很多關于不同版本的適配的場景。某個方法,在不同的版本下,可能實現方式不太一樣。于是,怎么在不斷往某個類增加不同的實現方式的情況下,保持代碼的美觀以及擴展性易讀性變成了一個問題。NotificationManagerCompat
這個類的實現就十分簡潔美觀。下面是一部分源碼截圖,有興趣的可以直接去看一下完整的源碼。
下面就是模仿后的實現:
public class BadgeNumberManager {
private Context mContext;
private BadgeNumberManager(Context context) {
mContext = context;
}
public static BadgeNumberManager from(Context context) {
return new BadgeNumberManager(context);
}
private static final BadgeNumberManager.Impl IMPL;
/**
* 設置應用在桌面上顯示的角標數字
* @param number 顯示的數字
*/
public void setBadgeNumber(int number) {
IMPL.setBadgeNumber(mContext, number);
}
interface Impl {
void setBadgeNumber(Context context, int number);
}
static class ImplHuaWei implements Impl {
@Override
public void setBadgeNumber(Context context, int number) {
BadgeNumberManagerHuaWei.setBadgeNumber(context, number);
}
}
static class ImplVIVO implements Impl {
@Override
public void setBadgeNumber(Context context, int number) {
BadgeNumberManagerVIVO.setBadgeNumber(context, number);
}
}
static class ImplBase implements Impl {
@Override
public void setBadgeNumber(Context context, int number) {
//do nothing
}
}
static {
String manufacturer = Build.MANUFACTURER;
if (manufacturer.equalsIgnoreCase("Huawei")) {
IMPL = new ImplHuaWei();
} else if (manufacturer.equalsIgnoreCase("vivo")) {
IMPL = new ImplVIVO();
} else if (manufacturer.equalsIgnoreCase("XXX")) {
//其他品牌機型的實現類
IMPL = new ImplXXX();
......
} else {
IMPL = new ImplBase();
}
}
}
使用的時候,只需要調用BadgeNumberManager.from(context).setBadgeNumber(num)
就行了。BadgeNumberManagerHuaWei
、BadgeNumberManagerVIVO
等都是針對某個手機品牌的具體實現類。
當然,這只是一種實現的思路而已。具體去實現的時候,請根據自己項目的實際情況,怎樣實現擴展性可讀性較高就選哪種。
4.后記
如果有關于別的機型的適配方案,歡迎在評論下留言(最好是自己親自測試過并且有效的)。如果文章中有出現錯誤的地方,歡迎指正。如果對于文章中的某些部分有不同的理解和想法,或者有更好的想法, 也歡迎留言討論。
5.填坑記錄(2017.12.11)
經過測試,目前暫時不支持的機型:華為榮耀6、OPPO A59、OPPO R9,OPPO R11、vivo X9i(截止至2017.12.11)
一開始以為某些機型不支持可能是少了某些跟角標設置相關的權限,于是反編譯微信、QQ、支付寶,從這些App中收集
AndroidManifest
里配置的可能跟角標設置相關的權限,并添加到Demo中來測試,后來發現還是不行針對華為手機,在某些機型上,例如華為 mate9,在
manifest
里除了需要配置com.huawei.android.launcher.permission.CHANGE_BADGE
權限之外,還需要配置android.permission.INTERNET
權限才可以正常設置桌面角標(不過一般的App應該都會配置了android.permission.INTERNET
權限)關于OPPO手機,在一些較舊的機型上可以正常設置桌面角標,但在一些比較新的機型上(例如OPPO R9,OPPO R11等),只有在通知權限管理中,有“在桌面圖標上顯示角標”這個選項的App才可以正常設置角標。目前就只發現QQ,微信,釘釘有這個權限,就連支付寶都沒有這個權限。于是嘗試著寫了個Demo,將Demo的包名改成了微信的包名,然后在通知權限管理中,就出現了“在桌面圖標上顯示圖標”這個選項。所以,在新的機型上,OPPO應該是根據包名來維護了一個白名單,只針對一些比較大型的IM類型的App開放桌面角標設置的權限。所以,這個問題暫時還沒有解決方法