找回密码
 立即注册
首页 业界区 安全 自定义最近任务管理器

自定义最近任务管理器

粒浊 4 小时前
需要先通过getTasks获取系统任务列表,系统应用才有权限,否则需要使用 ActivityManager.getAppTasks()‌
1.gif
2.gif
  1. val recentTasks = ActivityTaskManager.getInstance().getRecentTasks(Int.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, Process.myUserHandle().getIdentifier())
复制代码
View Code接着从TaskInfo中读取应用信息,appName,appIcon,最近快照,需要从task的baseIntent中构建intent对象
3.gif
4.gif
  1. val intent = Intent(task.baseIntent)
  2. intent.component = task.baseActivity
复制代码
View CodeTaskInfo中有四个属性字段
topActivity:当前任务栈顶
origActivity:最初启动的任务栈
realActivity:实际处理的任务栈
baseActivity:根任务栈
获取对应的应用名称跟图标,需要借助 ActivityInfo,不过在此之前需要需要先判断 resolveActivity()
若系统中没有能处理该 Intent 的 Activity(如应用已被卸载或 Intent 配置错误),会返回 null。此时直接使用会导致 NullPointerException
在读取最近任务时,其他应用的 Activity 可能因权限限制、组件禁用或版本差异无法解析,提前判空可避免崩溃
5.gif
6.gif
  1. val resolveInfo = packageManager.resolveActivity(intent, 0)
  2. if (resolveInfo == null) {
  3.     log("getRecentTasks resolveInfo is null $resolveInfo")
  4. }
复制代码
View Code如果resolveActivity方法不为空,可以直接获取 resolveInfo.activityInfo,然后拿到应用标题跟图标
7.gif
8.gif
  1. val title = activityInfo.loadLabel(packageManager).toString()
  2. val icon = activityInfo.loadIcon(packageManager).toBitmap()
复制代码
View Code任务快照则需要通过 ActivityManagerWrapper.getInstance().getTaskThumbnail(task.taskId, false)?.thumbnail 获取,耗时操作,需要放子线程
9.gif
10.gif
  1. private suspend fun setTaskThumbnail() {
  2.         Log.d(TAG, "setTaskThumbnail")
  3.         for (app in taskAdapter.getData()) {
  4.             currentCoroutineContext().ensureActive()
  5.             ActivityManagerWrapper.getInstance()
  6.                 .getTaskThumbnail(app.taskId, false)?.thumbnail?.let {
  7.                     val w = it.width
  8.                     val h = it.height
  9.                     Log.d(TAG, "${app.title} pkg ${app.packageName} thumbnail width $w height $h")
  10.                     if (w > 0 && h > 0) {
  11.                         app.thumbnailBmp = it.scale(w / 3, h / 3, false)
  12.                     }
  13.                 }
  14.         }
  15.     }
复制代码
View Code缩放原图,减少加载内存跟时长
如果需要回到首页
11.gif
12.gif
  1. private fun returnToHome() {
  2.         Log.d(TAG, "returnToHome")
  3.         val intent = Intent(Intent.ACTION_MAIN)
  4.             .addCategory(Intent.CATEGORY_HOME)
  5.             .setPackage(requireContext().packageName)
  6.         requireContext().startActivity(intent)
  7.         finish("returnToHome")
  8.     }
复制代码
View Code加载任务列表时,先加载任务,然后异步加载图片,避免最近任务延迟加载
13.gif
14.gif
  1.     private fun loadData() {
  2.         Log.d(TAG, "loadData")
  3.         mJob?.cancel()
  4.         mJob = lifecycleScope.launch {
  5.             taskAdapter.setData(getRecentTasks())
  6.             withContext(Dispatchers.IO) {
  7.                 setTaskThumbnail()
  8.             }
  9.             taskAdapter.notifyDataSetChanged()
  10.         }
  11.     }
