需要先通过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

 

posted on 2025-10-15 10:36  翻滚的咸鱼  阅读(29)  评论(0)    收藏  举报