記錄一次兼容Android手機角標的辛酸史。。。

一、前言

話說,在某天,正在煩惱某個功能點如何實現更好、更快,老大來了一句,iOS 應用圖標有未讀提示,這個華爲手機怎麼沒有呢?來,搞一下。

朦朦朧朧接了任務。嗯,就這樣開啓了辛酸路。

來,過來個人,抱着哭會兒。

先看看效果圖,手機拍個短信、釘釘、QQ 的小效果:

 

看到了嗎?zou si 它~!

昏暗無光的樣式吶,默默 MMP~!

Enmmm,另外在此註明下,本內容由 LZ 小白以及偶家明遠小哥哥一塊完成,下面貼出小哥哥博客地址:

https://blog.csdn.net/qq_33869391

Enmmm,下面開啓辛酸路吧。

二、辛酸路

首先看到應用程序 Logo 顯示未讀消息,內心第一想法便是,So so easy,相比良好的廠商以及提供了相關 Api 咯,LZ 無非就是整合一下,然而卵。

想了想,目前市面上主流的幾款機型:

  • 華爲

  • 小米

  • VIVO,OPPO

  • 爆炸神機  三星

  • 魅族、一加、索尼、聯想。。。

想想要兼容每家好煩呢,也不知道文檔寫的 6 不 6 ,對於 LZ 這樣小白理解力夠不夠。事實證明,LZ 想多了,滿臉的生無可戀。

一起來看“每家“ API 文檔

2.1 華爲

角標是華爲桌面提供給各應用顯示未讀消息用的,會在應用圖標右上角繪製一張消息條數的圖標。

貼心的華爲爲我們描述了角標的作用。

  • 華爲桌面角標業務介紹:https://developer.huawei.com/consumer/cn/devservice/doc/30801;

  • 華爲桌面角標開發指導書:https://developer.huawei.com/consumer/cn/devservice/doc/30802;

  • 華爲桌面未讀角標對外接口說明書:http://obs.cn-north-1.myhwclouds.com/consumer/docattachment/87918b190abda6d7b7a568a7ef1dfc314cd9ad040faccf1a999dcff158ec7d79/badge.pdf

這裏需要注意:

當桌面不支持角標功能時,接口會拋出異常,應用可以在調用接口的地方加上try … catch(Exception e) 語句以免程序崩潰。

2.2 小米

  • MIUI 6 至 MIUI 10 桌面角標適配說明:https://dev.mi.com/console/doc/detail?pId=939

這裏需要注意:

當應用向通知欄發送了一條通知 (除了進度條樣式和常駐通知外),應用圖標的右上角就會顯示「1」。值得一提,角標的數字代表應用的通知數,即應用發送了「x」條通知,角標就會顯示爲「x」。


如果開發者不滿意默認邏輯,想要自定義角標的數字,可以通過調用接口告知系統即可。
 

2.3 OPPO、VIVO

  • OPPO 開發平臺:https://open.oppomobile.com/wiki/doc#id=10026;

  • VIVO 開放平臺:https://dev.vivo.com.cn/documentCenter/doc/135。

Enmmm,上面倆個地址暫時找不到,LZ 附上詢問客服的截圖,供大家一覽~

 

上面是 OPPO ,下面 VIVO,不愧是好基友。

 

2.4 聯想

  • 應用圖標動態角標顯示:http://developer.zuk.com/detail/12

就是這個代碼吶,有點亂糟糟,排版不太好。

2.5 剩下的吶?Enmmm,沒找到。

MMP。。。

2.6 簡單總結

Enmmm,只想說,華爲,我愛你~!

好人,一生平安吶~

LZ 總有自己的犟勁兒,同時 LZ 查看了程序猿最大異性交友平臺,找到了以下曾經“輝煌“的庫:

  • https://github.com/q1113225201/Badger

  • https://github.com/leolin310148/ShortcutBadger

Enmmm,還有良心博文:

  • Badge分析&如何逼死處女座:https://www.jianshu.com/p/0992ff9eeeb6

  • 【筆記】Android桌面角標Badge官方文檔和兼容性解決:https://blog.csdn.net/q1113225201/article/details/79858032

三、來波實踐

到現在,我算是明白了,想徹底兼容,估計還得兼容每個廠商 ROM,而且誰知道每個系統版本會不會出現一些變動,至少這些對於 LZ 目前而言,太過於困難,那麼,LZ 基於目前現有資料,盡力而爲吧。

LZ 簡單描述下本文 LZ 思路:

  1. 創建定時器,用於模擬接收消息,便於顯示於角標內;

  2. 依據上方提供文檔地址,整合工具類,當然,抽取部分 GitHub 當年優秀之作整合爲一個 Utils;

  3. enmmm,開搞。這裏需要注意小米需要單獨綁定通知以及對於未提供 API 接口的設備,LZ 目前能力有限,暫不涉及。

下面開發放大招咯~各位和 LZ 一樣的伸手黨福利來咯!

3.1 騷年,給我一波權限

