LZ-Says:情,杂乱。绪,如同乱麻。若年的感情,似乎像个破碎的镜子,一点点破碎,一次次分裂。看着破碎镜子中的自己,呵,MD,感情的白痴。挫败感,席卷而来,压的透不过气。像个 SB 一样,以为搞点 502 就能粘合了?真特么的是个天字一号纯 SB。满手的渣子沫,似乎疼过劲儿了。余下时光,enmmm,各自安好。
一、前言
话说,在某天,正在烦恼某个功能点如何实现更好、更快,老大来了一句,iOS 应用图标有未读提示,这个华为手机怎么没有呢?来,搞一下。
朦朦胧胧接了任务。嗯,就这样开启了辛酸路。
来,过来个人,抱着哭会儿。
先看看效果图,手机拍个短信、钉钉、QQ 的小效果:
看到了吗?zou si 它~!
昏暗无光的样式呐,默默 MMP~!
Enmmm,另外在此注明下,本内容由 LZ 小白以及偶家明远小哥哥一块完成,下面贴出小哥哥博客地址:
Enmmm,下面开启辛酸路吧。
二、辛酸路
首先看到应用程序 Logo 显示未读消息,内心第一想法便是,So so easy,相比良好的厂商以及提供了相关 Api 咯,LZ 无非就是整合一下,然而卵。
想了想,目前市面上主流的几款机型:
华为
小米
VIVO,OPPO
爆炸神机 三星
魅族、一加、索尼、联想。。。
想想要兼容每家好烦呢,也不知道文档写的 6 不 6 ,对于 LZ 这样小白理解力够不够。事实证明,LZ 想多了,满脸的生无可恋。
2.1 华为
角标是华为桌面提供给各应用显示未读消息用的,会在应用图标右上角绘制一张消息条数的图标。
贴心的华为为我们描述了角标的作用。
华为桌面角标业务介绍:https://developer.huawei.com/consumer/cn/devservice/doc/30801;
华为桌面角标开发指导书:https://developer.huawei.com/consumer/cn/devservice/doc/30802;
华为桌面未读角标对外接口说明书:http://obs.cn-north-1.myhwclouds.com/consumer/docattachment/87918b190abda6d7b7a568a7ef1dfc314cd9ad040faccf1a999dcff158ec7d79/badge.pdf
这里需要注意:
当桌面不支持角标功能时,接口会抛出异常,应用可以在调用接口的地方加上try … catch(Exception e) 语句以免程序崩溃。
2.2 小米
- MIUI 6 至 MIUI 10 桌面角标适配说明:https://dev.mi.com/console/doc/detail?pId=939
这里需要注意:
当应用向通知栏发送了一条通知 (除了进度条样式和常驻通知外),应用图标的右上角就会显示「1」。值得一提,角标的数字代表应用的通知数,即应用发送了「x」条通知,角标就会显示为「x」。
如果开发者不满意默认逻辑,想要自定义角标的数字,可以通过调用接口告知系统即可。
2.3 OPPO、VIVO
Enmmm,上面俩个地址暂时找不到,LZ 附上询问客服的截图,供大家一览~
上面是 OPPO ,下面 VIVO,不愧是好基友。
2.4 联想
- 应用图标动态角标显示:http://developer.zuk.com/detail/12
就是这个代码呐,有点乱糟糟,排版不太好。
2.5 剩下的呐?Enmmm,没找到。
MMP。。。
2.6 简单总结
Enmmm,只想说,华为,我爱你~!
LZ 总有自己的犟劲儿,同时 LZ 查看了程序猿最大异性交友平台,找到了以下曾经“辉煌“的库:
Enmmm,还有良心博文:
Badge分析&如何逼死处女座:https://www.jianshu.com/p/0992ff9eeeb6
【笔记】Android桌面角标Badge官方文档和兼容性解决:https://blog.csdn.net/q1113225201/article/details/79858032
三、来波实践
到现在,我算是明白了,想彻底兼容,估计还得兼容每个厂商 ROM,而且谁知道每个系统版本会不会出现一些变动,至少这些对于 LZ 目前而言,太过于困难,那么,LZ 基于目前现有资料,尽力而为吧。
LZ 简单描述下本文 LZ 思路:
- 创建定时器,用于模拟接收消息,便于显示于角标内;
- 依据上方提供文档地址,整合工具类,当然,抽取部分 GitHub 当年优秀之作整合为一个 Utils;
- enmmm,开搞。这里需要注意小米需要单独绑定通知以及对于未提供 API 接口的设备,LZ 目前能力有限,暂不涉及。
下面开发放大招咯~各位和 LZ 一样的伸手党福利来咯!
3.1 骚年,给我一波权限
<uses-permission android:name="android.permission.INTERNET" />
<!-- 华为角标 -->
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
<!-- 三星角标 -->
<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
<!-- HTC角标 -->
<uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.htc.launcher.permission.UPDATE_SHORTCUT" />
<!-- 联想角标 -->
<uses-permission android:name="android.permission.READ_APP_BADGE" />
<!-- 索尼角标 -->
<uses-permission android:name="com.sonymobile.home.permission.PROVIDER_INSERT_BADGE" />
<uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
<uses-permission android:name="com.sonyericsson.home.action.UPDATE_BADGE" />
3.2 骚年,Utils 奉上
package com.heliquan.badgedemo;
import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author: heliquan
* @data: 2018/9/11
* @desc: 机型角标适配
*/
public class BadgeUtils {
/**
* Retrieve launcher activity name of the application from the context
*
* @param context The context of the application package.
* @return launcher activity name of this application. From the
* "android:name" attribute.
*/
public static String getLauncherClassName(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
// To limit the components this Intent will resolve to, by setting an explicit package name.
intent.setPackage(context.getPackageName());
intent.addCategory(Intent.CATEGORY_LAUNCHER);
// All Application must have 1 Activity at least.Launcher activity must be found!
ResolveInfo info = packageManager
.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
// get a ResolveInfo containing ACTION_MAIN, CATEGORY_LAUNCHER if there is no Activity which has filtered by CATEGORY_DEFAULT
if (info == null) {
info = packageManager.resolveActivity(intent, 0);
}
// 另一种实现方式
// ComponentName componentName = context.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName()).getComponent();
// return componentName.getClassName();
return info.activityInfo.name;
}
/**
* 设置Badge 目前支持Launcher:
* EXUI MIUI Sony Samsung LG HTC Nova
* 魅族 努比亚 666 果断不支持
* OPPO VIVO 狗
*
* @param context context
* @param msgCount count
*/
public static void setBadgeCount(Context context, int msgCount) {
if (msgCount <= 0) {
msgCount = 0;
} else {
msgCount = Math.max(0, Math.min(msgCount, 99));
}
Log.e("Love", "当前设备类型: " + Build.MANUFACTURER);
if (Build.MANUFACTURER.toLowerCase().contains("huawei")) {
setBadgeOfEXUI(context, msgCount);
} else if (Build.MANUFACTURER.toLowerCase().contains("nova")) {
setBadgeOfNova(context, msgCount);
} else if (Build.MANUFACTURER.toLowerCase().contains("zuk")) {
setBadgeOfZuk(context, msgCount);
} else if (Build.MANUFACTURER.equalsIgnoreCase("sony")) {
setBadgeOfSony(context, msgCount);
} else if (Build.MANUFACTURER.toLowerCase().contains("samsung") ||
Build.MANUFACTURER.toLowerCase().contains("lg")) {
setBadgeOfSumsung(context, msgCount);
} else if (Build.MANUFACTURER.toLowerCase().contains("htc")) {
setBadgeOfHTC(context, msgCount);
} else {
// 不管了
}
}
/**
* 设置华为Badge
* 良心企业呐
* 需要添加权限:<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
*
* @param context
* @param count
*/
private static void setBadgeOfEXUI(Context context, int count) {
try {
Bundle badgeBundle = new Bundle();
badgeBundle.putString("package", context.getPackageName());
badgeBundle.putString("class", getLauncherClassName(context));
badgeBundle.putInt("badgenumber", count);
context.getContentResolver().call(
Uri.parse("content://com.huawei.android.launcher.settings/badge/"),
"change_badge", null, badgeBundle);
} catch (Exception e) {
}
}
/**
* 设置Nova的Badge
*
* @param context context
* @param count count
*/
private static void setBadgeOfNova(Context context, int count) {
ContentValues contentValues = new ContentValues();
contentValues.put("tag", context.getPackageName() + "/" +
getLauncherClassName(context));
contentValues.put("count", count);
context.getContentResolver().insert(
Uri.parse("content://com.teslacoilsw.notifier/unread_count"),
contentValues);
}
/**
* 设置联想ZUK的Badge
* 需要添加权限:<uses-permission android:name="android.permission.READ_APP_BADGE" />
*
* @param context
* @param count
*/
private static void setBadgeOfZuk(Context context, int count) {
Bundle extra = new Bundle();
extra.putInt("app_badge_count", count);
context.getContentResolver().call(
Uri.parse("content://com.android.badge/badge"),
"setAppBadgeCount", null, extra);
}
/**
* 设置MIUI的Badge 小米需要和通知栏进行绑定 MMP
*/
public static void getBadgeOfMINU(Notification notification, int count) {
try {
Field field = notification.getClass().getDeclaredField("extraNotification");
Object extraNotification = field.get(notification);
Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
method.invoke(extraNotification, count);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置索尼的Badge
* 需添加权限:<uses-permission android:name="com.sonymobile.home.permission.PROVIDER_INSERT_BADGE" />
* <uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
* <uses-permission android:name="com.sonyericsson.home.action.UPDATE_BADGE" />
*
* @param context context
* @param count count
*/
private static void setBadgeOfSony(Context context, int count) {
String launcherClassName = getLauncherClassName(context);
if (launcherClassName == null) {
return;
}
boolean isShow = true;
if (count == 0) {
isShow = false;
}
Intent localIntent = new Intent();
localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE");
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", isShow); // 是否显示
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", launcherClassName); // 启动页
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", String.valueOf(count)); // 数字
localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.getPackageName()); // 包名
context.sendBroadcast(localIntent);
}
/**
* 设置三星的Badge设置LG的Badge
* 需添加权限:<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
* <uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
*
* @param context context
* @param count count
*/
private static void setBadgeOfSumsung(Context context, int count) {
// 获取你当前的应用
String launcherClassName = getLauncherClassName(context);
if (launcherClassName == null) {
return;
}
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", count);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", launcherClassName);
context.sendBroadcast(intent);
}
/**
* 设置HTC的Badge
*
* @param context context
* @param count count
*/
private static void setBadgeOfHTC(Context context, int count) {
Intent intentNotification = new Intent("com.htc.launcher.action.SET_NOTIFICATION");
ComponentName localComponentName = new ComponentName(context.getPackageName(),
getLauncherClassName(context));
intentNotification.putExtra("com.htc.launcher.extra.COMPONENT", localComponentName.flattenToShortString());
intentNotification.putExtra("com.htc.launcher.extra.COUNT", count);
context.sendBroadcast(intentNotification);
Intent intentShortcut = new Intent("com.htc.launcher.action.UPDATE_SHORTCUT");
intentShortcut.putExtra("packagename", context.getPackageName());
intentShortcut.putExtra("count", count);
context.sendBroadcast(intentShortcut);
}
}
3.3 骚年,走起?
package com.heliquan.badgedemo;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
/**
* @author: heliquan
* @data: 2018/9/11
* @desc: 机型角标适配
*/
public class MainActivity extends AppCompatActivity {
private MainActivity mSelfActivity = MainActivity.this;
private TextView mShowBadge;
// 模拟接收消息
private Handler mBadgeHandler = new Handler();
private int mBadgeCount = 1;
private static final String CHANNEL_ID = "heliquan";
private static final long[] VIBRATION_PATTERN = new long[]{0, 180, 80, 120};
Runnable runnable = new Runnable() {
@Override
public void run() {
int tempNUm = mBadgeCount++;
mShowBadge.setText("当前未读消息为:" + tempNUm);
Log.e("Love", "当前未读消息为:" + tempNUm);
if (Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
PackageManager pm = getPackageManager();
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName);
PendingIntent pendingIntent = PendingIntent.getActivity(
mSelfActivity, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mSelfActivity, CHANNEL_ID)
.setSmallIcon(getApplicationInfo().icon)
.setContentTitle(pm.getApplicationLabel(getApplicationInfo()).toString())
.setTicker("Ticker:" + tempNUm)
.setContentText("贺贺,我是第" + tempNUm + "个")
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setContentIntent(pendingIntent);
Notification notification = builder.build();
BadgeUtils.getBadgeOfMINU(notification, tempNUm);
notificationManager.notify(0, notification);
} else {
BadgeUtils.setBadgeCount(mSelfActivity, tempNUm);
}
mBadgeHandler.postDelayed(this, 1500);
}
};
private NotificationManager notificationManager;
private String packageName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initNotification();
initView();
}
private void initNotification() {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= 26) {
// Create the notification channel for Android 8.0
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"test for He.", NotificationManager.IMPORTANCE_DEFAULT);
channel.setVibrationPattern(VIBRATION_PATTERN);
notificationManager.createNotificationChannel(channel);
}
packageName = getApplicationInfo().packageName;
}
private void initView() {
mShowBadge = findViewById(R.id.showBadgeNum);
initViewData();
}
private void initViewData() {
mBadgeHandler.postDelayed(runnable, 1500);
}
}
3.4 来波最后的效果看看:
3.5 最后,LZ 附上目前测试通过的设备
小米5 MIUI 9.6 Android 版本 8.0.0
红米 note 4 MIUI 10 8.8.31 开发版 6.0
华为 Honor 9 Lite EMUI 8.0.0 系统 8.0
荣耀 9 系统 8.0
当然有好的方案欢迎一起沟通交流~
四、个人公众号
不定期发布博文,最近有点忙,感谢老铁理解,欢迎关注~