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

Android智能电视应用中准确获取前台应用包名的问题排查与解决方案需求

Android智能电视应用中准确获取前台应用包名的问题排查与解决方案需求

嘿,我完全懂你遇到的这个糟心事——作为设备所有者的电视监控应用,开机阶段误把后台运行的Settings进程当成前台,直接弹出PIN框,这体验确实很差。咱们先把你的问题背景、现有代码理清楚,再看看怎么优化获取前台应用的逻辑。


问题背景与需求

我正在开发一款智能电视Android应用,作为设备所有者,需要监控前台应用:如果前台是TV_SETTINGS_PACKAGES数组里的应用,就弹出PIN码验证。目前电视完全启动后功能正常,但开机过程中,Settings应用在后台做初始化操作时,我的服务在BOOT_COMPLETED触发后立刻启动,误判Settings为前台,直接弹出了PIN框。
核心需求:需要一种能准确判断**真正处于前台(可见)**的应用的方法。


现有核心代码与配置

1. SettingsMonitorService核心逻辑

class SettingsMonitorService : Service() { 
    // 目标监控包列表
    private val TV_SETTINGS_PACKAGES = arrayOf( 
        "com.android.settings",
        "com.android.tv.settings", // Android TV
        "com.tcl.tv.settings", // TCL
        "com.tcl.guard", // TCL Guard
        "com.sony.dtv.settings", // Sony Bravia
        "com.samsung.tv.settings", // Samsung
        "com.lge.tv.settings", // LG
        "com.google.android.tungsten.setupwraith", // Google TV
        "com.philips.tv.settings", // Philips
        "com.hisense.tv.settings", // Hisense
        "com.realtek.tv.settings", // Realtek-based TVs
        "com.amazon.tv.settings", // Fire TV
        "com.google.android.youtube.tv", // Youtube
        "com.netflix.ninja" // Netflix
    )

    private fun getForegroundPackageName(): String? {
        val usm = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
        val stats = usm.queryUsageStats(
            UsageStatsManager.INTERVAL_DAILY,
            System.currentTimeMillis() - 10000,
            System.currentTimeMillis()
        )
        return stats?.maxByOrNull { it.lastTimeUsed }?.packageName
    }

    private fun isSettingsInForeground(): Boolean {
        val recentPackage = getForegroundPackageName()
        Log.d(TAG, "Most recently used app: ${recentPackage}")
        return recentPackage != null && TV_SETTINGS_PACKAGES.contains(recentPackage)
    }

    // 其他服务代码...
}

2. 已声明的Manifest权限

<!-- Required permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!-- for legacy systems -->
<uses-permission android:name="android.permission.GET_TASKS" />

3. Service的Manifest注册

<service android:name=".SettingsMonitorService"
    android:enabled="true"
    android:exported="true"
    android:foregroundServiceType="dataSync" />

问题根源分析

你当前用UsageStatsManager最近使用的应用,这个逻辑的致命问题是:最近被调用的应用不一定是当前可见的前台应用。开机阶段,Settings会在后台执行初始化操作(比如加载系统配置、同步数据),它的lastTimeUsed会被更新,但此时它并没有真正显示在屏幕上,所以被误判成前台。


针对性解决方案

方案1:用ActivityManager.getRunningAppProcesses()兼容旧系统

虽然这个方法被标记为deprecated,但在大多数智能电视的定制系统上依然可用,能准确识别真正的前台进程:

private fun getForegroundPackageName(): String? {
    val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val runningProcesses = am.getRunningAppProcesses()
    runningProcesses?.forEach { process ->
        // 只判断真正处于前台可见的进程
        if (process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
            return process.processName
        }
    }
    return null
}

注意:你已经声明了GET_TASKS权限,这个方法适合搭配版本判断,给旧系统做兼容。

方案2:用AccessibilityService(高版本最可靠方案)

对于Android 8.0+的电视系统,AccessibilityService可以监听窗口状态变化,精准捕获真正显示在屏幕上的应用包名,这是最稳定的方案:

  1. 创建窗口监听的AccessibilityService:
class WindowMonitorAccessibilityService : AccessibilityService() {
    companion object {
        // 全局保存当前前台包名
        var currentForegroundPackage: String? = null
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // 监听窗口切换事件
        if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            currentForegroundPackage = event.packageName?.toString()
        }
    }

    override fun onInterrupt() {}
}
  1. 在Manifest中注册服务:
<service
    android:name=".WindowMonitorAccessibilityService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>
  1. 创建res/xml/accessibility_service_config.xml配置文件:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description" />
  1. 在你的SettingsMonitorService中,直接通过WindowMonitorAccessibilityService.currentForegroundPackage获取真实前台包名即可。

优势:作为设备所有者,你可以自动授予Accessibility权限,无需用户手动操作,完全避免后台进程误判。

方案3:延迟启动监控(临时快速修复)

如果不想大改代码,可以在BOOT_COMPLETED触发后延迟一段时间再启动监控,给电视足够时间完成开机初始化:

// 在BOOT_COMPLETED的广播接收器中
override fun onReceive(context: Context, intent: Intent) {
    if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
        // 延迟30秒启动服务,适配不同品牌电视的开机速度
        Handler(Looper.getMainLooper()).postDelayed({
            context.startService(Intent(context, SettingsMonitorService::class.java))
        }, 30000)
    }
}

缺点:延迟时间不好统一适配,不同电视开机速度差异大,只能作为临时过渡方案。


总结

优先推荐AccessibilityService方案,它能精准识别真正的前台可见应用,适配绝大多数智能电视系统;如果需要兼容旧系统,可以搭配getRunningAppProcesses()做版本判断;延迟启动仅适合临时应急,不推荐长期使用。

内容来源于stack exchange

火山引擎 最新活动