<uses-permission android:name="android.permission.INTERNET" />
    <!-- 華爲角標 -->
    <uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
    <!-- 三星角標 -->
    <uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
    <uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
    <!-- HTC角標 -->
    <uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.htc.launcher.permission.UPDATE_SHORTCUT" />
    <!-- 聯想角標 -->
    <uses-permission android:name="android.permission.READ_APP_BADGE" />
    <!-- 索尼角標 -->
    <uses-permission android:name="com.sonymobile.home.permission.PROVIDER_INSERT_BADGE" />
    <uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
    <uses-permission android:name="com.sonyericsson.home.action.UPDATE_BADGE" />

3.2 騷年,Utils 奉上

package com.heliquan.badgedemo;

import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author: heliquan
 * @data: 2018/9/11
 * @desc: 機型角標適配
 */
public class BadgeUtils {

    /**
     * Retrieve launcher activity name of the application from the context
     *
     * @param context The context of the application package.
     * @return launcher activity name of this application. From the
     * "android:name" attribute.
     */
    public static String getLauncherClassName(Context context) {
        PackageManager packageManager = context.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        // To limit the components this Intent will resolve to, by setting an explicit package name.
        intent.setPackage(context.getPackageName());
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        // All Application must have 1 Activity at least.Launcher activity must be found!
        ResolveInfo info = packageManager
                .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
        // get a ResolveInfo containing ACTION_MAIN, CATEGORY_LAUNCHER if there is no Activity which has filtered by CATEGORY_DEFAULT
        if (info == null) {
            info = packageManager.resolveActivity(intent, 0);
        }
        // 另一種實現方式
        // ComponentName componentName = context.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName()).getComponent();
        // return componentName.getClassName();
        return info.activityInfo.name;
    }

    /**
     * 設置Badge 目前支持Launcher:
     * EXUI MIUI Sony Samsung LG HTC Nova
     * 魅族 努比亞 666 果斷不支持
     * OPPO VIVO 狗
     *
     * @param context  context
     * @param msgCount count
     */
    public static void setBadgeCount(Context context, int msgCount) {
        if (msgCount <= 0) {
            msgCount = 0;
        } else {
            msgCount = Math.max(0, Math.min(msgCount, 99));
        }
        Log.e("Love", "當前設備類型: " + Build.MANUFACTURER);
        if (Build.MANUFACTURER.toLowerCase().contains("huawei")) {
            setBadgeOfEXUI(context, msgCount);
        } else if (Build.MANUFACTURER.toLowerCase().contains("nova")) {
            setBadgeOfNova(context, msgCount);
        } else if (Build.MANUFACTURER.toLowerCase().contains("zuk")) {
            setBadgeOfZuk(context, msgCount);
        } else if (Build.MANUFACTURER.equalsIgnoreCase("sony")) {
            setBadgeOfSony(context, msgCount);
        } else if (Build.MANUFACTURER.toLowerCase().contains("samsung") ||
                Build.MANUFACTURER.toLowerCase().contains("lg")) {
            setBadgeOfSumsung(context, msgCount);
        } else if (Build.MANUFACTURER.toLowerCase().contains("htc")) {
            setBadgeOfHTC(context, msgCount);
        } else {
            // 不管了
        }
    }

    /**
     * 設置華爲Badge
     * 良心企業吶
     * 需要添加權限:<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
     *
     * @param context
     * @param count
     */
    private static void setBadgeOfEXUI(Context context, int count) {
        try {
            Bundle badgeBundle = new Bundle();
            badgeBundle.putString("package", context.getPackageName());
            badgeBundle.putString("class", getLauncherClassName(context));
            badgeBundle.putInt("badgenumber", count);
            context.getContentResolver().call(
                    Uri.parse("content://com.huawei.android.launcher.settings/badge/"),
                    "change_badge", null, badgeBundle);
        } catch (Exception e) {
        }
    }

    /**
     * 設置Nova的Badge
     *
     * @param context context
     * @param count   count
     */
    private static void setBadgeOfNova(Context context, int count) {
        ContentValues contentValues = new ContentValues();
        contentValues.put("tag", context.getPackageName() + "/" +
                getLauncherClassName(context));
        contentValues.put("count", count);
        context.getContentResolver().insert(
                Uri.parse("content://com.teslacoilsw.notifier/unread_count"),
                contentValues);
    }

    /**
     * 設置聯想ZUK的Badge
     * 需要添加權限:<uses-permission android:name="android.permission.READ_APP_BADGE" />
     *
     * @param context
     * @param count
     */
    private static void setBadgeOfZuk(Context context, int count) {
        Bundle extra = new Bundle();
        extra.putInt("app_badge_count", count);
        context.getContentResolver().call(
                Uri.parse("content://com.android.badge/badge"),
                "setAppBadgeCount", null, extra);
    }

