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

2,517 阅读4分钟

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

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

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

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

2. 华为 HMS Core

// ================== 华为 ================
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

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

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

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获取不到,则改为此方法
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

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 权限。

示例代码

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. 三星

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

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地址

implementation 'me.leolin:ShortcutBadger:1.1.22'
// 设置角标
ShortcutBadger.applyCount(context, count); 

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

2. 分层适配策略

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 限制后台广播接收器,需改用 JobScheduler 或 WorkManager 异步更新角标。
  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 的迁移