复制代码
View Code
15.gif
16.gif
  1. private fun getRecentTasks(context: Context, maxLength: Int = 30): Collection<MyAppInfo> {
  2.         list.clear()
  3.         val recentTasks = ActivityTaskManager.getInstance()
  4.             .getRecentTasks(
  5.                 Int.MAX_VALUE,
  6.                 ActivityManager.RECENT_IGNORE_UNAVAILABLE,
  7.                 Process.myUserHandle().getIdentifier()
  8.             )
  9.         log("getRecentTasks size ${recentTasks.size}")
  10.         val packageManager = context.packageManager
  11.         if (packageManager == null) {
  12.             log("getRecentTasks packageManager is null")
  13.             return list
  14.         }
  15.         for (task in recentTasks) {
  16.             if (list.size >= maxLength) {
  17.                 log("getRecentTasks break")
  18.                 break
  19.             }
  20.             val intent = Intent(task.baseIntent)
  21.             if (task.baseActivity != null) {
  22.                 intent.component = task.baseActivity
  23.             }
  24.             // 设置intent的启动方式为 创建新task()【并不一定会创建】
  25.             intent.flags = (intent.flags and Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED.inv()
  26.                     or Intent.FLAG_ACTIVITY_NEW_TASK)
  27.             val resolveInfo = packageManager.resolveActivity(intent, 0)
  28.             if (resolveInfo == null) {
  29.                 log("getRecentTasks resolveInfo is null $resolveInfo")
  30.                 return list
  31.             }
  32.             val myAppInfo = MyAppInfo()
  33.             myAppInfo.taskId = task.taskId
  34.             try {
  35.                 val activityInfo = resolveInfo.activityInfo
  36.                 val pkg = activityInfo.packageName
  37.                 val title = activityInfo.loadLabel(packageManager).toString()
  38.                 myAppInfo.packageName = pkg
  39.                 myAppInfo.displayId = task.displayId
  40.                 log("getRecentTasks resolveInfo=$resolveInfo,title=$title,taskId=${task.taskId},displayId=${task.displayId}")
  41.                 val hasTask = filterTask.contains(pkg)
  42.                 if (hasTask) continue
  43.                 if (displayId != null && task.displayId != displayId) continue
  44.                 myAppInfo.title = title
  45.                 myAppInfo.appName = title
  46.                 myAppInfo.appLogo = activityInfo.loadIcon(packageManager).toBitmap()
  47.             } catch (e: Exception) {
  48.                 Log.e(TAG, "getRecentTasks ", e)
  49.             }
  50.             list.add(myAppInfo)
  51.             log("getRecentTasks add task ${myAppInfo.title},pkg=${myAppInfo.packageName}")
  52.         }
  53.         return list
  54.     }
复制代码
View Code打开任务应用

而进入任务应用,需要使用 ActivityManagerWrapper.getInstance().startActivityFromRecents()
17.gif
18.gif
  1. val options = ActivityOptions.makeBasic()
  2. options.launchDisplayId = displayId
  3. val bundle = options.toBundle()
  4. bundle.putInt("android.activity.windowingMode", 0)
  5. ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, options)
复制代码
View Code该方法是从最近任务恢复复用现有实例,会触发 onNewIntent()
跟startActivity的不同是直接start可能导致重复创建实例,并且二者系统处理逻辑也不一样,系统默认行为存在差异
多屏处理

19.gif
20.gif
  1. if (displayId != null && task.displayId != displayId) continue
复制代码
View Code如果有多屏需要考虑displayId,因为最近任务会加载所有屏幕打开的最近任务,而在当前屏幕下只需要加载当前屏幕下的最近任务
白名单处理

21.gif
22.gif
  1. val hasTask = filterTask.contains(pkg)
  2. if (hasTask) continue
复制代码
View Code如果需要屏蔽某个应用的任务,需要维护白名单,在最近任务列表中单独处理
结束任务