    /**
     * 設置MIUI的Badge 小米需要和通知欄進行綁定 MMP
     */
    public static void getBadgeOfMINU(Notification notification, int count) {
        try {
            Field field = notification.getClass().getDeclaredField("extraNotification");
            Object extraNotification = field.get(notification);
            Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
            method.invoke(extraNotification, count);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 設置索尼的Badge
     * 需添加權限:<uses-permission android:name="com.sonymobile.home.permission.PROVIDER_INSERT_BADGE" />
     * <uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
     * <uses-permission android:name="com.sonyericsson.home.action.UPDATE_BADGE" />
     *
     * @param context context
     * @param count   count
     */
    private static void setBadgeOfSony(Context context, int count) {
        String launcherClassName = getLauncherClassName(context);
        if (launcherClassName == null) {
            return;
        }
        boolean isShow = true;
        if (count == 0) {
            isShow = false;
        }
        Intent localIntent = new Intent();
        localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE");
        localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", isShow); // 是否顯示
        localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", launcherClassName); // 啓動頁
        localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", String.valueOf(count)); // 數字
        localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.getPackageName()); // 包名
        context.sendBroadcast(localIntent);
    }

    /**
     * 設置三星的Badge設置LG的Badge
     * 需添加權限:<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
     * <uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
     *
     * @param context context
     * @param count   count
     */
    private static void setBadgeOfSumsung(Context context, int count) {
        // 獲取你當前的應用
        String launcherClassName = getLauncherClassName(context);
        if (launcherClassName == null) {
            return;
        }
        Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
        intent.putExtra("badge_count", count);
        intent.putExtra("badge_count_package_name", context.getPackageName());
        intent.putExtra("badge_count_class_name", launcherClassName);
        context.sendBroadcast(intent);
    }

    /**
     * 設置HTC的Badge
     *
     * @param context context
     * @param count   count
     */
    private static void setBadgeOfHTC(Context context, int count) {
        Intent intentNotification = new Intent("com.htc.launcher.action.SET_NOTIFICATION");
        ComponentName localComponentName = new ComponentName(context.getPackageName(),
                getLauncherClassName(context));
        intentNotification.putExtra("com.htc.launcher.extra.COMPONENT", localComponentName.flattenToShortString());
        intentNotification.putExtra("com.htc.launcher.extra.COUNT", count);
        context.sendBroadcast(intentNotification);
        Intent intentShortcut = new Intent("com.htc.launcher.action.UPDATE_SHORTCUT");
        intentShortcut.putExtra("packagename", context.getPackageName());
        intentShortcut.putExtra("count", count);
        context.sendBroadcast(intentShortcut);
    }

}

3.3 騷年,走起

package com.heliquan.badgedemo;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

/**
 * @author: heliquan
 * @data: 2018/9/11
 * @desc: 機型角標適配
 */
public class MainActivity extends AppCompatActivity {

    private MainActivity mSelfActivity = MainActivity.this;

    private TextView mShowBadge;

    // 模擬接收消息
    private Handler mBadgeHandler = new Handler();

    private int mBadgeCount = 1;

    private static final String CHANNEL_ID = "heliquan";
    private static final long[] VIBRATION_PATTERN = new long[]{0, 180, 80, 120};

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            int tempNUm = mBadgeCount++;
            mShowBadge.setText("當前未讀消息爲:" + tempNUm);
            Log.e("Love", "當前未讀消息爲:" + tempNUm);
            if (Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
                PackageManager pm = getPackageManager();
                Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName);
                PendingIntent pendingIntent = PendingIntent.getActivity(
                        mSelfActivity, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
                NotificationCompat.Builder builder = new NotificationCompat.Builder(mSelfActivity, CHANNEL_ID)
                        .setSmallIcon(getApplicationInfo().icon)
                        .setContentTitle(pm.getApplicationLabel(getApplicationInfo()).toString())
                        .setTicker("Ticker:" + tempNUm)
                        .setContentText("賀賀,我是第" + tempNUm + "個")
                        .setWhen(System.currentTimeMillis())
                        .setAutoCancel(true)
                        .setContentIntent(pendingIntent);
                Notification notification = builder.build();
                BadgeUtils.getBadgeOfMINU(notification, tempNUm);
                notificationManager.notify(0, notification);
            } else {
                BadgeUtils.setBadgeCount(mSelfActivity, tempNUm);
            }
            mBadgeHandler.postDelayed(this, 1500);
        }
    };
    private NotificationManager notificationManager;
    private String packageName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initNotification();
        initView();
    }

    private void initNotification() {
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= 26) {
            // Create the notification channel for Android 8.0
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "test for He.", NotificationManager.IMPORTANCE_DEFAULT);
            channel.setVibrationPattern(VIBRATION_PATTERN);
            notificationManager.createNotificationChannel(channel);
        }
        packageName = getApplicationInfo().packageName;
    }

    private void initView() {
        mShowBadge = findViewById(R.id.showBadgeNum);
        initViewData();
    }


    private void initViewData() {
        mBadgeHandler.postDelayed(runnable, 1500);
    }

}

3.4 來波最後的效果看看:

 

3.5 最後,LZ 附上目前測試通過的設備

小米5  MIUI 9.6 Android 版本 8.0.0 
紅米    note 4 MIUI 10 8.8.31 開發版  6.0
華爲    Honor 9 Lite  EMUI 8.0.0 系統 8.0
榮耀 9 系統 8.0

當然有好的方案歡迎一起溝通交流~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章