1.Recent Panel按键处理流程
SystemUI有一个很重要的功能就是显示近期使用的app,方便用户点击使用。
手机长按HOME键或者点击Navigation Bar的近期任务栏虚拟键可以显示Recent Panel。
我这里手头上只有Android TV平台,并且也便于debug,所以讲讲收到Switch按键后,Recent Panel的显示流程。
KeyEvent.java中对于Switch按键的定义:
/** Key code constant: App switch key.
* Should bring up the application switcher dialog. */
public static final int KEYCODE_APP_SWITCH = 187;
* Should bring up the application switcher dialog. */
public static final int KEYCODE_APP_SWITCH = 187;
从KeyCode的定义可以看到Recent Panel的作用是the application switcher dialog。
当KeyEvent给到WindowManagerService之前,会先给到PhoneWindowManager处理(给系统一次机会,去处理按键消息)。
在KeyEvent出队列时,会走到interceptKeyBeforeDispatching函数,因此对于KEYCODE_APP_SWITCH的处理,会在这里进行。
/** {@inheritDoc} */
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
....
else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
if (!keyguardOn) {
if (down && repeatCount == 0) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
return -1;
}
if (!keyguardOn) {
if (down && repeatCount == 0) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
return -1;
}
在key down时,load最近使用的apps->preloadRecentApps();
在key up时,打开或关闭Recent Panel-> toggleRecentApps();
2.Recent Apps加载流程
在PhoneWindowManager里面开始执行preloadRecentApps()函数后,一步步调用,最终会call到我们熟悉的Recents.java,即最终是通过SystemUI去Reload Recent apps。
下面是这个函数的逻辑时序图:
预加载Recent Apps核心函数是Recents.java中的preloadRecentsInternal函数。
函数代码如下:
- void preloadRecentsInternal() {
- // Preload only the raw task list into a new load plan (which will be consumed by the
- // RecentsActivity) only if there is a task to animate to.
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- MutableBoolean topTaskHome = new MutableBoolean(true);
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
- sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
- loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
- TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
- if (top.getTaskCount() > 0) {
- preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
- topTaskHome.value);
- }
- }
- }
1)获取当前运行的Task
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
/** Returns the top task. */
public ActivityManager.RunningTaskInfo getTopMostTask() {
List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1);
if (tasks != null && !tasks.isEmpty()) {
return tasks.get(0);
}
return null;
}
public ActivityManager.RunningTaskInfo getTopMostTask() {
List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1);
if (tasks != null && !tasks.isEmpty()) {
return tasks.get(0);
}
return null;
}
/** Returns a list of the running tasks */
private List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
if (mAm == null) return null;
return mAm.getRunningTasks(numTasks); ->最终call到ActivityManager里面的getRunningTasks
}
private List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
if (mAm == null) return null;
return mAm.getRunningTasks(numTasks); ->最终call到ActivityManager里面的getRunningTasks
}
2)判定当前的task是否是RecentActivity
if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome))
/** Returns whether the recents is currently running */
public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
MutableBoolean isHomeTopMost) {
if (topTask != null) {
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
topActivity.getClassName().equals(Recents.sRecentsActivity)) {
if (isHomeTopMost != null) {
isHomeTopMost.value = false;
}
return true;
}
if (isHomeTopMost != null) {
isHomeTopMost.value = isInHomeStack(topTask.id);
}
}
return false;
}
public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
MutableBoolean isHomeTopMost) {
if (topTask != null) {
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
topActivity.getClassName().equals(Recents.sRecentsActivity)) {
if (isHomeTopMost != null) {
isHomeTopMost.value = false;
}
return true;
}
if (isHomeTopMost != null) {
isHomeTopMost.value = isInHomeStack(topTask.id);
}
}
return false;
}
其中,
Recents.sRecentsPackage = "com.android.systemui"
Recents.sRecentsActivity = "com.android.systemui.recents.RecentsActivity"
如果当前正在运行RecentActivity即Recent Panel正在显示,不去执行preload recent apps行为(因为没有意义,这个时候是去关闭Recent Panel)
3)获取raw recent tasks
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
这里获取的只是raw tasks数据,并不是真正在UI上显示的Tasks。
相关实现:
RecentsTaskLoadPlan.java
- /**
- * An optimization to preload the raw list of tasks.
- */
- public synchronized void preloadRawTasks(boolean isTopTaskHome) {
- mRawTasks = mSystemServicesProxy.getRecentTasks(mConfig.maxNumTasksToLoad,
- UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
- Collections.reverse(mRawTasks);
- if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
- }
mConfig.maxNumTasksToLoad在lowRamDevice是50,其他为100(lowRamDevice是指内存等于或低于512M低端机)
RecentsConfiguration.java
// Loading
maxNumTasksToLoad = ActivityManager.getMaxRecentTasksStatic();
maxNumTasksToLoad = ActivityManager.getMaxRecentTasksStatic();
ActivityManager.java
/**
* Return the maximum number of recents entries that we will maintain and show.
* @hide
*/
static public int getMaxRecentTasksStatic() {
if (gMaxRecentTasks < 0) {
return gMaxRecentTasks = isLowRamDeviceStatic() ? 50 : 100;
}
return gMaxRecentTasks;
}
* Return the maximum number of recents entries that we will maintain and show.
* @hide
*/
static public int getMaxRecentTasksStatic() {
if (gMaxRecentTasks < 0) {
return gMaxRecentTasks = isLowRamDeviceStatic() ? 50 : 100;
}
return gMaxRecentTasks;
}
获取到的Recent Tasks存放在mRamTasks全局变量。
SystemServicesProxy.java中获取当前的Recent Tasks,主要实现:
/** Returns a list of the recents tasks */
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
boolean isTopTaskHome) {
if (mAm == null) return null;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
boolean isTopTaskHome) {
if (mAm == null) return null;
...
List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
ActivityManager.RECENT_INCLUDE_PROFILES |
ActivityManager.RECENT_WITH_EXCLUDED, userId);
// Break early if we can't get a valid set of tasks
if (tasks == null) {
return new ArrayList<>();
}
ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
ActivityManager.RECENT_INCLUDE_PROFILES |
ActivityManager.RECENT_WITH_EXCLUDED, userId);
// Break early if we can't get a valid set of tasks
if (tasks == null) {
return new ArrayList<>();
}
4)raw recent tasks转化为显示的recent Tasks
第三步获取的raw recent tasks仅仅是原始数据,需要配合UI显示出来。
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
RecentsTaskLoadPlan.java
/**
* 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.
*/
synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
* 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.
*/
synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
....
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, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
ActivityInfoHandle infoHandle;
boolean hadCachedActivityInfo = false;
if (mActivityInfoCache.containsKey(cnKey)) {
infoHandle = mActivityInfoCache.get(cnKey);
hadCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
}
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
mSystemServicesProxy, infoHandle);
String contentDescription = loader.getAndUpdateContentDescription(taskKey,
activityLabel, mSystemServicesProxy, res);
Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
mSystemServicesProxy, res, infoHandle, false);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
// Update the activity info cache
if (!hadCachedActivityInfo && infoHandle.info != null) {
mActivityInfoCache.put(cnKey, infoHandle);
}
Bitmap icon = t.taskDescription != null
? t.taskDescription.getInMemoryIcon()
: null;
String iconFilename = t.taskDescription != null
? t.taskDescription.getIconFilename()
: null;
// Add the task to the stack
Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
icon, iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
if (!mConfig.multiStackEnabled ||
Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
int firstStackId = 0;
ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
if (stackTasks == null) {
stackTasks = new ArrayList<>();
stacksTasks.put(firstStackId, stackTasks);
}
stackTasks.add(task);
} else {
ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
if (stackTasks == null) {
stackTasks = new ArrayList<>();
stacksTasks.put(t.stackId, stackTasks);
}
stackTasks.add(task);
}
}
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, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
ActivityInfoHandle infoHandle;
boolean hadCachedActivityInfo = false;
if (mActivityInfoCache.containsKey(cnKey)) {
infoHandle = mActivityInfoCache.get(cnKey);
hadCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
}
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
mSystemServicesProxy, infoHandle);
String contentDescription = loader.getAndUpdateContentDescription(taskKey,
activityLabel, mSystemServicesProxy, res);
Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
mSystemServicesProxy, res, infoHandle, false);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
// Update the activity info cache
if (!hadCachedActivityInfo && infoHandle.info != null) {
mActivityInfoCache.put(cnKey, infoHandle);
}
Bitmap icon = t.taskDescription != null
? t.taskDescription.getInMemoryIcon()
: null;
String iconFilename = t.taskDescription != null
? t.taskDescription.getIconFilename()
: null;
// Add the task to the stack
Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
icon, iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
if (!mConfig.multiStackEnabled ||
Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
int firstStackId = 0;
ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
if (stackTasks == null) {
stackTasks = new ArrayList<>();
stacksTasks.put(firstStackId, stackTasks);
}
stackTasks.add(task);
} else {
ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
if (stackTasks == null) {
stackTasks = new ArrayList<>();
stacksTasks.put(t.stackId, stackTasks);
}
stackTasks.add(task);
}
}
获取RawTasks后,进行各种初始化,初始化的目的是为了配合UI显示。
Recent Tasks Preload Follow时序图如下:
3.RecentActivity显示和隐藏流程
在第一部分已经讲到,PhoneWindowManager在接收到KEYCODE_APP_SWITCH KeyEvent后,在key up时会进行开关Recent Panel显示的逻辑处理。
整理为时序图如下:
即最终会call到SystemUI的Recents.java中的toggleRecents()。
下面分析一下这个函数的处理逻辑(注意在第二部分我们分析到Raw Recent Tasks的数据,怎么给到RecentActivity使用的)
1)RecentActivity显示或消失判定依据
RecentActivity.java
- /** Toggles the recents activity */
- void toggleRecentsActivity() {
- // If the user has toggled it too quickly, then just eat up the event here (it's better than
- // showing a janky screenshot).
- // NOTE: Ideally, the screenshot mechanism would take the window transform into account
- if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
- return;
- }
- // If Recents is the front most activity, then we should just communicate with it directly
- // to launch the first task or dismiss itself
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- MutableBoolean isTopTaskHome = new MutableBoolean(true);
- if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
- // Notify recents to toggle itself
- Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- mLastToggleTime = SystemClock.elapsedRealtime();
- return;
- } else {
- // Otherwise, start the recents activity
- startRecentsActivity(topTask, isTopTaskHome.value);
- }
- }
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)
->这段code第二部分有分析过实现过程
// Notify recents to toggle itself
Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
mLastToggleTime = SystemClock.elapsedRealtime();
Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
mLastToggleTime = SystemClock.elapsedRealtime();
->这里会发送一个广播,通知RecentActivity
RecentActivity.java
注册上面提到的广播:
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
IntentFilter filter = new IntentFilter();
filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
广播接收器mServiceBroadcastReceiver处理逻辑:
else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
如果当前不是RecentActivity,那么走显示RecentActivity逻辑。
// Otherwise, start the recents activity
startRecentsActivity(topTask, isTopTaskHome.value);
startRecentsActivity(topTask, isTopTaskHome.value);
2)启动RecentActivity
startRecentsActivity函数会根据两种不同的source,传入不同的参数,这里说的source只是从Home和非Home启动RecentActivity
从Home启动:
// Determine whether we are coming from a search owned home activity
boolean fromSearchHome = (homeActivityPackage != null) &&
homeActivityPackage.equals(searchWidgetPackage);
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
startAlternateRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, false /* fromThumbnail */, stackVr);
boolean fromSearchHome = (homeActivityPackage != null) &&
homeActivityPackage.equals(searchWidgetPackage);
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
startAlternateRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, false /* fromThumbnail */, stackVr);
从非Home启动:
// Otherwise we do the normal fade from an unknown source
ActivityOptions opts = getUnknownTransitionActivityOptions();
startAlternateRecentsActivity(topTask, opts, true /* fromHome */,false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
ActivityOptions opts = getUnknownTransitionActivityOptions();
startAlternateRecentsActivity(topTask, opts, true /* fromHome */,false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
最后在startAlternateRecentsActivity函数中启动RecentActivity:
Intent intent = new Intent(sToggleRecentsAction);
intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
3)在RecentsActivity 中刷新Recent Tasks
在RecentsActivity的onStart函数中,会call updateRecentsTasks()函数刷新Tasks数据。
RecentsActivity.java
- /** Updates the set of recent tasks */
- void updateRecentsTasks() {
- // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
- // reconstructing the task stack
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
- if (plan == null) {
- plan = loader.createLoadPlan(this);
- }
- // Start loading tasks according to the load plan
- if (!plan.hasTasks()) {
- loader.preloadTasks(plan, mConfig.launchedFromHome);
- }
- RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
- loadOpts.runningTaskId = mConfig.launchedToTaskId;
- loadOpts.numVisibleTasks = mConfig.launchedNumVisibleTasks;
- loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
- loader.loadTasks(this, plan, loadOpts);
- ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
- mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
- if (!mConfig.launchedWithNoRecentTasks) {
- mRecentsView.setTaskStacks(stacks);
- }
RecentsTaskLoadPlan.java
- /**
- * Returns all TaskStacks from the preloaded list of recent tasks.
- */
- public ArrayList<TaskStack> getAllTaskStacks() {
- ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
- int stackCount = mStacks.size();
- for (int i = 0; i < stackCount; i++) {
- stacks.add(mStacks.valueAt(i));
- }
- // Ensure that we have at least one stack
- if (stacks.isEmpty()) {
- stacks.add(new TaskStack());
- }
- return stacks;
- }
这里的mStacks即我们第二步封装的数据。