最新安卓提醒及角標功能實現概總
一、提要
P.S. 最近工作需要非要做安卓的小紅點,真是傷透了腦筋,做個總結記錄下
關於安卓提醒功能及角標的實現,主要有以下需要注意的點:
- Android系統默認是不支持角標的。於是乎國內各大廠家就各顯神通,每家的實現方法不一,爲適配增加了難度;
- Android API版本在26之後的notification實現方法與之前的實現方法不一,需要注意;
- 同一個廠家的不同版本實現方法也出現了不同。(如MIUI系統在近年來的高版本就出現了和之前實現方法的差異)
二、實現
在瞭解了上面需要注意的點之後,我們可以開始着手做實現代碼。每個廠家的實現文檔可以在廠家官網查找也可以百度,這裏也只是做了一個最新的總結。
1. 權限設置
需要以下權限:
<!-- 網絡權限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!--角標相關-->
<uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.READ"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE"/>
<!-- 申請華爲角標權限,注:華爲角標權限需要Internet權限生效的前提下才會生效 -->
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
<uses-permission android:name="com.huawei.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS" />
2. 發送notification
首先需要彈出一條notification,在Android API>26時,需要使用NotificationChannel提醒組來實現,這裏需要注意下。具體實現如下
public class CommonNotification {
// 消息提示管理器
private static NotificationManager manager;
private static NotificationChannel channel;
private CommonNotification() throws InstantiationException {
throw new InstantiationException("This class is not for instantiation");
}
/**
* 發送提醒
* @param intent 需要傳輸的數據,使用bundle傳輸,可在to中使用onGetNewIntent中獲取數據
* @param title 提醒標題
* @param text 提醒文本
* @param count 提醒條數,可顯示在應用圖標右上角的小紅點
* @param id 提醒的id,同id將覆蓋,可通過過id進行消除相應提醒
* @param start 發送提醒的context
* @param to 點擊提醒,需要跳轉的context
*/
public static void sendNotification(Intent intent , String title , String text , int count , int id , Context start , Context to){
if (manager == null){
manager = (NotificationManager) start.getSystemService(Context.NOTIFICATION_SERVICE);
}
if (channel == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//初始化方法中第一個參數爲chanel的id,後邊需要使用
channel = new NotificationChannel("1",
"Channel1", NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true); //是否在桌面icon右上角展示小紅點
channel.setBypassDnd(true);
channel.enableVibration(true);//震動
channel.setLightColor(Color.RED); //小紅點顏色
channel.setShowBadge(true); //是否在久按桌面圖標時顯示此渠道的通知
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
//region 設置啓動特徵,不重新繪製,這裏可以防止點擊提醒後跳轉進來新創建activity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//endregion
//初始化pendingIntent,需要跳轉的activity,id,intent參數等
PendingIntent pendingIntent = PendingIntent.getActivity(start, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//第二個參數即是上邊channel的id,即notification所屬的channel
builder = new Notification.Builder(to , "1");
}else {
builder = new Notification.Builder(to);
}
builder.setContentTitle(title)
.setContentText(text)
.setSmallIcon(android.R.drawable.stat_notify_chat)
.setContentIntent(pendingIntent)
.setAutoCancel(true)//設置點擊自動取消
.setDefaults(Notification.DEFAULT_SOUND)//設置提醒時的生效
.setNumber(8);//設置可摺疊提醒數量
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// //鎖屏提醒顯示內容
// builder.setVisibility(Notification.VISIBILITY_PUBLIC);
// }
Notification notification = builder.build();
//喚醒屏幕
PowerUtil.wakeAndUnlock(to , true);
//更新角標
try {
BadgeUtil.setBadgeCount(start.getApplicationContext() , count , notification);
} catch (Exception e) {
e.printStackTrace();
}
if (manager != null) {
manager.notify(id, notification);
}
}
/**
* 取消所有提醒
*/
public static void cancelAll(){
if (manager != null) {
manager.cancelAll();
}
}
}
3. Badge角標實現
依於前邊的討論以及各大官網上的API文檔,做工具類如下:
注:當前已測試通過的有Samsung、MIUI系列、華爲(含榮耀系列)、OPPO、vivo。歡迎各位評論自己的機型測試結果
public class BadgeUtil {
private BadgeUtil() throws InstantiationException {
throw new InstantiationException("This class is not for instantiation");
}
/**
* 設置Badge 目前支持Launcher
*/
public static void setBadgeCount(Context context, int count , Notification notification) {
if (count <= 0) {
count = 0;
} else {
count = Math.max(0, Math.min(count, 99));
}
String rom = Build.MANUFACTURER.toLowerCase();
if (rom.contains("xiaomi")) { //小米
setBadgeOfMIUI(count , notification);
} else if (rom.contains("sony")) { //索尼
setBadgeOfSony(context, count);
} else if (rom.contains("samsung") || rom.contains("lg")) {
setBadgeOfSumsung(context, count);
} else if (rom.contains("htc")) { //htc
setBadgeOfHTC(context, count);
} else if (rom.contains("nova")) { //nova
setBadgeOfNova(context, count);
}else if (rom.contains("oppo")) { //oppo
setBadgeOfOPPO(context, count);
}else if (rom.contains("lemobile")) {//樂視
Log.e("BadgeUtil", "setBadgeCount: 樂視 暫不支持");
}else if (rom.contains("vivo")) {
setBadgeOfVIVO(context, count);
}else if (rom.contains("huawei") || Build.BRAND.equals("Huawei") ||Build.BRAND.equals("HONOR") || rom.equalsIgnoreCase("huawei")) {//華爲
setHuaweiBadge(context, count);
}else if (rom.contains("meizu")) {//魅族
Log.e("BadgeUtil", "setBadgeCount: 魅族 暫不支持");
}else if (rom.contains("jinli")) {//金立
Log.e("BadgeUtil", "setBadgeCount: 金立 暫不支持");
}else if (rom.contains("chuizi")) {//錘子
Log.e("BadgeUtil", "setBadgeCount: 錘子 暫不支持");
}else {
Log.e("BadgeUtil", "setBadgeCount: 未匹配機型:"+rom);
}
}
/**
* 設置MIUI的Badge
*/
private static void setBadgeOfMIUI(int count , Notification notification) {
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.sonyericsson.home.permission.BROADCAST_BADGE" />
*/
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
*/
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
*/
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);
}
/**
* 設置Nova的Badge
*/
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);
}
/**
* 設置vivo的Badge :vivoXplay5 vivo x7無效果
*/
private static void setBadgeOfVIVO(Context context,int count){
try {
//老版可用,新版已封閉,需申請
Intent intent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM");
intent.putExtra("packageName", context.getPackageName());
String launchClassName = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().getClassName();
intent.putExtra("className", launchClassName); intent.putExtra("notificationNum", count);
context.sendBroadcast(intent);
}catch (Exception e){
e.printStackTrace();
}
}
/**
*設置oppo的Badge :oppo角標提醒目前只針對內部軟件還有微信、QQ開放,其他的暫時無法提供
*/
private static void setBadgeOfOPPO(Context context,int count){
try {
if (Constant.version >= Build.VERSION_CODES.LOLLIPOP) {
Bundle extras = new Bundle();
extras.putInt("app_badge_count", count);
context.getContentResolver().call(Uri.parse("content://com.android.badge/badge"), "setAppBadgeCount", String.valueOf(count), extras);
} else {
Intent intent = new Intent("com.oppo.unsettledevent");
intent.putExtra("packageName", context.getPackageName());
intent.putExtra("number", count);
intent.putExtra("upgradeNumber", count);
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> receivers = packageManager.queryBroadcastReceivers(intent, 0);
if (receivers != null && receivers.size() > 0) {
context.sendBroadcast(intent);
} else {
Bundle extras = new Bundle();
extras.putInt("app_badge_count", count);
context.getContentResolver().call(Uri.parse("content://com.android.badge/badge"),
"setAppBadgeCount", null, extras);
}
}
Log.e("BadgeUtil", "setBadgeOfOPPO: "+"設置角標");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 設置華爲的Badge :mate8 和華爲 p7,honor暢玩系列可以,honor6plus 無效果
*/
private static void setHuaweiBadge(Context context, int count)
{
//權限檢查
try {
Bundle bundle = new Bundle();
bundle.putString("package", context.getPackageName());
bundle.putString("class", getLauncherClassName(context));
bundle.putInt("badgenumber", count);
context.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setBadgeOfMadMode(Context context, int count, String packageName, String className) {
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", count);
intent.putExtra("badge_count_package_name", packageName);
intent.putExtra("badge_count_class_name", className);
context.sendBroadcast(intent);
}
/**
* 重置Badge
*/
public static void resetBadgeCount(Context context , Notification notification) {
setBadgeCount(context, 0 , notification);
}
private static String getLauncherClassName(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setPackage(context.getPackageName());
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (info == null) {
info = packageManager.resolveActivity(intent, 0);
}
return info.activityInfo.name;
}
}
3. 調用
調用時只需要調用sendNotification方法並傳入相關參數即可,如:
Bundle bundle = new Bundle();
bundle.putString("name" , Constant.intentMsg);
intent.putExtras(bundle);
CommonNotification.sendNotification(intent , title, text, count, id ,activity , activity);
其中bundle的參數可以在點擊提醒後跳轉的activity的onNewIntent方法中獲取值。這也就是如何通過notification傳遞參數的方法了。
三、相關文件
這裏提供一個demo供大家下載交流:
- 百度雲盤下載 提取碼:wr2r