一文带你吃透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 的迁移
相关推荐
Joseph Cooper1 小时前
Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门
android·linux·运维
空中海1 小时前
安卓逆向03. 动态调试、抓包分析与 Frida Hook
android
一起搞IT吧2 小时前
相机Camera日志实例分析之二十:相机Camx【照片后置4800/5000/6400万拍照】单帧流程日志详解
android·嵌入式硬件·数码相机·智能手机
jinanwuhuaguo3 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
千码君20165 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
重生之我是Java开发战士8 小时前
【MySQL】事务 & 用户与权限管理
android·数据库·mysql
怣疯knight10 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio
ke_csdn10 小时前
从Java演变到Kotlin下的jet pack
android
wenzhangli711 小时前
在低代码设计中践行 Harness Engineering
android·低代码·rxjava
xingpanvip12 小时前
星盘接口开发文档:组合三限盘接口指南
android·开发语言·前端·python·php·lua