[貝聊科技]有關Android應用桌面角標(BadgeNumber)實現的探討

作者:小強 貝聊移動開發部 Android工程師

前言:本文主要講述了以下三方面:

  1. 怎么在Android系統下讓自家的應用圖標像iOS系統那樣支持數字角標的顯示?
  2. 在網上找不到現成的解決方案的情況下,該如何去尋找問題的突破口?
  3. 一種簡潔性和擴展性都比較高的封裝思路。

先放一個傳送門:GitHub傳送門

1.Android系統下如何支持應用桌面角標(BadgeNumber)的顯示

iOS系統下的應用桌面角標

其實本來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設置桌面角標的實現類

從上圖可以看出,QQ對于各種廠商的適配算是比較完善的了。除了小米、華為、OPPO、vivo,還適配了聯想、三星、索尼等。

不同機型的適配方法也都有具體的實現:(下面是手Q對于OPPO和vivo的適配)

手Q對于OPPO和vivo的適配

但是,我們也不能直接拷貝過來就使用。因為說不定有些方法只針對QQ才生效呢是吧?

在對微信的apk進行反編譯后,也能找到關于應用角標適配的代碼:

微信對于vivo手機桌面角標的適配

總之,對比了一下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)就行了。BadgeNumberManagerHuaWeiBadgeNumberManagerVIVO等都是針對某個手機品牌的具體實現類。

當然,這只是一種實現的思路而已。具體去實現的時候,請根據自己項目的實際情況,怎樣實現擴展性可讀性較高就選哪種。

4.后記

如果有關于別的機型的適配方案,歡迎在評論下留言(最好是自己親自測試過并且有效的)。如果文章中有出現錯誤的地方,歡迎指正。如果對于文章中的某些部分有不同的理解和想法,或者有更好的想法, 也歡迎留言討論。

5.填坑記錄(2017.12.11)

  1. 經過測試,目前暫時不支持的機型:華為榮耀6、OPPO A59、OPPO R9,OPPO R11、vivo X9i(截止至2017.12.11)

  2. 一開始以為某些機型不支持可能是少了某些跟角標設置相關的權限,于是反編譯微信、QQ、支付寶,從這些App中收集AndroidManifest里配置的可能跟角標設置相關的權限,并添加到Demo中來測試,后來發現還是不行

  3. 針對華為手機,在某些機型上,例如華為 mate9,在manifest里除了需要配置com.huawei.android.launcher.permission.CHANGE_BADGE權限之外,還需要配置android.permission.INTERNET權限才可以正常設置桌面角標(不過一般的App應該都會配置了android.permission.INTERNET權限)

  4. 關于OPPO手機,在一些較舊的機型上可以正常設置桌面角標,但在一些比較新的機型上(例如OPPO R9,OPPO R11等),只有在通知權限管理中,有“在桌面圖標上顯示角標”這個選項的App才可以正常設置角標。目前就只發現QQ,微信,釘釘有這個權限,就連支付寶都沒有這個權限。于是嘗試著寫了個Demo,將Demo的包名改成了微信的包名,然后在通知權限管理中,就出現了“在桌面圖標上顯示圖標”這個選項。所以,在新的機型上,OPPO應該是根據包名來維護了一個白名單,只針對一些比較大型的IM類型的App開放桌面角標設置的權限。所以,這個問題暫時還沒有解決方法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380