需要先通过getTasks获取系统任务列表,系统应用才有权限,否则需要使用 ActivityManager.getAppTasks()
val recentTasks = ActivityTaskManager.getInstance().getRecentTasks(Int.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, Process.myUserHandle().getIdentifier())
接着从TaskInfo中读取应用信息,appName,appIcon,最近快照,需要从task的baseIntent中构建intent对象
val intent = Intent(task.baseIntent)
intent.component = task.baseActivity
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") }
如果resolveActivity方法不为空,可以直接获取 resolveInfo.activityInfo,然后拿到应用标题跟图标
val title = activityInfo.loadLabel(packageManager).toString()
val icon = activityInfo.loadIcon(packageManager).toBitmap()
任务快照则需要通过 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) } } } }
缩放原图,减少加载内存跟时长
如果需要回到首页
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") }
加载任务列表时,先加载任务,然后异步加载图片,避免最近任务延迟加载
private fun loadData() { Log.d(TAG, "loadData") mJob?.cancel() mJob = lifecycleScope.launch { taskAdapter.setData(getRecentTasks()) withContext(Dispatchers.IO) { setTaskThumbnail() } taskAdapter.notifyDataSetChanged() } }
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 }
打开任务应用
而进入任务应用,需要使用 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)
该方法是从最近任务恢复复用现有实例,会触发 onNewIntent()
跟startActivity的不同是直接start可能导致重复创建实例,并且二者系统处理逻辑也不一样,系统默认行为存在差异
多屏处理
if (displayId != null && task.displayId != displayId) continue
如果有多屏需要考虑displayId,因为最近任务会加载所有屏幕打开的最近任务,而在当前屏幕下只需要加载当前屏幕下的最近任务
白名单处理
val hasTask = filterTask.contains(pkg) if (hasTask) continue
如果需要屏蔽某个应用的任务,需要维护白名单,在最近任务列表中单独处理
结束任务
使用 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) } }
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 }
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) }
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>
如果是全屏界面隐藏状态栏
window.isNavigationBarContrastEnforced = false WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.getInsetsController(window, window.decorView).apply { systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE hide(WindowInsetsCompat.Type.systemBars()) }
浙公网安备 33010602011771号