去除Android中的角标

为了MarkDown输入的方便, 文中的标点都是英文标点, 带来不便敬请谅解.

角标本来是iOS上的东东, 在原生Android中是没有的. 但是现在各大手机厂商几乎都自己实现了这个功能, 这让Android的碎片化更加严重. 在我看来, 安卓的通知栏可以很好地通知用户各种消息了, 非得学iOS将角标加入到系统来真是画蛇添足! 而且, 我相信很多用户对这些角标的显示十分地厌烦, 不得不挨个点开有角标的App来消去角标(包括我自己). 能不能一键把所有讨厌的角标消除掉呢? 自己来试试吧.

本文中的代码是使用Kotlin写的,如果难以理解请先学习一下Kotlin的简单语法

三星

三星里的角标是通过一个内置的应用BadgeProvider来实现的,可以在应用管理器中找到.

理论上来讲强制停止这个App就可以让角标不再显示, 但是人家是三星的内置应用, 你强制停止了待会又会自动启动起来,就是这么流氓. 如果root了将其卸载了估计能有用, 但是我没试验过.

既然强制停止这个App没有用, 那就想想别的办法吧. 目前有两种方法:

通过广播

发送一个Action为”android.intent.action.BADGE_COUNT_UPDATE”的广播,并且将包名/LAUNCHER Activity和角标的数目一起发送出去. 三星的系统中一个BroadcastReceiver会接收这个广播并且将角标加到对应的App上.代码如下:

1
2
3
4
5
6
7
8
9
10
fun sendToSamSumg(context: Context, packageName: String, className: String, number: Int) {
Log.d(TAG, "packageName:$packageName")
Log.d(TAG, "className:$className")
Log.d(TAG, "number:$number")
val badgeIntent = Intent(Actions.ACTION_SUMSUNG)
badgeIntent.putExtra("badge_count", number)
badgeIntent.putExtra("badge_count_package_name", packageName)
badgeIntent.putExtra("badge_count_class_name", className)
context.sendBroadcast(badgeIntent)
}

这样的话,如果我们只要在Manifest文件里注册一个接收这个Action的BroadcastReceiver, 理论上来讲任何时候都可以收到对应的广播,然后再发一条badge_count为0的广播就应该可以自动将角标给消去了.但是在我的s6手机上实验发现过一段时间后进程会被杀死再也收不到广播了,可能是三星对系统做了什么更改.

既然自动消除的路走不通了,那就来手动消除吧. 我们可以遍历系统中安装的所有App, 得到其包名和启动Activity, 并依次对其发送badge_count为0的广播.

1
2
3
4
5
6
7
8
9
10
11
fun sendToSamSungAllBroadCast(context: Context, number: Int, test: Boolean) {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER);
val list = context.packageManager.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES);
for (resolveInfo in list) {
val activityName = resolveInfo.activityInfo.name
val packageName = resolveInfo.activityInfo.applicationInfo.packageName
sendToSamSumg(context, packageName, activityName, number, test)
}

}

实验了一下可以达到目的,但是效率很差, 一方面手机中装的App一般会很多,另一方面广播的效率比较差,所以需要好几秒才能完成整个过程.那有没有更好的办法了呢? 回答是Yes! 看下面的方法:

通过ContentProvider

三星系统中的角标数目都是存储在一个Uri为”content://com.sec.badge/apps”的ContentProvider里的, 这个ContentProvider 包含以下的几个属性:

  • _id
  • package
  • class
  • badgecount
  • icon
  • extraData

通过访问和修改这个ContentProvider里面的badgecount属性,就可以获取或者更改对应App的角标数目.
但是首先需要申请读写的权限:

1
2
<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />

这个ContentProvider只会存储需要显示角标的App,当一个App显示过一次角标后就会被存储起来, 并不会包含其它的App, 所以需要遍历的App数目相对较少. 我们需要做的就是遍历ContentProvider里面所有的App并且将其badgecount置为0就可以消除掉所有的角标.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun sendToSamSungAllContentResolver(context: Context, number: Int) {
val uri = Uri.parse("content://com.sec.badge/apps")
val contentResolver = context.contentResolver
val c: Cursor? = contentResolver.query(uri, null, null, null, null) ?: return
try {
var where = "package in ("
while (c!!.moveToNext()) {
val pkg = c.getString(1)
val clazz = c.getString(2)
val badgeCount = c.getInt(3)
Log.d(TAG, "package: $pkg, class: $clazz, badgeCount: $badgeCount")
where += "'$pkg',"
}
where += "' ')"
Log.d(TAG, "update $where to number: $number")
val cv = ContentValues()
cv.put("badgeCount", number)

contentResolver.update(Uri.parse("content://com.sec.badge/apps"), cv, where, null)
} catch (e: Exception) {
Log.e(TAG, e.toString())
c!!.close()
}
}

相比于上面的方法,这种方法去除角标效率极大地得到了提高,瞬间就可以清除掉所有的角标. 为了方便,我们创建一个Widget放在桌面上,这样一点击就可以把所有的角标都清除掉.
效果图如下:

Sony

索尼需要显示一个角标需要发送一个Action为”com.sonyericsson.home.action.UPDATE_BADGE”的广播,同样也需要申请权限

1
2
<uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
<uses-permission android:name="com.sonyericsson.home.permission.RECEIVE_BADGE" />

发送广播的Intent包含4种数据,分别是包名/启动Activity/是否显示角标/角标数目.需要注意的是角标数目是字符串类型的而不是Int类型.

1
2
3
4
5
6
7
8
9
fun sendToSony(context: Context, packageName: String, className: String, number: Int) {
Log.d(TAG, "sendToSony $packageName")
val intent = Intent(Actions.ACTION_SONY);
intent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", className);
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", number != 0);
intent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", number.toString());
intent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", packageName);
context.sendBroadcast(intent);
}

通过上面的方法我们就可以让一个App的启动图标上显示角标.如果我们发送的number为0,当然就可以将角标给消去.所以要达到一键消除所有角标的效果就必须得遍历所有的App,这点不像三星可以直接操作ContentProvider.

1
2
3
4
5
6
7
8
9
10
fun sendToSonyAll(context: Context, number: Int) {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER);
val list = context.packageManager.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES);
for (resolveInfo in list) {
val activityName = resolveInfo.activityInfo.name
val packageName = resolveInfo.activityInfo.applicationInfo.packageName
sendToSony(context, packageName, activityName, number)
}
}

Htc

Htc同索尼一样,也是发送广播,Action为”com.htc.launcher.action.SET_NOTIFICATION”,同样也需要申请权限:

1
2
<uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.htc.launcher.permission.UPDATE_SHORTCUT" />

要以消除角标的话也需要遍历所有的App.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 fun sendToHtc(context: Context, packageName: String, className: String, number: Int) {
Log.d(TAG, "sendToHtc $packageName,number:$number")
val setNotificationIntent = Intent(Actions.ACTION_HTC);
val localComponentName = ComponentName(packageName, className);
setNotificationIntent.putExtra("com.htc.launcher.extra.COMPONENT", localComponentName.flattenToShortString());
setNotificationIntent.putExtra("com.htc.launcher.extra.COUNT", number);
context.sendBroadcast(setNotificationIntent);
}

fun sendToHtcAll(context: Context, number: Int) {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER);
val list = context.packageManager.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES);
for (resolveInfo in list) {
val packageName = resolveInfo.activityInfo.applicationInfo.packageName
val activityName = resolveInfo.activityInfo.name
sendToHtc(context, packageName, activityName, number)
}
}

本文的代码见GitHub

To be continued…