android 6.0 SystemUI源碼分析(3)-Recent Panel加載顯示流程
轉載 http://blog.csdn.net/zhudaozhuan/article/details/50819499 1.Recent Panel按鍵處理流程 SystemUI有一個很重要的功能就是顯示近期使用的app,友善使用者點選使用。
手機長按HOME鍵或者點選Navigation Bar的近期工作列虛拟鍵可以顯示Recent Panel。
我這裡手頭上隻有Android TV平台,并且也便于debug,是以講講收到Switch按鍵後,Recent Panel的顯示流程。
KeyEvent.java中對于Switch按鍵的定義:
public static final int KEYCODE_APP_SWITCH = 187;
從KeyCode的定義可以看到Recent Panel的作用是the application switcher dialog。
當KeyEvent給到WindowManagerService之前,會先給到PhoneWindowManager處理(給系統一次機會,去處理按鍵消息)。 在KeyEvent出隊列時,會走到interceptKeyBeforeDispatching函數,是以對于KEYCODE_APP_SWITCH的處理,會在這裡進行。
@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;
} 在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函數。 函數代碼如下: [java] view plain copy
- 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();
public ActivityManager.RunningTaskInfo getTopMostTask() {
List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1);
if (tasks != null && !tasks.isEmpty()) {
return tasks.get(0);
}
return null;
}
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))
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 [java] view plain copy
- 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());
- }
調用SystemServicesProxy裡的getRecentTasks函數去RecentTasks。 mConfig.maxNumTasksToLoad在lowRamDevice是50,其他為100(lowRamDevice是指記憶體等于或低于512M低端機)
RecentsConfiguration.java // Loading
maxNumTasksToLoad = ActivityManager.getMaxRecentTasksStatic();
ActivityManager.java
static public int getMaxRecentTasksStatic() {
if (gMaxRecentTasks < 0) {
return gMaxRecentTasks = isLowRamDeviceStatic() ? 50 : 100;
}
return gMaxRecentTasks;
} 擷取到的Recent Tasks存放在mRamTasks全局變量。
SystemServicesProxy.java中擷取目前的Recent Tasks,主要實作:
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<>();
}
4)raw recent tasks轉化為顯示的recent Tasks 第三步擷取的raw recent tasks僅僅是原始資料,需要配合UI顯示出來。 loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
RecentsTaskLoadPlan.java
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);
}
}
擷取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 [java] view plain copy
- 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);
- }
- }
RecentActivity消失的條件:目前Task的Activity為RecentActivity,則表示目前已經是RecentActivity,那麼不重新整理資料,直接消失。 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();
->這裡會發送一個廣播,通知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);
廣播接收器mServiceBroadcastReceiver處理邏輯: else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// 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);
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 , fromSearchHome, false , stackVr); 從非Home啟動: // Otherwise we do the normal fade from an unknown source
ActivityOptions opts = getUnknownTransitionActivityOptions();
startAlternateRecentsActivity(topTask, opts, true ,false , false , 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);
}
3)在RecentsActivity 中重新整理Recent Tasks 在RecentsActivity的onStart函數中,會call updateRecentsTasks()函數重新整理Tasks資料。 RecentsActivity.java
[java] view plain copy
- 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);
- }
mRecentsView設定的stacks就是我們在第二部擷取的資料。
RecentsTaskLoadPlan.java [java] view plain copy
- 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即我們第二步封裝的資料。