很多应用开发人员,在日常开发过程中,经常会遇到一些需求,例如需要知道当前最上层的Activity是哪个,并结合这个Activity的名称来完成一些特定场景的需求。最简单的方法,是在创建Activity的时候将该Actvity存储到一个集合中,而当Activity销毁的时候,再将该Activity从集合中移除,这种方案虽然能够获取自己应用当前最上层的Activity是那个,但却无法获取除了自己应用以外的其他场景。犹豫谷歌为系统开发提供了特定的API,作为系统开发人员,我们完全可以通过这些API实时获取当前最上层的Activity信息,并将这些信息同步给系统应用。本篇文章我们将会结合Android12的系统源码,来探讨一下如何通过系统内部API实时获取当前系统最上层的Activity的信息,以及如何实时监听当前系统Activity栈信息的变化。
1、根任务是指包含一个或多个 Activity 的任务,并且没有父任务,根任务通常是由用户启动的应用程序或系统应用程序的主要任务。
在 Android 12 中,我们可以通过以下代码获取最上层的根任务信息。
IActivityManager ams = ActivityManager.getService(); //获取ActivityManagerService服务对象
List<ActivityManager.RunningTaskInfo> runningTasks = mAm.getTasks(1);;//调用getAllRootTaskInfos方法
首先调用ActivityManager的getService方法获取ActivityManagerService服务对象,然后调用该服务对象的getTasks(int maxNum) 方法,该方法会返回一个指定数量包含所有根任务信息的列表,每个根任务都有其任务 ID、根 Activity 的信息以及与之关联的其他活动堆栈。
2、来看下和RunningTaskInfo类相关的代码。
base/core/java/android/app/ActivityManager.java
public class ActivityManager {
public static class RunningTaskInfo extends TaskInfo implements Parcelable {
/**
* 当前任务的唯一标识id
* A unique identifier for this task.
*/
@Deprecated
public int id;
/**
* 当前任务状态的缩略图
* Thumbnail representation of the task's current state.
*/
@Deprecated
public Bitmap thumbnail;
/**
* 当前任务的状态描述
* Description of the task's current state.
*/
@Deprecated
public CharSequence description;
/**
* 当前任务中正在运行的Activity的数量
* Number of activities that are currently running (not stopped and persisted) in this task.
*/
@Deprecated
public int numRunning;
}
}
RunningTaskInfo类包含了当前任务对应的唯一标识ID、Bitmap类型的缩略图、状态描述以及该任务正运行的Activity的数量。
3、RunningTaskInfo继承自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、任务是否支持分屏、任务是否支持多窗口、用于控制画中画模式的参数类、任务是否持有焦点、任务是否可见等。
1、我们主要是通过调用ActivityManagerService的相关方法来实时监听Activity对应的任务栈的变化的,具体可以参考以下代码。
//获取ActivityManagerService的实例对象
IActivityManager am = ActivityManager.getService();
//调用registerTaskStackListener方法,注册监听任务栈变化的回调对象
am.registerTaskStackListener(new TaskStackListener() {
@Override
public void onTaskStackChanged() throws RemoteException {
final ActivityManager.RunningTaskInfo runningTask;
try {
//
List<ActivityManager.RunningTaskInfo> runningTasks = mAm.getTasks(1);
if (runningTasks == null) {
return;
}
runningTask = runningTasks.get(0);
} catch (RemoteException e) {
e.printStackTrace();
return;
}
if (runningTask == null) {
return;
}
int displayId = runningTask.displayId;
if (INVALID_DISPLAY != displayId && runningTask.topActivity != null) {
String key = "display_" + displayId + "_top_activity";
String packageName = runningTask.topActivity.getPackageName();
String activityName = runningTask.topActivity.getClassName();
String value = packageName + "/" + activityName;
boolean isUpdate = !value.equals(mHashMapDisplayTopActivity.get(key));
if (isUpdate) {
Log.d(TAG, "updateTasks: key = " + key + " value = " + value);
mHashMapDisplayTopActivity.put(key, value);
}
}
}
});;
对以上代码做个简单介绍:
不过在进行实测的时候发现,通过调用registerTaskStackListener方法注册的回调方法,有一定概率在页面发生切换的时候不会进行回调,比如应用异常崩溃,应用ANR被强制关闭等特殊情况,这个字段回调方法并没有执行,这就需要我们在这些事件发生的时候,通知AMS让其进行回调事件的调用。