一、前言
話說,在某天,正在煩惱某個功能點如何實現更好、更快,老大來了一句,iOS 應用圖標有未讀提示,這個華爲手機怎麼沒有呢?來,搞一下。
朦朦朧朧接了任務。嗯,就這樣開啓了辛酸路。
來,過來個人,抱着哭會兒。
先看看效果圖,手機拍個短信、釘釘、QQ 的小效果:
看到了嗎?zou si 它~!
昏暗無光的樣式吶,默默 MMP~!
Enmmm,另外在此註明下,本內容由 LZ 小白以及偶家明遠小哥哥一塊完成,下面貼出小哥哥博客地址:
Enmmm,下面開啓辛酸路吧。
二、辛酸路
首先看到應用程序 Logo 顯示未讀消息,內心第一想法便是,So so easy,相比良好的廠商以及提供了相關 Api 咯,LZ 無非就是整合一下,然而卵。
想了想,目前市面上主流的幾款機型:
-
華爲
-
小米
-
VIVO,OPPO
-
爆炸神機 三星
-
魅族、一加、索尼、聯想。。。
想想要兼容每家好煩呢,也不知道文檔寫的 6 不 6 ,對於 LZ 這樣小白理解力夠不夠。事實證明,LZ 想多了,滿臉的生無可戀。
一起來看“每家“ API 文檔
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
-
OPPO 開發平臺:https://open.oppomobile.com/wiki/doc#id=10026;
-
VIVO 開放平臺:https://dev.vivo.com.cn/documentCenter/doc/135。
Enmmm,上面倆個地址暫時找不到,LZ 附上詢問客服的截圖,供大家一覽~
上面是 OPPO ,下面 VIVO,不愧是好基友。
2.4 聯想
-
應用圖標動態角標顯示:http://developer.zuk.com/detail/12
就是這個代碼吶,有點亂糟糟,排版不太好。
2.5 剩下的吶?Enmmm,沒找到。
MMP。。。
2.6 簡單總結
Enmmm,只想說,華爲,我愛你~!
好人,一生平安吶~
LZ 總有自己的犟勁兒,同時 LZ 查看了程序猿最大異性交友平臺,找到了以下曾經“輝煌“的庫:
-
https://github.com/q1113225201/Badger
-
https://github.com/leolin310148/ShortcutBadger
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
當然有好的方案歡迎一起溝通交流~