You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Kotlin中筛选具备通知权限的第三方应用失败,权限检查逻辑异常求助

Kotlin中筛选具备通知权限的第三方应用失败,权限检查逻辑异常求助

看起来你在筛选有通知权限的应用时踩了几个版本适配和API调用的坑,我来帮你梳理下问题所在和修正方案:

核心问题分析

你当前的权限检查逻辑有两个关键错误:

  1. 传错了UID参数:在调用AppOps相关方法时,你用了android.os.Process.myUid()(当前APP的UID),但我们要检查的是目标应用的UID——AppOps是基于应用唯一UID记录权限状态的,用自己的UID查其他应用的权限,结果必然错误,这就是你拿到MODE_IGNORED(1)而非预期MODE_ALLOWED(0)的根本原因。
  2. 版本适配不完整:不同Android版本检查其他应用通知权限的官方API差异很大,尤其是Android 13(API 33)之后,官方提供了更直接的查询方式,无需再绕AppOps。

修正后的完整代码

下面是修复后的NotificationPermissionHelper类,包含了正确的权限检查逻辑和版本适配:

class NotificationPermissionHelper(private val context: Context) {

    fun getAllApps(): List<ApplicationInfo> {
        val packageManager = context.packageManager
        val mainIntent = Intent(Intent.ACTION_MAIN, null)
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER)
        val resolveInfoList: List<ResolveInfo> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            packageManager.queryIntentActivities(
                mainIntent,
                PackageManager.ResolveInfoFlags.of(0L)
            )
        } else {
            packageManager.queryIntentActivities(mainIntent, 0)
        }
        return resolveInfoList.mapNotNull { resolveInfo ->
            try {
                packageManager.getApplicationInfo(resolveInfo.activityInfo.packageName, 0)
            } catch (e: PackageManager.NameNotFoundException) {
                // 应用已卸载的情况,返回null过滤掉
                null
            }
        }
    }

    fun getAppsWithNotificationPermission(): List<ApplicationInfo> {
        val allApps = getAllApps()
        return allApps.filter { hasNotificationPermission(it) }
    }

    private fun hasNotificationPermission(appInfo: ApplicationInfo): Boolean {
        return when {
            // Android 13+ 用官方专属API,直接且准确
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
                val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                notificationManager.areNotificationsEnabledForPackage(appInfo.packageName, android.os.Process.myUserHandle())
            }
            // Android 8.0到12,用AppOps常量替代硬编码字符串
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
                val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
                val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    appOpsManager.unsafeCheckOpNoThrow(
                        AppOpsManager.OP_POST_NOTIFICATION,
                        appInfo.uid, // 关键:使用目标应用的UID
                        appInfo.packageName
                    )
                } else {
                    @Suppress("DEPRECATION")
                    appOpsManager.checkOpNoThrow(
                        AppOpsManager.OP_POST_NOTIFICATION,
                        appInfo.uid,
                        appInfo.packageName
                    )
                }
                mode == AppOpsManager.MODE_ALLOWED
            }
            // Android 8.0以下,fallback到字符串类型的Op
            else -> {
                val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
                @Suppress("DEPRECATION")
                val mode = appOpsManager.checkOpNoThrow(
                    "android:post_notification",
                    appInfo.uid,
                    appInfo.packageName
                )
                mode == AppOpsManager.MODE_ALLOWED
            }
        }
    }
}

关键修改说明

  1. 替换UID为目标应用的UID:把Process.myUid()改成appInfo.uid,确保我们查询的是目标应用的权限状态。
  2. 分版本适配API
    • Android 13+直接用NotificationManager.areNotificationsEnabledForPackage,这是官方推荐的方式,避免AppOps的复杂逻辑。
    • Android 8.0+用AppOps的常量OP_POST_NOTIFICATION替代硬编码字符串,减少出错概率。
  3. 简化逻辑层级:把权限检查和应用信息直接绑定,避免额外的packageName传参,减少出错可能。

额外注意事项

  • 确保你的APP在Manifest中声明了<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />权限,并且在Android 11+设备上已经请求并获得了该权限,否则可能无法获取完整的应用列表。
  • 可以在权限检查方法中添加异常捕获,比如处理目标应用UID无效的极端情况,但一般ApplicationInfo中的uid都是有效的。

内容来源于stack exchange

火山引擎 最新活动