惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog RSS Feed
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
雷峰网
雷峰网
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
P
Proofpoint News Feed
The Cloudflare Blog
D
Docker
大猫的无限游戏
大猫的无限游戏

博客园 - 翻滚的咸鱼

Ops权限行为记录源码解析 TaskView方案框架原理 视频解码器问题 常用命令 扫光动效 引导记录 性能优化 View共享动效 常用脚本文件 Launcher 卡片框架多模块集成 Launcher 桌面源码笔记二 Launcher 桌面源码笔记一 氛围灯动态屏保取色方案二 氛围灯动态屏保取色方案一 anr问题 TV RecyclerView 焦点处理笔记 多类型适配器 多屏下字体自动取色 阻尼拉伸方案 图片区域点击处理 RecyclerView设置默认焦点跟多页面焦点抢占 TV RecyclerView 滑动后操作保持落焦在左侧第一个View Fragment翻盖动效 当个View下,使用Drawable入场退场动画
自定义最近任务管理器
翻滚的咸鱼 · 2025-10-15 · via 博客园 - 翻滚的咸鱼

需要先通过getTasks获取系统任务列表,系统应用才有权限,否则需要使用 ActivityManager.getAppTasks()

val recentTasks = ActivityTaskManager.getInstance().getRecentTasks(Int.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, Process.myUserHandle().getIdentifier())

View Code

接着从TaskInfo中读取应用信息,appName,appIcon,最近快照,需要从task的baseIntent中构建intent对象

val intent = Intent(task.baseIntent)
intent.component = task.baseActivity

View Code

TaskInfo中有四个属性字段

topActivity:当前任务栈顶

origActivity:最初启动的任务栈

realActivity:实际处理的任务栈

baseActivity:根任务栈

获取对应的应用名称跟图标,需要借助 ActivityInfo,不过在此之前需要需要先判断 resolveActivity()

若系统中没有能处理该 Intent 的 Activity(如应用已被卸载或 Intent 配置错误),会返回 null。此时直接使用会导致 NullPointerException

在读取最近任务时,其他应用的 Activity 可能因权限限制、组件禁用或版本差异无法解析,提前判空可避免崩溃

val resolveInfo = packageManager.resolveActivity(intent, 0)
if (resolveInfo == null) {
    log("getRecentTasks resolveInfo is null $resolveInfo")
}

View Code

如果resolveActivity方法不为空,可以直接获取 resolveInfo.activityInfo,然后拿到应用标题跟图标

val title = activityInfo.loadLabel(packageManager).toString()
val icon = activityInfo.loadIcon(packageManager).toBitmap()

View Code

任务快照则需要通过 ActivityManagerWrapper.getInstance().getTaskThumbnail(task.taskId, false)?.thumbnail 获取,耗时操作,需要放子线程

private suspend fun setTaskThumbnail() {
        Log.d(TAG, "setTaskThumbnail")
        for (app in taskAdapter.getData()) {
            currentCoroutineContext().ensureActive()
            ActivityManagerWrapper.getInstance()
                .getTaskThumbnail(app.taskId, false)?.thumbnail?.let {
                    val w = it.width
                    val h = it.height
                    Log.d(TAG, "${app.title} pkg ${app.packageName} thumbnail width $w height $h")
                    if (w > 0 && h > 0) {
                        app.thumbnailBmp = it.scale(w / 3, h / 3, false)
                    }
                }
        }
    }

View Code

缩放原图,减少加载内存跟时长

如果需要回到首页

private fun returnToHome() {
        Log.d(TAG, "returnToHome")
        val intent = Intent(Intent.ACTION_MAIN)
            .addCategory(Intent.CATEGORY_HOME)
            .setPackage(requireContext().packageName)
        requireContext().startActivity(intent)
        finish("returnToHome")
    }

View Code

加载任务列表时,先加载任务,然后异步加载图片,避免最近任务延迟加载

    private fun loadData() {
        Log.d(TAG, "loadData")
        mJob?.cancel()
        mJob = lifecycleScope.launch {
            taskAdapter.setData(getRecentTasks())
            withContext(Dispatchers.IO) {
                setTaskThumbnail()
            }
            taskAdapter.notifyDataSetChanged()
        }
    }

View Code

private fun getRecentTasks(context: Context, maxLength: Int = 30): Collection<MyAppInfo> {
        list.clear()
        val recentTasks = ActivityTaskManager.getInstance()
            .getRecentTasks(
                Int.MAX_VALUE,
                ActivityManager.RECENT_IGNORE_UNAVAILABLE,
                Process.myUserHandle().getIdentifier()
            )
        log("getRecentTasks size ${recentTasks.size}")
        val packageManager = context.packageManager
        if (packageManager == null) {
            log("getRecentTasks packageManager is null")
            return list
        }
        for (task in recentTasks) {
            if (list.size >= maxLength) {
                log("getRecentTasks break")
                break
            }
            val intent = Intent(task.baseIntent)
            if (task.baseActivity != null) {
                intent.component = task.baseActivity
            }
            // 设置intent的启动方式为 创建新task()【并不一定会创建】
            intent.flags = (intent.flags and Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED.inv()
                    or Intent.FLAG_ACTIVITY_NEW_TASK)
            val resolveInfo = packageManager.resolveActivity(intent, 0)
            if (resolveInfo == null) {
                log("getRecentTasks resolveInfo is null $resolveInfo")
                return list
            }
            val myAppInfo = MyAppInfo()
            myAppInfo.taskId = task.taskId
            try {
                val activityInfo = resolveInfo.activityInfo
                val pkg = activityInfo.packageName
                val title = activityInfo.loadLabel(packageManager).toString()
                myAppInfo.packageName = pkg
                myAppInfo.displayId = task.displayId
                log("getRecentTasks resolveInfo=$resolveInfo,title=$title,taskId=${task.taskId},displayId=${task.displayId}")
                val hasTask = filterTask.contains(pkg)
                if (hasTask) continue
                if (displayId != null && task.displayId != displayId) continue
                myAppInfo.title = title
                myAppInfo.appName = title
                myAppInfo.appLogo = activityInfo.loadIcon(packageManager).toBitmap()
            } catch (e: Exception) {
                Log.e(TAG, "getRecentTasks ", e)
            }
            list.add(myAppInfo)
            log("getRecentTasks add task ${myAppInfo.title},pkg=${myAppInfo.packageName}")
        }
        return list
    }