使用 ActivityManager.forceStopPackage(pkgName) 强制停止应用,包括后台服务、定时任务等
但是有些场景可能需要应用自己处理结束,需要保留服务,这种情况下需要特殊处理
23.gif
24.gif
  1. private fun stopProcessIfExist(am: ActivityManager, pkgName: String, taskId: Int? = null) {
  2.         log("stopProcessIfExist taskId:$taskId,pkg:$pkgName")
  3.         taskId?.let {
  4.             ActivityManagerWrapper.getInstance().removeTask(it)
  5.         }
  6.         if (Constant.PACKAGE_TEST == pkgName) {
  7.             am.runningAppProcesses?.let { processList ->
  8.                 log("stopProcessIfExist processList size ${processList.size}")
  9.                 for (i in processList.indices) {
  10.                     val info = processList[i]
  11.                     if (info != null && pkgName == info.processName) {
  12.                         Log.w(TAG, "processName=${info.processName} pid=${info.pid}")
  13.                         if (Constant.PACKAGE_TEST == info.processName) {
  14.                             Process.killProcess(info.pid)
  15.                         }
  16.                     }
  17.                 }
  18.             }
  19.         } else if (Constant.PACKAGE_TEST_SERVICE == pkgName) {
  20.             //do nothing
  21.         } else {
  22.             log("force-stop pkg:$pkgName")
  23.             am.forceStopPackage(pkgName)
  24.         }
  25.     }
复制代码
View CodeProcess.killProcess(info.pid) 仅终止目标进程,所以需要配合ActivityManagerWrapper.getInstance().removeTask(it)一起使用
而结束任务杀进程同样是耗时操作,根据情况需要放到子线程处理
内存信息

25.gif
26.gif
  1.     private fun getMemoryInfo(): ActivityManager.MemoryInfo? {
  2.         try {
  3.             val am =
  4.                 Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
  5.             val mi = ActivityManager.MemoryInfo()
  6.             am?.getMemoryInfo(mi)
  7.             return mi
  8.         } catch (e: Exception) {
  9.             Log.e(TAG, "loadMemory :$e")
  10.         }
  11.         return null
  12.     }
复制代码
View Code
27.gif
28.gif
  1.     fun loadMemory(): Pair<String, Double> {
  2.         val info = getMemoryInfo()
  3.         val totalMem = info?.totalMem ?: 0
  4.         val availMem = info?.availMem ?: 0
  5.         log("loadMemory totalMem $totalMem avail $availMem")
  6.         val total: Double = if (totalMem == 0L) 16.0 else {
  7.             totalMem / (1024 * 1024 * 1024.0)
  8.         }
  9.         val avail: Double = if (availMem == 0L) 0.0 else availMem / (1024 * 1024 * 1024.0)
  10.         log("loadMemory total $total avail $avail")
  11.         val using = Utils.getApp().resources.getString(R.string.memory_using)
  12.         val memoryText = "$using ${df.format(total - avail)} / ${df.format(total)}GB"
  13.         val progressNum = (total - avail) / total
  14.         val currentProgress = progressNum * 100
  15.         log("loadMemory progressNum $progressNum currentProgress $currentProgress")
  16.         return Pair(memoryText, currentProgress)
  17.     }
复制代码
View Codexml配置

29.gif
30.gif
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:tools="http://schemas.android.com/tools">
  4.     <uses-permission android:name="android.permission.REORDER_TASKS" />
  5.     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  6.     <uses-sdk   tools:overrideLibrary="com.android.systemui.shared"/>
  7.    
  8.         
  9.             <intent-filter>
  10.                
  11.             </intent-filter>
  12.             <meta-data
  13.                 android:name="distractionOptimized"
  14.                 android:value="true" />
  15.         </activity>
  16.     </application>
  17. </manifest>
复制代码
View Code如果是全屏界面隐藏状态栏
31.gif
32.gif
  1. window.isNavigationBarContrastEnforced = false
  2.         WindowCompat.setDecorFitsSystemWindows(window, false)
  3.         WindowCompat.getInsetsController(window, window.decorView).apply {
  4.             systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
  5.             hide(WindowInsetsCompat.Type.systemBars())
  6.         }
复制代码
View Code 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册