前言
很多应用开发人员,在日常开发过程中,经常会遇到一些需求,例如需要知道当前最上层的Activity是哪个,并结合这个Activity的名称来完成一些特定场景的需求。最简单的方法,是在创建Activity的时候将该Actvity存储到一个集合中,而当Activity销毁的时候,再将该Activity从集合中移除,这种方案虽然能够获取自己应用当前最上层的Activity是那个,但却无法获取除了自己应用以外的其他场景。犹豫谷歌为系统开发提供了特定的API,作为系统开发人员,我们完全可以通过这些API实时获取当前最上层的Activity信息,并将这些信息同步给系统应用。本篇文章我们将会结合Android12的系统源码,来探讨一下如何通过系统内部API实时获取当前系统最上层的Activity的信息,以及如何实时监听当前系统Activity栈信息的变化。
一、获取最上层的根任务信息
1、根任务是指包含一个或多个 Activity 的任务,并且没有父任务,根任务通常是由用户启动的应用程序或系统应用程序的主要任务。
在 Android 12 中,我们可以通过以下代码获取最上层的根任务信息。
IActivityManager ams = ActivityManager.getService(); //获取ActivityManagerService服务对象
List<ActivityTaskManager.RootTaskInfo> runningTasks = mAm.getAllRootTaskInfos();;//调用getAllRootTaskInfos方法
首先调用ActivityManager的getService方法获取ActivityManagerService服务对象,然后调用该服务对象的getAllRootTaskInfos方法,该方法会返回一个指定数量包含所有根任务信息的列表,每个根任务都有其任务 ID、根 Activity 的信息以及与之关联的其他活动堆栈。
2、来看下和RootTaskInfo类相关的代码。
base/core/java/android/app/ActivityTaskManager .java
public class ActivityTaskManager {
public static class RootTaskInfo extends TaskInfo implements Parcelable {
// TODO(b/148895075): Move some of the fields to TaskInfo.
public Rect bounds = new Rect();
public int[] childTaskIds;
public String[] childTaskNames;
public Rect[] childTaskBounds;
public int[] childTaskUserIds;
public boolean visible;
// Index of the stack in the display's stack list, can be used for comparison of stack order
public int position;
}
}
3、RootTaskInfo 继承自TaskInfo,继续来看下该类有哪些关键属性。
base/core/java/android/app/TaskInfo.java
public class TaskInfo {
private static final String TAG = "TaskInfo";
/**
* 当前任务对应的用户id
* The id of the user the task was running as if this is a leaf task. The id of the current
* running user of the system otherwise.
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int userId;
/**
* 任务id
* The identifier for this task.
*/
public int taskId;
/**
* 此任务中是否有正在运行的Activity
* Whether or not this task has any running activities.
*/
public boolean isRunning;
/**
* 启动当前任务的Intent
* The base intent of the task (generally the intent that launched the task). This intent can
* be used to relaunch the task (if it is no longer running) or brought to the front if it is.
*/
@NonNull
public Intent baseIntent;
/**
* The component of the first activity in the task, can be considered the "application" of this
* task.
*/
@Nullable
public ComponentName baseActivity;
/**
* 当前任务对应的Activity栈中的最上层正在显示的activity
* The component of the top activity in the task, currently showing to the user.
*/
@Nullable
public ComponentName topActivity;
/**
* The component of the target activity if this task was started from an activity alias.
* Otherwise, this is null.
*/
@Nullable
public ComponentName origActivity;
/**
* The component of the activity that started this task (may be the component of the activity
* alias).
* @hide
*/
@Nullable
public ComponentName realActivity;
/**
* The number of activities in this task (including running).
*/
public int numActivities;
/**
* The last time this task was active since boot (including time spent in sleep).
* @hide
*/
@UnsupportedAppUsage
public long lastActiveTime;
/**
* 当前任务对应的屏幕设备id
* The id of the display this task is associated with.
* @hide
*/
public int displayId;
/**
* The feature id of {@link com.android.server.wm.TaskDisplayArea} this task is associated with.
* @hide
*/
public int displayAreaFeatureId = FEATURE_UNDEFINED;
/**
* The recent activity values for the highest activity in the stack to have set the values.
* {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
*/
@Nullable
public ActivityManager.TaskDescription taskDescription;
/**
* The locusId of the task.
* @hide
*/
@Nullable
public LocusId mTopActivityLocusId;
/**
* 当前任务是否支持分屏
* True if the task can go in the split-screen primary stack.
* @hide
*/
@UnsupportedAppUsage
public boolean supportsSplitScreenMultiWindow;
/**
* 当前任务是否支持多窗口
* Whether this task supports multi windowing modes based on the device settings and the
* root activity resizability and configuration.
* @hide
*/
public boolean supportsMultiWindow;
/**
* The resize mode of the task. See {@link ActivityInfo#resizeMode}.
* @hide
*/
@UnsupportedAppUsage
public int resizeMode;
/**
* The current configuration of the task.
* @hide
*/
@NonNull
@UnsupportedAppUsage
public final Configuration configuration = new Configuration();
/**
* Used as an opaque identifier for this task.
* @hide
*/
@NonNull
public WindowContainerToken token;
/**
* 用于控制画中画模式的参数类
* The PictureInPictureParams for the Task, if set.
* @hide
*/
@Nullable
public PictureInPictureParams pictureInPictureParams;
/**
* The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
* (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
* {@code null} otherwise.
* @hide
*/
@Nullable
public Rect displayCutoutInsets;
/**
* 当前任务最上层Activity的类型
* The activity type of the top activity in this task.
* @hide
*/
public @WindowConfiguration.ActivityType int topActivityType;
/**
* The {@link ActivityInfo} of the top activity in this task.
* @hide
*/
@Nullable
public ActivityInfo topActivityInfo;
/**
* Whether the direct top activity is in size compat mode on foreground.
* @hide
*/
public boolean topActivityInSizeCompat;
/**
* Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
* supports), this is what the system actually uses for resizability based on other policy and
* developer options.
* @hide
*/
public boolean isResizeable;
/**
* Relative position of the task's top left corner in the parent container.
* @hide
*/
public Point positionInParent;
/**
* The launch cookies associated with activities in this task if any.
* @see ActivityOptions#setLaunchCookie(IBinder)
* @hide
*/
public ArrayList<IBinder> launchCookies = new ArrayList<>();
/**
* The identifier of the parent task that is created by organizer, otherwise
* {@link ActivityTaskManager#INVALID_TASK_ID}.
* @hide
*/
public int parentTaskId;
/**
* 当前任务是否持有焦点
* Whether this task is focused.
* @hide
*/
public boolean isFocused;
/**
* 当前任务是否可见
* Whether this task is visible.
* @hide
*/
public boolean isVisible;
/**
* Whether this task is sleeping due to sleeping display.
* @hide
*/
public boolean isSleeping;
}
TaskInfo类中也包含了很多对于当前任务至关重要的信息:任务对应的用户id、任务id、任务中是否有运行的Activity、启动任务的Intent、任务对应的Activity栈最上层Activity,对应的屏幕设备id、任务是否支持分屏、任务是否支持多窗口、用于控制画中画模式的参数类、任务是否持有焦点、任务是否可见等。
二、实时监听Activity栈信息变化。
1、我们主要是通过调用ActivityManagerService的相关方法来实时监听Activity对应的任务栈的变化的,具体可以参考以下代码。
public class ActivityTopTaskManager {
private Context mContext;
private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
private IActivityManager mAm;
private final Object mLock = new Object();
public void init(Context context) {
try {
mContext = context;
//获取ActivityManagerService的实例对象
IActivityManager am = ActivityManager.getService();
//调用registerTaskStackListener方法,注册监听任务栈变化的回调对象
am.registerTaskStackListener(new TaskStackListener() {
@Override
public void onTaskStackChanged() throws RemoteException {
//调用updateTasks
updateTasks();
}
});;
} catch (Exception e) {
e.printStackTrace();
}
}
//获取任务栈信息
public void updateTasks() {
try {
List<ActivityTaskManager.RootTaskInfo> rootTasks = mAm.getAllRootTaskInfos();
if (rootTasks == null) {
return;
}
int focusedStackId = INVALID_STACK_ID;
final ActivityTaskManager.RootTaskInfo focusedTaskInfo = mAm.getFocusedRootTaskInfo();
if (focusedTaskInfo != null) {
focusedStackId = focusedTaskInfo.taskId;
}
SparseArray<TopTaskInfoContainer> topTasks = new SparseArray<>();
synchronized (mLock) {
for (ActivityTaskManager.RootTaskInfo info : rootTasks) {
int displayId = info.displayId;
if (info.childTaskNames.length == 0 || !info.visible) { // empty stack or not shown
continue;
}
TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
info.topActivity, info.childTaskIds[info.childTaskIds.length - 1],
info.displayId, info.position, info);
TopTaskInfoContainer currentTopTaskInfo = topTasks.get(displayId);
if (currentTopTaskInfo == null || newTopTaskInfo.position > currentTopTaskInfo.position) {
topTasks.put(displayId, newTopTaskInfo);
Log.i(TAG, "Updating top task to: " + newTopTaskInfo);
}
}
for (int i = 0; i < topTasks.size(); i++) {
TopTaskInfoContainer topTask = topTasks.valueAt(i);
int displayId = topTasks.keyAt(i);
mTopTasks.put(displayId, topTask);
}
}
} catch (Exception e) {
Log.d(TAG, "updateTasks: e = " + e);
}
}
}
- 调用ActivityManagerService的registerTaskStackListener方法注册回调对象,实时监听当前任务栈的变化。
- 继续调用ActivityManagerService的getAllRootTaskInfos方法,获取当前的所有跟任务信息,该方法返回一个类型为RootTaskInfo的集合,关于RootTaskInfo这个类,前面我们已经做过简单介绍了。
- 最后结合RootTaskInfo的相关属性,可以成功获取到当前系统中每个屏幕设备对应的最上层可见的Activity的包名和组件名称。
💡 技术无价,赞赏随心
写文不易,如果本文帮你避开了“八小时踩坑”,或者让你直呼“学到了!”
欢迎扫码赞赏,让我知道这篇内容值得!