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

做者:小強 貝聊移動開發部 Android工程師 前言:本文主要講述瞭如下三方面:android

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

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

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

iOS系統下的應用桌面角標
其實原本Android原生系統是不支持應用桌面角標( BadgeNumber)顯示的。咱們目前看到的能支持應用桌面角標顯示的Android系統,都是第三方廠商本身定製的。經過實現一套本身的 launcher而且提供外部接口給第三方應用來調用便可。

咱們公司的APP裏涉及到IM的功能。因此常常會有用戶向客服反饋,爲何某Q、某信都支持應用桌面角標的顯示,但大家的APP卻不行......本着用戶就是上帝的原則,因而應用桌面角標顯示的優化就提上了日程。其實,測試部門在以前就已經跟咱們提過這事了,只不過當時正忙於項目開發,沒時間優化。前段時間需求很少的時候,給公司的Android應用加上了桌面角標顯示的支持。如今將這個優化的過程總結一下。github

目前已經存在的開源庫

若是你們有接觸過這方面的優化,應該很快就能夠在搜索引擎上找到某個被推薦次數較多的開源庫 ShortcutBadgerbash

雖然這個庫適配的覆蓋機型貌似不少,但在實際的測試中發現,某些方法可能對於目前市面上的國產流行機型已經不奏效了。因此,不建議你們直接將這個開源項目用到項目中去。做爲學習和參考卻是一個不錯的選擇。並且,在實際方案抉擇的過程當中,咱們發現,公司的APP主流機型排行榜中,前十的機型幾乎被OPPO、vivo、華爲、小米這四個品牌屠榜了。因此,咱們的優化目標暫時就先定下來了:先集中精力適配市面上的這四個主流品牌機型。其餘的冷門機型,後面再慢慢完善。(其實實際上咱們也找不來那麼多冷門的機型進行測試,因此對於沒自身確認過奏效的方案,即便網上已經有人給出,出於謹慎仍是先不採納)微信

國產主流機型應用角標的適配(OPPO、vivo、華爲、小米)

在開始以前,先聲明一下。第一,不是全部的國產手機都能找到支持角標顯示的方案(即便理論上能夠,可能人家只對某Q某信等一些國民級的應用開放設置應用角標的白名單)。第二,本文中涉及到的方案都是通過實際測試且奏效的了(由於測試手機有限,因此不敢說針對這四個品牌的手機機型百分百支持,但支持大部分的機型應該是沒問題的)。並且,有些品牌的手機適配方案很容易找到,有些品牌的適配方案則很難找到,這部分我會放到後面的章節來講。下面直接上適配方案:app

華爲:

先在AndroidManifest文件裏配置好下面的權限:ide

<!--華爲手機更新應用桌面角標須要的權限-->
    <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的適配方案則是最難找的。既然在網上找不到,而在國內某Q和某信貌似又是適配得最好的,這就說明,某Q和某信的源碼裏確定有現成的解決方案。那麼,不如嘗試一下反編譯,看看能不能從這兩個APP中找到一些靈感?優化

在對某Q的apk進行反編譯後,在某個類下果真找到了設置應用角標的實現類:

某Q設置桌面角標的實現類

從上圖能夠看出,某Q對於各類廠商的適配算是比較完善的了。除了小米、華爲、OPPO、vivo,還適配了聯想、三星、索尼等。

不一樣機型的適配方法也都有具體的實現:(下面是對於OPPO和vivo的適配)

某Q對於OPPO和vivo的適配

可是,咱們也不能直接拷貝過來就使用。由於說不定有些方法只針對某Q才生效呢是吧?

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

某信對於vivo手機桌面角標的適配

總之,對比了一下某Q和某信的源碼,在某些機型的適配方式上,可能兩邊會有些出入。實現方式可能也不太同樣。但不得不說,不愧是大廠的APP,看了源碼後,實在是學習了不少,特別是一些細節上的處理。

上面總結出的適配方案,其實就是在參考了網上各類資料以及某Q和某信的源碼以後總結出來的可行的適配方案。若是還不知足你們的需求,你們能夠發揮一下本身的主觀能動性,找到本身想要的解決方案,並總結出一套屬於本身的適配方案。

3.一種擴展性比較高的簡潔的封裝思路

看完了某Q和某信的源碼後,我發現兩邊都有一個共同點,那就是某個實現類裏塞了不少適配的方法。估計也是可能涉及到不一樣的人在不一樣時期維護的歷史緣由。但一個類裏面的代碼太多了,可能會對查閱和後續維護形成一些不便。

這裏,我參考了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.源碼GitHub地址

爲了響應部分童鞋的要求,現已將BadgeNumberManager的實現源碼上傳到了GitHub: beiliao-mobile:BadgeNumberManager

5.後記

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

注:本文僅供技術學習探討使用。若是文中有不適宜的內容,請聯繫咱們,咱們會第一時間處理:)

6.填坑記錄(2017.12.11)

  1. 通過測試,目前暫時不支持的機型:華爲榮耀六、OPPO A5九、OPPO R9,OPPO R十一、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開放桌面角標設置的權限。因此,這個問題暫時尚未解決方法

相關文章
相關標籤/搜索