一文带你吃透Android APP 各大厂商角标的适配!!!!

在 Android 生态中,应用角标(Badge)适配一直是开发者面临的痛点问题。由于 Android 系统本身未提供统一角标 API,不同厂商设备(如华为、小米、OPPO、vivo 等)均采用私有实现方案。以下是针对主流厂商的角标适配技术指南及未来趋势分析:

一、当前主流厂商适配方案

1. 原生 Android 方案(API 26+)

xml 复制代码
NotificationCompat.Builder(context, CHANNEL_ID)
    .setNumber(unreadCount)  // 数字角标
    .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) // 图标类型
  • 局限:仅部分 OEM 厂商支持,且显示逻辑不统一(如 Pixel 设备优先显示数字,三星可能显示红点)。

2. 华为 HMS Core

java 复制代码
// ================== 华为 ================
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);

//================== 荣耀 ====================
//荣耀从华为独立之后,其设置角标的规则也进行更改,不过整体的改动不大

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.hihonor.android.launcher.settings/badge/"), "change_badge", null, bundle);
  • 注意 :需申请 com.huawei.android.launcher.permission.CHANGE_BADGE 权限。

3. 小米 MIUI

java 复制代码
Intent intent = new Intent("android.intent.action.APPLICATION_MESSAGE_UPDATE");
intent.putExtra("android.intent.extra.update_application_component_name", 
    context.getPackageName() + "/" + launcherClassName);
intent.putExtra("android.intent.extra.update_application_message_text", count);
context.sendBroadcast(intent);
  • 限制 :当 count=0 时需发送空字符串清除角标。
  • 注意:小米手机比较特殊,其App角标与App通知相关联,无法脱离通知栏独立设置角标未读数量。

4. OPPO ColorOS

java 复制代码
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(); 
     } 
}

private static boolean canResolveBroadcast(Context context, Intent intent) {
    PackageManager packageManager = context.getPackageManager(); 
    List<ResolveInfo> receivers = packageManager.queryBroadcastReceivers(intent, 0); 
    return receivers != null && receivers.size() > 0; 
}
  • 要求 :需添加权限 <uses-permission android:name="com.oppo.launcher.permission.WRITE_SETTINGS"/>

5. vivo

5.1、Funtouch OS

java 复制代码
Intent intent = new Intent();
int missedCalls = 10;
intent.setAction("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM");
intent.putExtra("packageName", "com.android.xxxx");//接入方自己的包名
intent.putExtra("className", "com.android.xxxx.Mainxxxx");//对应接入方的launcher入口的activity全路径activity名字(AndroidManifest中标识了android.intent.category.LAUNCHER的activity)
intent.putExtra("notificationNum", missedCalls); 
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
sendBroadcast(intent);
  • 在ard8.0以后,还需要给Intent加上下面的Flag

    Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND

  • 如果此Flag获取不到,则改为此方法

java 复制代码
public static int invokeIntconstants(String CanonicalName, String name, int default_value) {
    int value = default_value;
    try {
        Class<?> c = Class.forName(CanonicalName);
        Field Field = c.getField(name);
        value = (int) Field.get(c);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return value;
    }
}
  • 限制:仅支持数字角标,且最大显示值为 99。
  • 权限 :需申请 com.vivo.notification.permission.BADGE_ICON 权限。

5.2、Origin OS

java 复制代码
Class<?> spClass = Class.forName("android.os.SystemProperties");
Method method = spClass.getMethod("get", String.class, String.class);
method.setAccessible(true);
currentOsName= (String) method.invoke(null, "ro.vivo.os.name" ,defName); 
currentOsVersion=(String) method.invoke(null, "ro.vivo.os.version" ,defVersion); 
  • 权限 :需申请 com.vivo.abe.permission.launcher.notification.num 权限。

示例代码

java 复制代码
public static void setBadgeNumber() {
        Uri uri = Uri.parse("content://" +"com.vivo.abe.provider.launcher.notification.num");
        Bundle extra = new Bundle();
        extra.putString("package", String);//接入的App包名
        extra.putString("class", String);//接入的App class名 
        extra.putInt("badgenumber", int);//目标的角标数 
        /*这里一定要先使用 ContentProviderClient 建立非稳连接,不可以直接通过 getContentResolver()调用 call 方法,会有 Server 端崩溃带崩 Client 端的风险*/
        ContentProviderClient client = null;
        try {
            client = getContentResolver().acquireUnstableContentProviderClient(uri);
            if (client != null) {
                int result = client.call("change_badge", null, extra).getInt("result");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (client != null) {
                if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.N){                   
                    client.close();
                } else {
                    client.release();
                }
            }
        }
    }

6. 三星