View Code

打开任务应用

而进入任务应用,需要使用 ActivityManagerWrapper.getInstance().startActivityFromRecents()

val options = ActivityOptions.makeBasic()
options.launchDisplayId = displayId
val bundle = options.toBundle()
bundle.putInt("android.activity.windowingMode", 0)
ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, options)

View Code

该方法是从最近任务恢复复用现有实例,会触发 onNewIntent()

跟startActivity的不同是直接start可能导致重复创建实例,并且二者系统处理逻辑也不一样,系统默认行为存在差异

多屏处理

if (displayId != null && task.displayId != displayId) continue

View Code

如果有多屏需要考虑displayId,因为最近任务会加载所有屏幕打开的最近任务,而在当前屏幕下只需要加载当前屏幕下的最近任务

白名单处理

val hasTask = filterTask.contains(pkg)
if (hasTask) continue

View Code

如果需要屏蔽某个应用的任务,需要维护白名单,在最近任务列表中单独处理

结束任务

使用 ActivityManager.forceStopPackage(pkgName) 强制停止应用,包括后台服务、定时任务等

但是有些场景可能需要应用自己处理结束,需要保留服务,这种情况下需要特殊处理

private fun stopProcessIfExist(am: ActivityManager, pkgName: String, taskId: Int? = null) {
        log("stopProcessIfExist taskId:$taskId,pkg:$pkgName")
        taskId?.let {
            ActivityManagerWrapper.getInstance().removeTask(it)
        }
        if (Constant.PACKAGE_TEST == pkgName) {
            am.runningAppProcesses?.let { processList ->
                log("stopProcessIfExist processList size ${processList.size}")
                for (i in processList.indices) {
                    val info = processList[i]
                    if (info != null && pkgName == info.processName) {
                        Log.w(TAG, "processName=${info.processName} pid=${info.pid}")
                        if (Constant.PACKAGE_TEST == info.processName) {
                            Process.killProcess(info.pid)
                        }
                    }
                }
            }
        } else if (Constant.PACKAGE_TEST_SERVICE == pkgName) {
            //do nothing
        } else {
            log("force-stop pkg:$pkgName")
            am.forceStopPackage(pkgName)
        }
    }

View Code

Process.killProcess(info.pid) 仅终止目标进程,所以需要配合ActivityManagerWrapper.getInstance().removeTask(it)一起使用

而结束任务杀进程同样是耗时操作,根据情况需要放到子线程处理

内存信息

    private fun getMemoryInfo(): ActivityManager.MemoryInfo? {
        try {
            val am =
                Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
            val mi = ActivityManager.MemoryInfo()
            am?.getMemoryInfo(mi)
            return mi
        } catch (e: Exception) {
            Log.e(TAG, "loadMemory :$e")
        }
        return null
    }

View Code

    fun loadMemory(): Pair<String, Double> {
        val info = getMemoryInfo()
        val totalMem = info?.totalMem ?: 0
        val availMem = info?.availMem ?: 0
        log("loadMemory totalMem $totalMem avail $availMem")
        val total: Double = if (totalMem == 0L) 16.0 else {
            totalMem / (1024 * 1024 * 1024.0)
        }
        val avail: Double = if (availMem == 0L) 0.0 else availMem / (1024 * 1024 * 1024.0)
        log("loadMemory total $total avail $avail")
        val using = Utils.getApp().resources.getString(R.string.memory_using)
        val memoryText = "$using ${df.format(total - avail)} / ${df.format(total)}GB"
        val progressNum = (total - avail) / total
        val currentProgress = progressNum * 100
        log("loadMemory progressNum $progressNum currentProgress $currentProgress")
        return Pair(memoryText, currentProgress)
    }

View Code

xml配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.REORDER_TASKS" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-sdk   tools:overrideLibrary="com.android.systemui.shared"/>
    <application>
        <activity
            android:name=".RecentTaskActivity"
            android:launchMode="singleTask"
            android:theme="@style/Theme.RecentTask"
            android:taskAffinity=".recents"
            android:process=".recents"
            android:configChanges="mcc|mnc|locale|layoutDirection|uiMode|touchscreen|keyboard|keyboardHidden|screenLayout|smallestScreenSize|fontScale|navigation"
            android:exported="true" >
            <intent-filter>
                <action android:name="com.zeekr.platform.service.intent.action.RECENT_TASK_ACTIVITY"/>
            </intent-filter>
            <meta-data
                android:name="distractionOptimized"
                android:value="true" />
        </activity>
    </application>

</manifest>

View Code

如果是全屏界面隐藏状态栏

window.isNavigationBarContrastEnforced = false
        WindowCompat.setDecorFitsSystemWindows(window, false)
        WindowCompat.getInsetsController(window, window.decorView).apply {
            systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
            hide(WindowInsetsCompat.Type.systemBars())
        }

View Code