问题
操作描述:任意打开多个应用后切换到后台,点击最近任务图标显示后台应用为空。
出现机型:Android 8.1 Go版本
问题截图:
分析过程
拿到这个问题,首先考虑的是有哪些情况会导致这个问题:
case A:没有获取到activity stack中的数据;
case B:数据获取到了,没有获取到各个task的快照;
case C:其他未知原因;
解决这个问题我们要了解recents获取最近任务数据的基本流程:
从时序上看,在RecentsTaskLoadPlan中获取stack中的app数据,preloadPlan的代码如下:
/**
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
*
* The tasks will be ordered by:
* - least-recent to most-recent stack tasks
* - least-recent to most-recent freeform tasks
*
* Note: Do not lock, since this can be calling back to the loader, which separately also drives
* this call (callers should synchronize on the loader before making this call).
*/
void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
boolean includeFrontMostExcludedTask) {
Resources res = mContext.getResources();
ArrayList allTasks = new ArrayList<>();
if (mRawTasks == null) {
preloadRawTasks(includeFrontMostExcludedTask);
}
SparseArray affiliatedTasks = new SparseArray<>();
SparseIntArray affiliatedTaskCounts = new SparseIntArray();
SparseBooleanArray lockedUsers = new SparseBooleanArray();
String dismissDescFormat = mContext.getString(
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
int currentUserId = mPreloadedUserId;
long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
Settings.Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
if (RecentsDebugFlags.Static.EnableMockTasks) {
lastStackActiveTime = 0;
}
long newLastStackActiveTime = -1;
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, ActivityManager.RecentTaskInfo.getStackId(t), t.baseIntent,
ActivityManager.RecentTaskInfo.getUserId(t), ActivityManager.RecentTaskInfo.getFirstActiveTime(t),
ActivityManager.RecentTaskInfo.getLastActiveTime(t));
// This task is only shown in the stack if it satisfies the historical time or min
// number of tasks constraints. Freeform tasks are also always shown.
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(ActivityManagerDelegator.RecentTaskInfoDelegator.getStackId(t));
boolean isStackTask;
if (Recents.getConfiguration().isGridEnabled) {
// When grid layout is enabled, we only show the first
// TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT} tasks.
isStackTask = ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime &&
i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else if (Recents.getConfiguration().isLowRamDevice) {
// Show a max of 3 items
isStackTask = ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime &&
i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else {
isStackTask = isFreeformTask || !isHistoricalTask(t) ||
(ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
}
boolean isLaunchTarget = taskKey.id == runningTaskId;
// The last stack active time is the baseline for which we show visible tasks. Since
// the system will store all the tasks, we don't want to show the tasks prior to the
// last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
// the other stack-task constraints.
if (isStackTask && newLastStackActiveTime < 0) {
newLastStackActiveTime = ActivityManager.RecentTaskInfo.getLastActiveTime(t);
}
// Load the title, icon, and color
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
String titleDescription = loader.getAndUpdateContentDescription(taskKey,
t.taskDescription, res);
String dismissDescription = String.format(dismissDescFormat, titleDescription);
String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
false /* loadIfNotCached */, false /* storeInCache */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
if (lockedUsers.indexOfKey(ActivityManager.RecentTaskInfo.getUserId(t)) < 0) {
lockedUsers.put(ActivityManager.RecentTaskInfo.getUserId(t), Recents.getSystemServices().isDeviceLocked(ActivityManager.RecentTaskInfo.getUserId(t)));
}
boolean isLocked = lockedUsers.get(ActivityManager.RecentTaskInfo.getUserId(t));
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, ActivityManager.RecentTaskInfo.getAffiliatedTaskColor(t), icon,
thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
ActivityManagerRecentTaskInfo.supportsSplitScreenMultiWindow(t), ActivityManager.RecentTaskInfo.bounds(t),
t.taskDescription, ActivityManager.RecentTaskInfo.resizeMode(t), t.topActivity, isLocked);
allTasks.add(task);
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
newLastStackActiveTime, currentUserId);
}
// Initialize the stacks
mStack = new TaskStack();
mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
}
可以看出mRawTask就是获取到的最近任务的list,我们在遍历该list之前,将list的size打印出来。打印出Log如下:
06-13 08:59:17.156 1011-2095/com.android.systemui V/monster: taskCount: 2
在此之前打开了settings 和 phone 应用,如上述log,recents已经获取到最近任务数据。接下来,需要查一下是否是没有获取到对应的task的快照呢?
其获取的地方在此处:
ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
false /* loadIfNotCached */, false /* storeInCache */);
/frameworks/base/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
boolean storeInCache) {
SystemServicesProxy ssp = Recents.getSystemServices();
ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
if (cached != null) {
return cached;
}
cached = mTempCache.getAndInvalidateIfModified(taskKey);
if (cached != null) {
mThumbnailCache.put(taskKey, cached);
return cached;
}
if (loadIfNotCached) {
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
if (storeInCache) {
mThumbnailCache.put(taskKey, thumbnailData);
}
return thumbnailData;
}
}
}
// We couldn't load any thumbnail
return null;
}
所以需要在获取的地方判断获取的thumbnail是否为空?获取log如下:
06-13 09:30:33.900 1003-1251/com.android.systemui V/monster: thumbnail: true
06-13 09:30:36.669 1003-1003/com.android.systemui V/monster: thumbnail: true
可以看出,也获取到了对应的快照了。那么需要再去寻找其他可疑的地方。
从preload()函数中可以看到这么一段逻辑:
if (Recents.getConfiguration().isGridEnabled) {
// When grid layout is enabled, we only show the first
// TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT} tasks.
isStackTask = ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime &&
i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else if (Recents.getConfiguration().isLowRamDevice) {
// Show a max of 3 items
isStackTask = ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime &&
i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else {
isStackTask = isFreeformTask || !isHistoricalTask(t) ||
(ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
}
从上面可以看出在LowRam的机器上(Android Go)版本,google设计了这么一个逻辑,获取task的lastActiveTime,与一个阈值进行比较,如果小于这个阈值,则获取不到app的相关信息,并不让显示taskView。所以将这些时间打印出来,观察是否有无异常,log如下:
12-16 12:05:11.241 942-4048/com.android.systemui V/monster: legacyLastStackActiveTime: 0
lastStackActiveTime: 1505966512318
12-16 12:05:11.246 942-4048/com.android.systemui V/monster: getLastActiveTime: 1450238711095
异常log中获取到了一些时间戳,转化成正常时间:
1505966512318 > 2017-09-21 12:01:52
1450238711095 > 2015-12-16 12:05:11
这里的阈值是1505966512318,而最近任务的lastActiveTime是1450238711095,前面有介绍到此逻辑:
else if (Recents.getConfiguration().isLowRamDevice) {
// Show a max of 3 items
isStackTask = ActivityManager.RecentTaskInfo.getLastActiveTime(t) >= lastStackActiveTime &&
i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
}
由于设备是Android Go,这里的isStackTask flag当前就是false了,而正是因为这个flag导致recents在处理的时候,认为当前获取的的recents task是非法的,故没有显示taskView。
之后经确认,当时问题发现者存在手动调动时间,导致获取的stack active time在阈值时间之前,联网校准时间后,该问题解决。