Android14 通过AMS 实例获取前台Activity 信息
文章目录
- [Android14 通过AMS 实例获取前台Activity 信息](#Android14 通过AMS 实例获取前台Activity 信息)
-
- 一、前言
- [二、AMS 实例获取前台Activity 信息](#二、AMS 实例获取前台Activity 信息)
- 三、其他
-
- [1、AMS 实例获取前台Activity 信息 小结](#1、AMS 实例获取前台Activity 信息 小结)
- 2、查看ActivityManager内部类应用数据对象
-
- [(1) `RunningTaskInfo`:正在运行的任务信息](#(1)
RunningTaskInfo
:正在运行的任务信息) - [(2) `RecentTaskInfo`:最近任务信息](#(2)
RecentTaskInfo
:最近任务信息) - [(3) `RunningAppProcessInfo`:正在运行的应用进程信息](#(3)
RunningAppProcessInfo
:正在运行的应用进程信息)
- [(1) `RunningTaskInfo`:正在运行的任务信息](#(1)
- 总结
- [3、正在运行的应用进程等级 RunningAppProcessInfo.importance](#3、正在运行的应用进程等级 RunningAppProcessInfo.importance)
- 4、查看ActivityManager内部类应用数据对象打印日志
- 5、清除后台任务代码
一、前言
本文主要讲一下:Android 通过AMS 实例获取Activity 信息的一些示例讲解
比如:获取当前存活的Activity界面;获取后台任务列表等。
一般应用场景是:清后台或者想跳转到最近任务界面;
本来不想写的,我以为这东西百度一下马上就出来了,感觉是很容易的东西;
豆包AI搜索出来的代码:
// 获取ActivityManager实例(AMS的客户端代理)
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// 获取当前运行的任务信息,最多返回1个任务(当前前台任务)
List<ActivityManager.RunningTaskInfo> runningTasks = activityManager.getRunningTasks(1);
if (runningTasks != null && !runningTasks.isEmpty()) {
// 获取当前任务中Activity的数量
int activityCount = runningTasks.get(0).numActivities;
Log.d(TAG, "当前任务中的Activity数量: " + activityCount);
} else {
Log.d(TAG, "未获取到运行中的任务");
}
//getRunningTasks 已经废弃,需要使用getAppTasks方法
List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
if (appTasks != null) {
Log.d(TAG, "当前应用的任务数量: " + appTasks.size());
}
// 可选:获取当前运行的所有应用进程信息
List<ActivityManager.RunningAppProcessInfo> processes = activityManager.getRunningAppProcesses();
Log.d(TAG, "当前运行的应用进程数量: " + (processes != null ? processes.size() : 0));
RunningTaskInfo包含一些应用信息,看起来比较正确;
但是旁边同事说网上找到的答案和AI搜索到的都是验证不行。
应该是有坑的。所以这里总结记录一下。有需要的可以收藏查看。
本文的总结是有些网上目前没有的知识点,后续展示的其他内容也是有价值的。
二、AMS 实例获取前台Activity 信息
1、分析过程
运行了上面的AI搜索的代码,发现返回的只有当前demo应用的界面信息,无法获取其他后台的应用信息!
然后发现添加系统签名才能获取到后台其他的应用信息。
所以soga.
第一点需要注意的就是:应用需要系统签名才能获取后台应用信息。
下面所有的代码都是基于系统签名进行验证有用的代码。
2、获取存活的Activity应用
存活的Activity 指的是app界面未退出的应用,比如进入应用后按Home退出的应用;
如果是一直"返回"退出的应用,是不存在存活Activity的。
// 获取ActivityManager实例(AMS的客户端代理)
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// 获取当前运行的任务信息(当前前台任务)
List<ActivityManager.RunningTaskInfo> runningTasks=activityManager.getRunningTasks(30);
Log.d(TAG, "当前任务中的Activity数量 runningTasks: " + runningTasks.size());
for (ActivityManager.RunningTaskInfo runningTask : runningTasks) {
Log.d(TAG, "前台任务ID: " + runningTask.id + runningTask.toString()); //runningTask.toString() 包含了大部分内容
tv_info.append("\n前台任务ID: " + runningTask.id + ", topActivity = " + runningTask.topActivity);
int activityCount = runningTask.numActivities;
tv_info.append("当前任务中的Activity数量: " + activityCount);
}
通过验证发现 getRunningTasks 还是可以使用,需要设置一个比较大的任务列表数值,就会返回多个任务对象;
最新的 getAppTasks() 方法反而没啥作用,只能返回本身对象;
3、获取最近任务
这里指的是最近的后台任务。
Android 界面上划显示的后台界面任务列表就是通过这种方式获取的。
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
for (ActivityManager.RecentTaskInfo task : recentTasks) {
Log.d(TAG, "Task ID: " + task.id);
Log.d(TAG, "Top Activity: " + task.topActivity); //通过这个可以预览或者跳转到最近使用的应用界面
// 其他相关信息...
}
上面获取到的最近任务列表 会存在部分task.topActivity 为null的情况,这些一般就会跳过显示。
清除后台应用一般就是循环forceStopPackage应用,之后在最近任务无法获取到它了。
有时候获取到的 存活的Activity应用列表和 最近任务列表是一样的。
普通Activity应用,后退back退出界面,虽然没有存活的Activity,但是它还是会显示在最近的后台应用列表中。
虽然应用的Activity不存在了,也没有后台服务,但是系统还是会有缓存保留,
缓存进程还在,下次直接打开Activity,还是之前的应用进程id,通过ps -ef | grep 包名 可以验证。
如果调用forceStopPackage应用或者强制kill应用进程,重新打开应用就会生成新的进程。
需要注意的是,缓存进程不会保存很久,系统会存在不定期清除的情况。
网上的说法是上面最近任务的获取方式已经过期,需要使用UsageStatsManager.queryUsageStats。
比如下面代码:
UsageStatsManager usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
List<UsageStats> usageStats = usm.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(24), // 最近24小时
System.currentTimeMillis()
);
Log.d(TAG, "24小时内启动过的进程数: " + usageStats.size());
tv_info.append("\n 24小时内启动过的进程数: " + usageStats.size());
for (UsageStats stats : usageStats) {
// if ( stats.getLastTimeUsed() > 0) {
Log.d(TAG, "Package Name: " + stats.getPackageName() + ", Last Time Used: " + stats.getLastTimeUsed() + ", Last Time Visible: " + stats.getLastTimeVisible());
// }
}
但是实际测试上面代码是不行的,stats.getLastTimeUsed() 方法返回的值基本都是零。
虽然返回的UsageStats列表数据比较多,但是UsageStats对象没啥具体数据,只有个不准确的时间戳和包名。
所以获取最近任务还是需要使用 ActivityManager.getRecentTasks ,
有些方法虽然过期,但是对于系统或者系统应用来说是稳定/正常使用的。
3、获取存活进程
后台的任务值的是存活的应用,有存活的Activity或者后台Service都是存活的应用。
// 获取ActivityManager实例(AMS的客户端代理)
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// 可选:获取当前运行的所有应用进程信息,类似 ps -ef
List<ActivityManager.RunningAppProcessInfo> processes = activityManager.getRunningAppProcesses();
Log.d(TAG, "当前运行的应用进程数量: " + (processes != null ? processes.size() : 0));
tv_info.append("\n当前运行的应用进程数量: " + (processes != null ? processes.size() : 0));
for (ActivityManager.RunningAppProcessInfo process : processes) {
Log.d(TAG, "进程名: " + process.processName + " ,importance = " + process.importance );
tv_info.append("\n进程名: " + process.processName + " ,importance = " + process.importance + ",process.uid = " + process.uid + ",process.pid = " + process.pid);
}
上面这个获取RunningAppProcessInfo对象的场景可能是需要判断某个应用是否存活才需要。
对比一下后台任务和ps -ef
ps -ef 也是获取系统目前存活的进程,不仅仅是系统应用,还有些底层进程。
ps -ef 过滤某个应用的信息示例:
用户类型 父进程ID(ppid) 进程ID(pid) 启动时间 程序计数器 应用包名
console:/ # ps -ef | grep com.android.settings
system 1219 539 0 20:40:35 ? 00:00:16 com.android.settings
root 4081 5439 4 21:47:26 ttyS0 00:00:00 grep com.android.settings
//普通应用
u0_a36 9085 483 20 19:07:55 ? 00:00:06 org.skg.chromium
很多人以为 ps -ef第二三个数值是pid和uid,其实不是,是(父)进程id,有些应用会存在多个子进程。
RunningAppProcessInfo 的pid和ps -ef 中的ppid是一样的;
注意 ,RunningAppProcessInfo 的uid和ps -ef 中的pid是不一样的;
RunningAppProcessInfo 的uid 一般是指应用签名的用户id,如果是系统签名就是1000,否则就是其他。
三、其他
1、AMS 实例获取前台Activity 信息 小结
(1)获取系统各个Activity应用的信息必须是系统签名,否则只能获取到自身应用信息;
(2)获取存活的Activity应用使用的api://返回 List<ActivityManager.RunningTaskInfo>
ActivityManager.getRunningTasks(30);
ActivityManager.getAppTasks();//没啥用
(3)获取获取最近任务使用应用的api: //返回 List<ActivityManager.RecentTaskInfo>
ActivityManager.getRecentTasks(30, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
UsageStatsManager.queryUsageStats()//没啥用
(4)获取目前存活的进程应用使用的api://返回 List<ActivityManager.RunningAppProcessInfo>
ActivityManager.getRunningAppProcesses();
上面就是Activity应用信息的相关api;
最近任务的应用列表,并不仅仅是指最近使用的应用,还需要有系统缓存或者有存活进程的应用才会出现在列表。
不同的获取方式返回的数据对象是不同,这个可以看看哪些数据是实际有用的。
2、查看ActivityManager内部类应用数据对象
主要查看 RunningTaskInfo、RecentTaskInfo、RunningAppProcessInfo 这三个对象有啥有用的信息:
(1) RunningTaskInfo
:正在运行的任务信息
核心含义 :描述当前系统中 "活动任务栈" 的状态(一个任务是一系列关联 Activity
的集合,遵循 "后进先出" 的栈结构)。
有用信息:
taskId
:任务的唯一标识符,用于区分不同任务。topActivity
:任务栈顶的ComponentName
(包含包名和类名),即当前用户可见的最上层Activity
。baseActivity
:任务栈底的ComponentName
,通常是任务的入口Activity
(如应用的 launcher Activity)。numActivities
:该任务中包含的Activity
数量,反映任务的复杂度。description
:任务的描述信息(可能为null
),通常用于 UI 展示。isRunning
:任务是否处于运行状态(非暂停 / 停止)。priority
:任务的优先级(数值越低优先级越高),影响系统内存不足时的回收策略。
用途 :可用于监控当前活跃的任务栈结构、判断前台应用(通过 topActivity
)、分析任务切换流程等。
(2) RecentTaskInfo
:最近任务信息
核心含义:描述用户最近交互过的任务(包含已退出但仍保留在 "最近任务列表" 中的任务),用于系统 "最近任务" 界面(如多任务切换界面)的展示。
有用信息:
taskId
:任务的唯一标识符(与RunningTaskInfo
的taskId
对应)。baseIntent
:启动该任务的基础Intent
,可用于重新打开任务(如用户点击最近任务时,系统通过此Intent
重启任务)。lastActiveTime
:任务最后一次活跃的时间戳,用于排序最近任务列表。description
:任务的描述文本(可能包含 Activity 标签)。icon
:任务的图标(通常是应用的图标)。label
:任务的显示名称(通常是应用名称或 Activity 标签)。isPersisted
:任务是否被持久化(即使进程被销毁,仍保留在最近列表中)。
用途:主要用于构建自定义的最近任务列表、判断用户最近使用的应用、实现类似系统多任务切换的功能。
(3) RunningAppProcessInfo
:正在运行的应用进程信息
核心含义 :描述当前系统中正在运行的应用进程(一个进程可能包含多个组件,如 Activity
、Service
等)。
有用信息:
pid
:进程的 ID(进程唯一标识)。uid
:进程所属的用户 ID(用于权限管理,同一应用通常共享相同uid
)。processName
:进程名称(通常与应用包名一致,或为包名 + 进程别名,如com.example.app:background
)。importance
:进程的重要性等级(如IMPORTANCE_FOREGROUND
表示前台进程,IMPORTANCE_BACKGROUND
表示后台进程),决定了内存不足时系统优先回收哪个进程。pkgList
:该进程中包含的所有应用包名(一个进程可能运行多个包的组件,如共享进程的应用)。status
:进程的状态(如STATUS_RUNNING
表示正在运行,STATUS_PAUSED
表示暂停)。lastTrimLevel
:最近一次内存修剪的等级(反映系统内存压力,数值越高压力越大)。
用途:可用于监控系统进程状态、分析应用内存占用、判断进程是否存活、实现进程管理工具(如清理后台进程)等。
总结
RunningTaskInfo
聚焦于 "任务栈" 的 Activity 组织关系,反映当前活跃的界面流程;RecentTaskInfo
聚焦于用户最近交互的任务,用于任务切换场景;RunningAppProcessInfo
聚焦于底层进程的运行状态,反映系统资源分配和进程优先级。
这三个内部类的源码都是可以在Android Studio/系统源码查看包含的具体信息。
3、正在运行的应用进程等级 RunningAppProcessInfo.importance
进程是否被终止,取决于系统的 "内存管理策略",核心依据是进程重要性等级(由高到低):
进程类型 | 重要性等级 | 存活优先级 | 典型场景 |
---|---|---|---|
前台进程 | IMPORTANCE_FOREGROUND //100 |
最高 | 有可见 Activity 或前台服务 |
可见进程 | IMPORTANCE_VISIBLE //200 |
高 | 有部分可见的 Activity(如弹窗) |
服务进程 | IMPORTANCE_SERVICE //300 |
中 | 有后台服务运行 |
缓存进程 | IMPORTANCE_CACHED //400 |
低 | 无活跃组件,仅作缓存 |
这里可以看到进程重要性等级的值越低,存活优先级越高,应用进程存活的概率越大。
当系统内存不足时,会按照 "从低优先级到高优先级" 的顺序回收进程:
先杀缓存进程,再杀服务进程,最后才会考虑可见 / 前台进程。
因此,即使 Activity 销毁,只要进程是 "服务进程" 或 "缓存进程",就可能继续存活。
这个也是为啥只有Activity组件的应用退出界面后,系统还是能检测到它在应用进程和最近任务进程列表;
如果对该应用进行forceStop,那么它就不会被检测在应用进程和最近任务进程列表。
进程等级类别定义:
public class ActivityManager {
private static String TAG = "ActivityManager";
public static final int IMPORTANCE_FOREGROUND = 100;
/**
* Constant for {@link #importance}: This process is running something
* that is actively visible to the user, though not in the immediate
* foreground. This may be running a window that is behind the current
* foreground (so paused and with its state saved, not interacting with
* the user, but visible to them to some degree); it may also be running
* other services under the system's control that it inconsiders important.
*/
public static final int IMPORTANCE_VISIBLE = 200;
/**
* Constant for {@link #importance}: This process contains services
* that should remain running. These are background services apps have
* started, not something the user is aware of, so they may be killed by
* the system relatively freely (though it is generally desired that they
* stay running as long as they want to).
*/
public static final int IMPORTANCE_SERVICE = 300;
/**
* Constant for {@link #importance}: This process process contains
* cached code that is expendable, not actively running any app components
* we care about.
*/
public static final int IMPORTANCE_CACHED = 400;
...//中间还有些100多,200多的值可能没那么重要。
}
4、查看ActivityManager内部类应用数据对象打印日志
三个应用数据对象的定义如下:
@SystemService(Context.ACTIVITY_SERVICE)
public class ActivityManager {
private static String TAG = "ActivityManager";
...
public static class RunningTaskInfo extends TaskInfo implements Parcelable {...}
public static class RecentTaskInfo extends TaskInfo implements Parcelable {...}
public static class RunningAppProcessInfo implements Parcelable {...}
}
上面三个内部类本身没有实现toString()方法;
但是 TaskInfo 有 toString()包含的内容还挺多的,比如:
public class TaskInfo {
private static final String TAG = "TaskInfo";
@Override
public String toString() {
return "TaskInfo{userId=" + userId + " taskId=" + taskId
+ " displayId=" + displayId
+ " isRunning=" + isRunning
+ " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
+ " topActivity=" + topActivity + " origActivity=" + origActivity
+ " realActivity=" + realActivity
+ " numActivities=" + numActivities
+ " lastActiveTime=" + lastActiveTime
+ " supportsMultiWindow=" + supportsMultiWindow
+ " resizeMode=" + resizeMode
+ " isResizeable=" + isResizeable
+ " minWidth=" + minWidth
+ " minHeight=" + minHeight
+ " defaultMinSize=" + defaultMinSize
+ " token=" + token
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
+ " shouldDockBigOverlays=" + shouldDockBigOverlays
+ " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
+ " launchCookies=" + launchCookies
+ " positionInParent=" + positionInParent
+ " parentTaskId=" + parentTaskId
+ " isFocused=" + isFocused
+ " isVisible=" + isVisible
+ " isSleeping=" + isSleeping
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
+ " topActivityEligibleForLetterboxEducation= "
+ topActivityEligibleForLetterboxEducation
+ " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
+ " isFromDoubleTap= " + isFromLetterboxDoubleTap
+ " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
+ " topActivityLetterboxHorizontalPosition= "
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
+ cameraCompatControlStateToString(cameraCompatControlState)
+ "}";
}
...
}
所以 RunningTaskInfo 和 RecentTaskInfo 可以用 toString() 打印查看所有信息看看哪些有用;
RunningAppProcessInfo 就对比上面有用的信息数据逐个打印想要的数据进行查看即可。
RunningTaskInfo.toString()打印的示例日志,如下:
前台任务ID: 102
TaskInfo{userId=0 taskId=102 displayId=0 isRunning=true
baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.android.settings/.Settings } baseActivity=ComponentInfo{com.android.settings/
com.android.settings.homepage.SettingsHomepageActivity} topActivity=ComponentInfo{com.android.settings/
com.android.settings.SubSettings} origActivity=ComponentInfo{com.android.settings/
com.android.settings.Settings} realActivity=ComponentInfo{com.android.settings/
com.android.settings.homepage.SettingsHomepageActivity}
numActivities=3 lastActiveTime=9088708 supportsMultiWindow=true
resizeMode=1 isResizeable=true minWidth=-1 minHeight=-1
defaultMinSize=180
token=WCT{android.window.IWindowContainerToken$Stub$Proxy@76ece20}
topActivityType=1 pictureInPictureParams=null shouldDockBigOverlays=false
launchIntoPipHostTaskId=-1 lastParentTaskIdBeforePip=-1
displayCutoutSafeInsets=null
topActivityInfo=ActivityInfo{9bbb3d9 com.android.settings.SubSettings}
launchCookies=[] positionInParent=Point(0, 0) parentTaskId=-1
isFocused=false isVisible=false isVisibleRequested=false
isSleeping=false topActivityInSizeCompat=false
topActivityEligibleForLetterboxEducation= false
isLetterboxDoubleTapEnabled= false
topActivityEligibleForUserAspectRatioButton= false
topActivityBoundsLetterboxed= false
isFromLetterboxDoubleTap= false
topActivityLetterboxVerticalPosition= -1
topActivityLetterboxHorizontalPosition= -1
topActivityLetterboxWidth=-1 topActivityLetterboxHeight=-1
isUserFullscreenOverrideEnabled=false
isTopActivityTransparent=false
locusId=null displayAreaFeatureId=1 cameraCompatControlState=hidden
}
5、清除后台任务代码
一般只遍历当前后台任务,获取包名,然后根据需求清除一部分或者全部的应用
forceStopPackage 方法是隐藏的,所以需要反射:
public static void forceStopPackage(Context context , String packageName) {
try {
ActivityManager mActivityManager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
mActivityManager.forceStopPackage(packageName);
Method method;
method = Class.forName("android.app.ActivityManager").getMethod("forceStopPackage", String.class);
method.invoke(mActivityManager, packageName);
} catch (Exception e) {
e.printStackTrace();
}
}
上面就是根据包名强制停止应用程序的示例代码;
被停止的应用程序,后续不会在最近任务列表和存活进程列表中出现。
上面内容还是比较多的,可能不一定都有用,可以收藏后续按需查看吧。