记录一次兼容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

当然有好的方案欢迎一起沟通交流~

猜你喜欢

转载自blog.csdn.net/czl0325/article/details/82769537