Android App 厂商角标适配

949 阅读3分钟

一、背景

本篇介绍一下笔者在维护IM应用时,设置App角标的相关经验。同时这里设置角标都是基于系统厂商的Launcher,没有适配三方的Launcher应用,因为我们统计下来发现近些年使用三方Launcher应用比较少了,大部分用户还是以系统Launcher为主。所在在我们的项目中,主要是适配各个厂商。

二、厂商角标设置规则

华为

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);

vivo

Funtouch OS

申请权限

<!--funtouch os-->
<uses-permission android:name="com.vivo.notification.permission.BADGE_ICON" />

适配代码:

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;
        }
    }

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); 

申请权限

<!--origin os-->
<uses-permission android:name="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();
                }
            }
        }
    }

魅族

申请权限

<!--魅族角标-->
<uses-permission android:name="com.meizu.flyme.launcher.permission.WRITE_BADGE_EXTRAS"/>

设置代码

Bundle extra = new Bundle();
extra.putString("package", context.getPackageName());
extra.putString("class", "yourLauncherClassName");
extra.putInt("badge_number", number);
context.getContentResolver().call(Uri.parse("content://" + "com.meizu.flyme.launcher.app_extras" + "/badge_extras"), "change_badge", null, extra);

OPPO

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;
}

小米

小米手机比较特殊,其App角标与App通知相关联,无法脱离通知栏独立设置角标未读数量。

三星

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);
}

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);
}

三、总结&注意事项

封装建议

以上各个厂商设置角标的方式都不同,可以考虑封装成不同的策略,基于Build.MANUFACTURER字段来选择不同的策略,使上层无感知。

注意事项

在实践过程中,我们发现频繁设置角标可能会引发卡顿。

起因是我们的卡顿监控发现部分卡顿堆栈竟然是设置角标,经过排查发现用户接收消息非常频繁。而我们的策略是只要应用内的未读数发生变化,那么就会立刻更新角标的未读数。这就导致可能会非常频繁的更新应用角标。

发现此问题后,我们调整了策略。优化后的策略是当App在前台时,不更新角标。当App切换到后台再统一更新,这样可以大大的减少更新频率。优化策略后线上由于频繁更新角标引发的卡顿也就消失了。

总结

本篇主要是总结一下各个厂商的角标适配代码,以及过程中我们遇到的问题。读者遇到相关需求直接拿来使用就好,不过厂商也可能会随时更新,使用时也请做好测试。