背景:
上一篇文章马哥有分享关于桌面角标应该如何设计的文章:
聊一聊android桌面的未读计数角标应该如何设计呢?
有一个学员朋友就积极联系马哥,说他原来做第三方应用的,有负责过app在各个手机桌面显示角标功能,对这个角标适配深感麻烦,也非常希望google官方可以完全统一这一块的桌面角标功能各个手机厂商各自为战的情况。
学员朋友也分享出来的了他以前适配各个手机厂商的桌面角标的相关详细接口调用,当时适配有10种手机的桌面角标功能。
下面学员朋友开始分享app适配各个手机厂商的桌面角标。
适配整体情况:
适配角标一般需要去各个厂商的官方文档中进行寻找,有的厂商还经常没有文档,这个时候可能就需要通过一些官方商务渠道或者论坛售后渠道等联系获取适配方案。不过最简单方法当然是去网上搜人家现成开源的,当时我们就有参考github上一个开源的角标适配库,用它就可以实现大部分主流手机桌面的角标功能。
接口设计讲解:
主要有以下2个接口方法:
void executeBadge
主要负责真正实现对角标的设置,也就是在这个方法中实现对手机厂商的角标设置
List getSupportLaunchers()
主要来实现提供出哪些桌面包名是可以支持角标的,因为有的厂商可能有一些桌面是不支持的。
主流厂商方案
其实主要的这些厂商普遍分为3个技术实现方向:
1.采用类似华为的方案,采用contentprovider,使用call接口调用传递app角标数据
2.采用Brocast广播方案,相关角标数据使用广播发送
3、小米方案特殊一些,采用通知相关接口,属于比较另类
下面列出一些主流手机的桌面角标接口实现(因为本人不做app好些年了,不确定下面接口是否已经更新变化)
华为
cpp
public class HuaweiHomeBadger extends Badger {
private final Uri CONTENT_URI = Uri.parse("content://com.huawei.android.launcher.settings/badge/");
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
Bundle localBundle = new Bundle();
localBundle.putString("package", context.getPackageName());
localBundle.putString("class", componentName.getClassName());
localBundle.putInt("badgenumber", badgeCount);
context.getContentResolver().call(CONTENT_URI, "change_badge", null, localBundle);
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList("com.huawei.android.launcher");
}
}
OPPO
cpp
public class OPPOHomeBader extends Badger {
private static final String PROVIDER_CONTENT_URI = "content://com.android.badge/badge";
private static final String INTENT_ACTION = "com.oppo.unsettledevent";
private static final String INTENT_EXTRA_PACKAGENAME = "pakeageName";
private static final String INTENT_EXTRA_BADGE_COUNT = "number";
private static final String INTENT_EXTRA_BADGE_UPGRADENUMBER = "upgradeNumber";
private static final String INTENT_EXTRA_BADGEUPGRADE_COUNT = "app_badge_count";
private static int ROMVERSION = -1;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
if (badgeCount == 0) {
badgeCount = -1;
}
Intent intent = new Intent(INTENT_ACTION);
intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName());
intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount);
intent.putExtra(INTENT_EXTRA_BADGE_UPGRADENUMBER, badgeCount);
if (canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent);
} else {
int version = getSupportVersion();
if (version == 6) {
try {
Bundle extras = new Bundle();
extras.putInt(INTENT_EXTRA_BADGEUPGRADE_COUNT, badgeCount);
context.getContentResolver().call(Uri.parse(PROVIDER_CONTENT_URI), "setAppBadgeCount", null, extras);
} catch (Throwable th) {
throw new ShortcutBadgeException("unable to resolve intent: " + intent.toString());
}
}
}
}
@Override
public List<String> getSupportLaunchers() {
return Collections.singletonList("com.oppo.launcher");
}
private int getSupportVersion() {
int i = ROMVERSION;
if (i >= 0) {
return ROMVERSION;
}
try {
i = ((Integer) executeClassLoad(getClass("com.color.os.ColorBuild"), "getColorOSVERSION", null, null)).intValue();
} catch (Exception e) {
i = 0;
}
if (i == 0) {
try {
String str = getSystemProperty("ro.build.version.opporom");
if (str.startsWith("V1.4")) {
return 3;
}
if (str.startsWith("V2.0")) {
return 4;
}
if (str.startsWith("V2.1")) {
return 5;
}
} catch (Exception ignored) {
}
}
ROMVERSION = i;
return ROMVERSION;
}
private Object executeClassLoad(Class cls, String str, Class[] clsArr, Object[] objArr) {
Object obj = null;
if (!(cls == null || checkObjExists(str))) {
Method method = getMethod(cls, str, clsArr);
if (method != null) {
method.setAccessible(true);
try {
obj = method.invoke(null, objArr);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return obj;
}
private Method getMethod(Class cls, String str, Class[] clsArr) {
Method method = null;
if (cls == null || checkObjExists(str)) {
return method;
}
try {
cls.getMethods();
cls.getDeclaredMethods();
return cls.getDeclaredMethod(str, clsArr);
} catch (Exception e) {
try {
return cls.getMethod(str, clsArr);
} catch (Exception e2) {
return cls.getSuperclass() != null ? getMethod(cls.getSuperclass(), str, clsArr) : method;
}
}
}
private Class getClass(String str) {
Class cls = null;
try {
cls = Class.forName(str);
} catch (ClassNotFoundException ignored) {
}
return cls;
}
private boolean checkObjExists(Object obj) {
return obj == null || obj.toString().equals("") || obj.toString().trim().equals("null");
}
private String getSystemProperty(String propName) {
String line;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
} catch (IOException ex) {
return null;
} finally {
try {
if (input != null) {
input.close();
}
} catch (IOException var2) {
}
}
return line;
}
}
vivo
cpp
public class VivoHomeBadger extends Badger {
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
Intent intent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM");
intent.putExtra("packageName", context.getPackageName());
intent.putExtra("className", componentName.getClassName());
intent.putExtra("notificationNum", badgeCount);
context.sendBroadcast(intent);
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList("com.vivo.launcher");
}
}
中兴
cpp
public class ZTEHomeBadger extends Badger {
private final Uri CONTENT_URI = Uri.parse("content://com.android.launcher3.cornermark.unreadbadge");
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount)
throws ShortcutBadgeException {
Bundle extra = new Bundle();
extra.putInt("app_badge_count", badgeCount);
extra.putString("app_badge_component_name", componentName.flattenToString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
context.getContentResolver().call(CONTENT_URI, "setAppUnreadCount", null, extra);
}
}
@Override
public List<String> getSupportLaunchers() {
return new ArrayList<String>(0);
}
}
联想Zuk
需在设置 -- 通知和状态栏 -- 应用角标管理 中开启应用
cpp
public class ZukHomeBadger extends Badger {
private final Uri CONTENT_URI = Uri.parse("content://com.android.badge/badge");
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
Bundle extra = new Bundle();
extra.putInt("app_badge_count", badgeCount);
context.getContentResolver().call(CONTENT_URI, "setAppBadgeCount", null, extra);
}
@Override
public List<String> getSupportLaunchers() {
return Collections.singletonList("com.zui.launcher");
}
}
Sony
cpp
public class SonyHomeBadger extends Badger {
private static final String INTENT_ACTION = "com.sonyericsson.home.action.UPDATE_BADGE";
private static final String INTENT_EXTRA_PACKAGE_NAME = "com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME";
private static final String INTENT_EXTRA_ACTIVITY_NAME = "com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME";
private static final String INTENT_EXTRA_MESSAGE = "com.sonyericsson.home.intent.extra.badge.MESSAGE";
private static final String INTENT_EXTRA_SHOW_MESSAGE = "com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE";
private static final String PROVIDER_CONTENT_URI = "content://com.sonymobile.home.resourceprovider/badge";
private static final String PROVIDER_COLUMNS_BADGE_COUNT = "badge_count";
private static final String PROVIDER_COLUMNS_PACKAGE_NAME = "package_name";
private static final String PROVIDER_COLUMNS_ACTIVITY_NAME = "activity_name";
private static final String SONY_HOME_PROVIDER_NAME = "com.sonymobile.home.resourceprovider";
private final Uri BADGE_CONTENT_URI = Uri.parse(PROVIDER_CONTENT_URI);
private AsyncQueryHandler mQueryHandler;
@Override
public void executeBadge(Context context, ComponentName componentName,
int badgeCount) throws ShortcutBadgeException {
if (sonyBadgeContentProviderExists(context)) {
executeBadgeByContentProvider(context, componentName, badgeCount);
} else {
executeBadgeByBroadcast(context, componentName, badgeCount);
}
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList("com.sonyericsson.home", "com.sonymobile.home");
}
private static void executeBadgeByBroadcast(Context context, ComponentName componentName, int badgeCount) {
Intent intent = new Intent(INTENT_ACTION);
intent.putExtra(INTENT_EXTRA_PACKAGE_NAME, componentName.getPackageName());
intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName());
intent.putExtra(INTENT_EXTRA_MESSAGE, String.valueOf(badgeCount));
intent.putExtra(INTENT_EXTRA_SHOW_MESSAGE, badgeCount > 0);
context.sendBroadcast(intent);
}
/**
* Send request to Sony badge content provider to set badge in Sony home launcher.
*
* @param context the context to use
* @param componentName the componentName to use
* @param badgeCount the badge count
*/
private void executeBadgeByContentProvider(Context context, ComponentName componentName,
int badgeCount) {
if (badgeCount < 0) {
return;
}
final ContentValues contentValues = createContentValues(badgeCount, componentName);
if (Looper.myLooper() == Looper.getMainLooper()) {
// We're in the main thread. Let's ensure the badge update happens in a background
// thread by using an AsyncQueryHandler and an async update.
if (mQueryHandler == null) {
mQueryHandler = new AsyncQueryHandler(
context.getApplicationContext().getContentResolver()) {
};
}
insertBadgeAsync(contentValues);
} else {
// Already in a background thread. Let's update the badge synchronously. Otherwise,
// if we use the AsyncQueryHandler, this thread may already be dead by the time the
// async execution finishes, which will lead to an IllegalStateException.
insertBadgeSync(context, contentValues);
}
}
/**
* Asynchronously inserts the badge counter.
*
* @param contentValues Content values containing the badge count, package and activity names
*/
private void insertBadgeAsync(final ContentValues contentValues) {
mQueryHandler.startInsert(0, null, BADGE_CONTENT_URI, contentValues);
}
/**
* Synchronously inserts the badge counter.
*
* @param context Caller context
* @param contentValues Content values containing the badge count, package and activity names
*/
private void insertBadgeSync(final Context context, final ContentValues contentValues) {
context.getApplicationContext().getContentResolver()
.insert(BADGE_CONTENT_URI, contentValues);
}
/**
* Creates a ContentValues object to be used in the badge counter update. The package and
* activity names must correspond to an activity that holds an intent filter with action
* "android.intent.action.MAIN" and category android.intent.category.LAUNCHER" in the manifest.
* Also, it is not allowed to publish badges on behalf of another client, so the package and
* activity names must belong to the process from which the insert is made.
* To be able to insert badges, the app must have the PROVIDER_INSERT_BADGE
* permission in the manifest file. In case these conditions are not
* fulfilled, or any content values are missing, there will be an unhandled
* exception on the background thread.
*
* @param badgeCount the badge count
* @param componentName the component name from which package and class name will be extracted
*
*/
private ContentValues createContentValues(final int badgeCount,
final ComponentName componentName) {
final ContentValues contentValues = new ContentValues();
contentValues.put(PROVIDER_COLUMNS_BADGE_COUNT, badgeCount);
contentValues.put(PROVIDER_COLUMNS_PACKAGE_NAME, componentName.getPackageName());
contentValues.put(PROVIDER_COLUMNS_ACTIVITY_NAME, componentName.getClassName());
return contentValues;
}
/**
* Check if the latest Sony badge content provider exists .
*
* @param context the context to use
* @return true if Sony badge content provider exists, otherwise false.
*/
private static boolean sonyBadgeContentProviderExists(Context context) {
boolean exists = false;
ProviderInfo info = context.getPackageManager().resolveContentProvider(SONY_HOME_PROVIDER_NAME, 0);
if (info != null) {
exists = true;
}
return exists;
}
}
三星
cpp
public class SamsungHomeBadger extends Badger {
private static final String CONTENT_URI = "content://com.sec.badge/apps?notify=true";
private static final String[] CONTENT_PROJECTION = new String[]{"_id", "class"};
private DefaultBadger defaultBadger;
public SamsungHomeBadger() {
if (Build.VERSION.SDK_INT >= 21) {
defaultBadger = new DefaultBadger();
}
}
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
if (defaultBadger != null && defaultBadger.isSupported(context)) {
defaultBadger.executeBadge(context, componentName, badgeCount);
} else {
Uri mUri = Uri.parse(CONTENT_URI);
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = null;
try {
cursor = contentResolver.query(mUri, CONTENT_PROJECTION, "package=?", new String[]{componentName.getPackageName()}, null);
if (cursor != null) {
String entryActivityName = componentName.getClassName();
boolean entryActivityExist = false;
while (cursor.moveToNext()) {
int id = cursor.getInt(0);
ContentValues contentValues = getContentValues(componentName, badgeCount, false);
contentResolver.update(mUri, contentValues, "_id=?", new String[]{String.valueOf(id)});
if (entryActivityName.equals(cursor.getString(cursor.getColumnIndex("class")))) {
entryActivityExist = true;
}
}
if (!entryActivityExist) {
ContentValues contentValues = getContentValues(componentName, badgeCount, true);
contentResolver.insert(mUri, contentValues);
}
}
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}
}
private ContentValues getContentValues(ComponentName componentName, int badgeCount, boolean isInsert) {
ContentValues contentValues = new ContentValues();
if (isInsert) {
contentValues.put("package", componentName.getPackageName());
contentValues.put("class", componentName.getClassName());
}
contentValues.put("badgecount", badgeCount);
return contentValues;
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList(
"com.sec.android.app.launcher",
"com.sec.android.app.twlauncher"
);
}
}
小米
cpp
ublic class XiaomiHomeBadger extends Badger {
public static final String INTENT_ACTION = "android.intent.action.APPLICATION_MESSAGE_UPDATE";
public static final String EXTRA_UPDATE_APP_COMPONENT_NAME = "android.intent.extra.update_application_component_name";
public static final String EXTRA_UPDATE_APP_MSG_TEXT = "android.intent.extra.update_application_message_text";
private ResolveInfo resolveInfo;
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
try {
Class miuiNotificationClass = Class.forName("android.app.MiuiNotification");
Object miuiNotification = miuiNotificationClass.newInstance();
Field field = miuiNotification.getClass().getDeclaredField("messageCount");
field.setAccessible(true);
try {
field.set(miuiNotification, String.valueOf(badgeCount == 0 ? "" : badgeCount));
} catch (Exception e) {
field.set(miuiNotification, badgeCount);
}
} catch (Exception e) {
Intent localIntent = new Intent(
INTENT_ACTION);
localIntent.putExtra(EXTRA_UPDATE_APP_COMPONENT_NAME, componentName.getPackageName() + "/" + componentName.getClassName());
localIntent.putExtra(EXTRA_UPDATE_APP_MSG_TEXT, String.valueOf(badgeCount == 0 ? "" : badgeCount));
if (canResolveBroadcast(context, localIntent)) {
context.sendBroadcast(localIntent);
}
}
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
tryNewMiuiBadge(context, badgeCount);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void tryNewMiuiBadge(Context context, int badgeCount) throws ShortcutBadgeException {
if (resolveInfo == null) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
resolveInfo = context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
}
if (resolveInfo != null) {
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle("")
.setContentText("")
.setSmallIcon(resolveInfo.getIconResource());
Notification notification = builder.build();
try {
Field field = notification.getClass().getDeclaredField("extraNotification");
Object extraNotification = field.get(notification);
Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
method.invoke(extraNotification, badgeCount);
mNotificationManager.notify(0, notification);
} catch (Exception e) {
throw new ShortcutBadgeException("not able to set badge", e);
}
}
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList(
"com.miui.miuilite",
"com.miui.home",
"com.miui.miuihome",
"com.miui.miuihome2",
"com.miui.mihome",
"com.miui.mihome2",
"com.i.miui.launcher"
);
}
}
华硕
cpp
public class AsusHomeBadger extends Badger {
private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE";
private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count";
private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name";
private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name";
@Override
public void executeBadge(Context context, ComponentName componentName, int badgeCount) throws ShortcutBadgeException {
Intent intent = new Intent(INTENT_ACTION);
intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount);
intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName());
intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName());
intent.putExtra("badge_vip_count", 0);
if (canResolveBroadcast(context, intent)) {
context.sendBroadcast(intent);
} else {
throw new ShortcutBadgeException("unable to resolve intent: " + intent.toString());
}
}
@Override
public List<String> getSupportLaunchers() {
return Arrays.asList("com.asus.launcher");
}
}
大家也可以直接去看看开源
https://gitee.com/yufeilong/badger-helper-master
更多framework实战开发干货,请关注下面"千里马学框架"