java 复制代码
public static void setBadgeNumber(Context context, int number) {
    Intent localIntent = new Intent("android.intent.action.BADGE_COUNT_UPDATE"); //数字 
    localIntent.putExtra("badge_count", number); //包名 
    localIntent.putExtra("badge_count_package_name", context.getPackageName()); //启动页 
    localIntent.putExtra("badge_count_class_name", 
    BadgeNumberManager.getLauncherClassName(context)); 
    context.sendBroadcast(localIntent); 
}

7. sony

java 复制代码
public static void setBadgeNumber(Context context, int number) {
    boolean isShow = true; 
    if ("0".equals(number)) {
        isShow = false;
    }
    Intent localIntent = new Intent(); //是否显示 
    localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", isShow); 
    localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE"); //启动页 
    localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", 
    BadgeNumberManager.getLauncherClassName(context)); //数字 
    localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", number); //包名 
    localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", 
    context.getPackageName());
    context.sendBroadcast(localIntent);
}

二、统一适配方案建议

1. 使用 ShortcutBadger 库

GitHub地址

gradle 复制代码
implementation 'me.leolin:ShortcutBadger:1.1.22'
java 复制代码
// 设置角标
ShortcutBadger.applyCount(context, count); 

// 清除角标
ShortcutBadger.removeCount(context); 
  • 优势:封装了 30+ 厂商的私有 API,覆盖华为、小米、三星等主流设备。
  • 局限:无法保证 100% 设备兼容性,需定期更新。

2. 分层适配策略

java 复制代码
public class BadgeUtils {
    public static void updateBadge(Context context, int count) {
        if (Build.VERSION.SDK_INT >= 26) {
            // 使用原生 Notification 角标
            updateNotificationBadge(context, count);
        } else {
            // 厂商私有 API 适配
            if (isXiaomi()) {
                updateXiaomiBadge(context, count);
            } else if (isHuawei()) {
                updateHuaweiBadge(context, count);
            }
            // 其他厂商...
        }
    }
}

三、未来趋势与挑战

  1. 折叠屏/多任务场景
    折叠屏设备的分屏模式要求角标在多任务界面同步更新,需监听 ActivityManager.AppTask 状态变化。
  2. 动态岛(Dynamic Island)扩展
    Android 14 引入的 "Enhanced Notifications" 支持更丰富的交互式角标,需适配新的 Notification.Style
  3. 隐私合规要求
    Android 13 限制后台广播接收器,需改用 JobSchedulerWorkManager 异步更新角标。
  4. 统一标准推进
    谷歌正在推动 Unified Badge API 提案(预计 Android 15 落地),未来可能通过 LauncherApps 服务提供标准化接口。

四、适配检查清单

  1. ✅ 在 AndroidManifest.xml 中声明所有所需权限
  2. ✅ 获取 Launcher Activity 的完整类名(如 com.example.MainActivity
  3. ✅ 处理厂商 ROM 的版本差异(如 MIUI 12 与 MIUI 13 的角标逻辑变化)
  4. ✅ 在应用设置中引导用户开启通知权限
  5. ✅ 使用云真机测试平台验证多设备兼容性

总结:

Android 角标适配本质上是与厂商生态的博弈,开发者需在 统一封装厂商定制 间找到平衡点。随着 Android 15 标准化进程的推进,未来有望通过统一 API 降低适配成本,但短期内仍需保持多路径适配策略。

更多分享

  1. 一文带你吃透Kotlin协程的launch()和async()的区别
  2. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  3. 一文带你吃透Android中常见的高效数据结构
  4. 一文带你吃透Android中Service的种类和启动方式
  5. 一文带你顺利完成从 Groovy 到 Kotlin DSL 的迁移
相关推荐
运维_攻城狮1 小时前
遇到liunx服务器IO负载,读IO流量峰值347MB/s,排查并解决。
android·运维·服务器·mysql
m0_748232392 小时前
qwenvl 以及qwenvl 2 模型架构理解
android·前端·后端
web136885658712 小时前
PHP For 循环
android·java·php
Neo Evolution3 小时前
每天一个Flutter开发小项目 (6) : 表单与验证的专业实践 - 构建预约应用
android·开发语言·前端·javascript·flutter
小墙程序员3 小时前
Android Framework 面试系列(四)Activity 启动原理
android
和道一文字yyds3 小时前
MySQL 中如何解决深度分页的问题?什么是 MySQL 的主从同步机制?它是如何实现的?如何处理 MySQL 的主从同步延迟?
android·数据库·mysql
KdanMin3 小时前
Android SystemUI深度定制实战:下拉状态栏集成响铃功能开关全解析
android
黄大包3 小时前
解决安卓recyclerView滚到底部不彻底问题
android·滚动·recycleview·抖动
胖虎13 小时前
Android 布局系列(四):ConstraintLayout 使用指南
android·xml·布局·constraint·约束布局
Android小码家3 小时前
Android SystemUI开发(一)
android·framework·systemui