Android App 厂商角标适配

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


一、背景 

本篇介绍一下笔者在维护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-->

<!--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切换到后台再统一更新,这样可以大大的减少更新频率。优化策略后线上由于频繁更新角标引发的卡顿也就消失了。

总结

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

作者:半山居士
链接:https://juejin.cn/post/7475229875822559273


来源: 互联网
本文观点不代表码客-全球程序员交流社区立场,不承担法律责任,文章及观点也不构成任何投资意见。

赞 ()

相关推荐

  • 置顶 开发项目接单群,免费入群了

    无论你是Android、ios、java、php,或者你是产品经理、老板,都可以免费入群接单或者发布项目,全程不收取任何费用。

    2025年02月21日 15点27分
  • Android App 厂商角标适配

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

    2025年02月26日 23点07分
  • 鸿蒙Next-方法装饰器以及防抖方法注解实现

    以下是关于 鸿蒙Next(HarmonyOS NEXT)中 MethodDecorator 的详细介绍及使用指南,结合了多个技术来源的实践总结:

    2025年02月26日 22点58分
  • DevEco Studio常用快捷键以及如何跟AndroidStudio的保持同步

    DevEco Studio是华为推出的用于开发HarmonyOS应用的集成开发环境,它提供了丰富的快捷键以提高开发效率,以下为你详细介绍不同操作场景下的常用快捷键:

    2025年02月26日 22点56分
  • Android | 利用ItemDecoration绘制RecyclerView分割线

    RecyclerView.ItemDecoration 是 Android 提供的一种扩展机制,用于为 RecyclerView 的每个子项(Item)添加装饰(Decoration)。它通常用于绘制分割线、边距、背景等,目的是增强 RecyclerView 的显示效果。

    2025年02月26日 22点52分
  • Android 布局优化:利用 ViewStub 和 Merge 提升性能

    提升界面渲染性能是一个至关重要的任务,尤其是在应用启动时,渲染界面需要快速且流畅。为了优化 UI 渲染速度,Android 提供了许多工具,其中 ViewStub 和 Merge 标签是非常有效的布局优化手段。通过合理使用这两者,可以延迟加载不必要的视图、减少布局的嵌套层级,从而加速应用的启动和运行。

    2025年02月26日 22点47分
  • 玩转 ImageView.ScaleType:图片的缩放与裁剪技巧

    ImageView 是最常用的控件之一,它用于展示各种类型的图片。为了能够根据需求调整图片的显示效果,Android 提供了 ImageView.ScaleType 枚举,它可以灵活地控制图片如何适应 ImageView 的尺寸。本文将探讨 ImageView.ScaleType 的不同选项、使用场景及其实现技巧。

    2025年02月26日 22点45分
  • Android加快你的编译速度

    工欲善其事,必先利其器。如果每次运行项目都要花费5-10分钟,那人的心态都要崩了。

    2025年02月25日 15点40分
  • Flutter多渠道打包的解决方案(walle)

    我们的应用集成了TalkingData这个第三方工具来统计日活、事件等,需要在应用启动时初始化SDK,这个时候需要传入当前渠道,以便数据的统计。 由于最开始是用脚本一个个打包,一直想解决打包过慢的问题,但是网上大多教程是基于命令行参数或flavor,实质上并没有根本解决打包效率的问题。直到发现了文章的主角 walle。

    2025年02月25日 15点36分
  • Android-分享一个对RecyclerView二次封装的库(EasyRecyclerView)

    在日常的Android开发当中,我们肯定会有使用到RecyclerView的需求,这里分享一个对RecyclerView二次封装的开源库——EasyRecyclerView。它基本上满足基本的开发需求,希望能帮看文章的小伙伴提高开发效率。

    2025年02月25日 15点31分
  • Android原生系统真的那么好用吗?安卓原生系统吊打其他系统,因为有Google-Play,所以应用都是纯净的?

    实际方面,原生 Android 的最大优势是更新快速,对供应商特别是 Android 上游的补丁的合并速度非常快,几乎总能在第一时间更新。因为系统越原生,更新的代价就越小。反之,如果一个停止更新的原生 Android 其实是没有多大意义的,它失去了它最大的优势。

    2025年02月25日 15点30分
  • Application 作为 Dialog 的 Context?小心踩坑!

    大家好,相信大家在使用 Dialog 时,都有一个非常基本的认知:就是 Dialog 的 context 只能是 Activity,而不能是 Application,不然会导致弹窗崩溃:

    2025年02月25日 15点25分
  • Android 应用的线程世界:最少需要几个线程才能启动?

    这篇文章主要介绍了 Android App 中的多种线程,包括守护线程(如 Signal Catcher 等)、渲染线程、主线程、三方线程(如 OkHttp、Glide、ARouter 相关线程)等,还提及三方库中线程池的情况及可能存在的问题。

    2025年02月25日 15点13分
  • Android 复杂项目崩溃率收敛至0.01%实践

    在我们的项目中,每个版本发布之后,我们会创建一个opt分支,用于修复线上崩溃以及业务逻辑BUG。

    2025年02月25日 15点11分
  • Android 能悄悄知道用户截屏?这里有你想要的答案

    很多应用在当你截屏的时候能够感知到,并提示你是否要发送截屏等等。

    2025年02月25日 15点07分
  • 科大讯飞讯飞星火API能力免费开放,引领大模型商业化新篇章

    合肥本土科技巨头科大讯飞传来振奋人心的消息:讯飞星火API能力正式向公众免费开放,其中,讯飞星火Lite API更是实现了永久免费。这一举措无疑为整个行业注入了新的活力,也彰显了科大讯飞在大模型商业化进程中的坚定决心。

    2025年02月21日 15点57分

发表回复

评论列表

点击查看更多

    联系我们

    在线咨询: QQ交谈

    微信:dxmcpjl

    邮件:1529097251#qq.com

    工作时间:周一至周五,9:30-18:30,节假日休息

    微信