Android SystemUI系列:
1.Android SystemUI之启动流程(一)
2.Android SystemUI之StatusBar,状态栏(二)
3.Android SystemUI之下拉菜单,通知栏,快捷面板(三)
4.Android SystemUI之NavigationBar,导航栏(四)
5.Android SystemUI之Recent,近期列表(五)
一、近期任务列表启动
1.近期列表View结构图
2.NavigationBarFragment.onRecentsClick和NavigationBarFragment.onRecentsTouch
结合上个博客导航栏,我们可以知道近期列表的启动入口是onRecentsTouch和onRecentsClick
onRecentsTouch
private boolean onRecentsTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_DOWN) {
mCommandQueue.preloadRecentApps();//启动近期列表
} else if (action == MotionEvent.ACTION_CANCEL) {
mCommandQueue.cancelPreloadRecentApps();
} else if (action == MotionEvent.ACTION_UP) {
if (!v.isPressed()) {
mCommandQueue.cancelPreloadRecentApps();
}
}
return false;
}
public void preloadRecentApps() {
synchronized (mLock) {
mHandler.removeMessages(MSG_PRELOAD_RECENT_APPS);
mHandler.obtainMessage(MSG_PRELOAD_RECENT_APPS, 0, 0, null).sendToTarget();
}
}
case MSG_PRELOAD_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).preloadRecentApps();
}
onRecentsClick
private void onRecentsClick(View v) {
if (LatencyTracker.isEnabled(getContext())) {
LatencyTracker.getInstance(getContext()).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
}
mStatusBar.awakenDreams();
mCommandQueue.toggleRecentApps();
}
public void toggleRecentApps() {
synchronized (mLock) {
mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
Message msg = mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null);
msg.setAsynchronous(true);
msg.sendToTarget();
}
}
case MSG_TOGGLE_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).toggleRecentApps();
}
Recents这个类继承CommandQueue.Callbacks,所以上述函数
mCallbacks.get(i).preloadRecentApps()会调用Recents.preloadRecentApps
mCallbacks.get(i).toggleRecentApps()会调用Recents.toggleRecentApps
Recents.preloadRecentApps
public void preloadRecentApps() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
if (mOverviewProxyService.getProxy() != null) {
// TODO: Proxy to Launcher
return;
}
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.preloadRecents();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
}
if (mOverviewProxyService.getProxy() != null) {
// TODO: Proxy to Launcher
return;
}
mOverviewProxyService.getProxy() 是什么时候不为空呢?远程服务IOverviewProxy存在的时候,那么这个远程服务在哪里实现呢?
OverviewProxyService里面有这段代码:
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
<string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>
所以在9.0以后你会发现近期列表的实现在Launcher3里面,不过本博文还是研究systemui的近期列表。com.android.systemui/.recents.RecentsActivity
RecentsImpl.preloadRecents
public void preloadRecents() {
//ScreenPin正在使用就停止
if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
return;
}
// Skip preloading recents when keyguard is showing
//锁屏也停止
final StatusBar statusBar = getStatusBar();
if (statusBar != null && statusBar.isKeyguardShowing()) {
return;
}
// 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. Post this to ensure that we
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
SystemServicesProxy ssp = Recents.getSystemServices();
if (!ssp.isRecentsActivityVisible(null)) {
//从ams上面获取运行的任务栈的信息
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) {
return;
}
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken
// for the pausing activity)
preloadIcon(runningTask.id);
// At this point, we don't know anything about the stack state. So only
// calculate the dimensions of the thumbnail that we need for the transition
// into Recents, but do not draw it until we construct the activity options when
// we start Recents
updateHeaderBarLayout(stack, null /* window rect override*/);
}
}
});
}
上述代码涉及到RecentsTaskLoader,RecentsTaskLoadPlan,TaskStack,这三个类会伴随近期列表的创建和消亡,可见其重要。
RecentsTaskLoader和RecentsTaskLoadPlan主要加载最近任务相关数据,比如图标,名称,截图等
TaskStack这个管理所有信息加载完后保持在Task的栈。
loader.preloadTasks这个会加载对应的资源文件
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
}
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
int currentUserId) {
try {
Trace.beginSection("preloadPlan");
plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
}
public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
int currentUserId) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
//获取近期列表task
if (mRawTasks == null) {
mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
ActivityManager.getMaxRecentTasksStatic(), currentUserId);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
}
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
final ComponentName sourceComponent = t.origActivity != null
// Activity alias if there is one
? t.origActivity
// The real activity if there is no alias (or the target if there is one)
: t.realActivity;
final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
sourceComponent, t.userId, t.lastActiveTime);
boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
boolean isStackTask = !isFreeformTask;
boolean isLaunchTarget = taskKey.id == runningTaskId;
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
if (info == null) {
continue;
}
// Load the title, icon, and color
//taskDescription 这个类里面保持了mLabel,Bitmap mIcon等信息,getAndUpdateActivityTitle返回label信息
String title = opts.loadTitles
? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
: "";
String titleDescription = opts.loadTitles
? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
: "";
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, 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);
// TODO: Refactor to not do this every preload
if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
}
boolean isLocked = mTmpLockedUsers.get(t.userId);
// Add the task to the stack
Task task = new Task(taskKey, icon,
thumbnail, title, titleDescription, activityColor, backgroundColor,
isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
t.taskDescription, t.resizeMode, t.topActivity, isLocked);
allTasks.add(task);
}
// Initialize the stacks
mStack = new TaskStack();
mStack.setTasks(allTasks, false /* notifyStackChanges */);
}
public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
// Compute a has set for each of the tasks
ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
ArrayList<Task> addedTasks = new ArrayList<>();
ArrayList<Task> removedTasks = new ArrayList<>();
ArrayList<Task> allTasks = new ArrayList<>();
Log.i("Systemui-xiao","TaskStack setTasks tasks:"+tasks+",mRawTaskList:"+mRawTaskList);
// Disable notifications if there are no callbacks
if (mCb == null) {
notifyStackChanges = false;
}
// Remove any tasks that no longer exist
int taskCount = mRawTaskList.size();
for (int i = taskCount - 1; i >= 0; i--) {
Task task = mRawTaskList.get(i);
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
removedTasks.add(task);
}
}
}
// Add any new tasks
taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task newTask = tasks.get(i);
Task currentTask = currentTasksMap.get(newTask.key);
if (currentTask == null && notifyStackChanges) {
addedTasks.add(newTask);
} else if (currentTask != null) {
// The current task has bound callbacks, so just copy the data from the new task
// state and add it back into the list
currentTask.copyFrom(newTask);
newTask = currentTask;
}
allTasks.add(newTask);
}
// Sort all the tasks to ensure they are ordered correctly
for (int i = allTasks.size() - 1; i >= 0; i--) {
allTasks.get(i).temporarySortIndexInStack = i;
}
mStackTaskList.set(allTasks);
mRawTaskList.clear();
mRawTaskList.addAll(allTasks);
// Only callback for the removed tasks after the stack has updated
int removedTaskCount = removedTasks.size();
Task newFrontMostTask = getFrontMostTask();
for (int i = 0; i < removedTaskCount; i++) {
mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
AnimationProps.IMMEDIATE, false /* fromDockGesture */,
true /* dismissRecentsIfAllRemoved */);
}
// Only callback for the newly added tasks after this stack has been updated
int addedTaskCount = addedTasks.size();
for (int i = 0; i < addedTaskCount; i++) {
mCb.onStackTaskAdded(this, addedTasks.get(i));
}
// Notify that the task stack has been updated
if (notifyStackChanges) {
mCb.onStackTasksUpdated(this);
}
}
mCb.onStackTaskAdded(this, addedTasks.get(i));
mCb.onStackTasksUpdated(this);
会回调TaskStackView里面的onStackTaskAdded和onStackTasksUpdated 。由于现在TaskStackView还没加载起来,所以现在是暂时不会调用,不过后续等TaskStackView加载起来,再调用setTasks就能起到数据变化,近期任务列表也会相应的添加对应的近期任务。
preloadPlan这个函数主要就是加载资源文件。
Recents.toggleRecentApps
public void toggleRecentApps() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
// If connected to launcher service, let it handle the toggle logic
IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
if (overviewProxy != null) {
final Runnable toggleRecents = () -> {
try {
if (mOverviewProxyService.getProxy() != null) {
mOverviewProxyService.getProxy().onOverviewToggle();
}
} catch (RemoteException e) {
Log.e(TAG, "Cannot send toggle recents through proxy service.", e);
}
};
// Preload only if device for current user is unlocked
final StatusBar statusBar = getComponent(StatusBar.class);
if (statusBar != null && statusBar.isKeyguardShowing()) {
statusBar.executeRunnableDismissingKeyguard(() -> {
// Flush trustmanager before checking device locked per user
mTrustManager.reportKeyguardShowingChanged();
mHandler.post(toggleRecents);
}, null, true /* dismissShade */, false /* afterKeyguardGone */,
true /* deferred */);
} else {
toggleRecents.run();
}
return;
}
int growTarget = getComponent(Divider.class).getView().growsRecents();
int currentUser = sSystemServicesProxy.getCurrentUser();
mImpl.toggleRecents(growTarget);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.toggleRecents(growTarget);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
}
RecentsImpl.toggleRecents
public void toggleRecents(int growTarget) {
//判断ScreenPinn是否允许中,如果运行中就不启动近期任务列表
if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
return;
}
// Skip this toggle if we are already waiting to trigger recents via alt-tab
if (mFastAltTabTrigger.isDozing()) {
return;
}
if (mWaitingForTransitionStart) {
mToggleFollowingTransitionStart = true;
return;
}
mDraggingInRecents = false;
mLaunchedWhileDocking = false;
mTriggeredFromAltTab = false;
try {
MutableBoolean isHomeStackVisible = new MutableBoolean(true);
long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
SystemServicesProxy ssp = Recents.getSystemServices();
//ssp.isRecentsActivityVisible判断近期列表是否已经启动,如果启动的话就再启动它
if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!launchState.launchedWithAltTab) {
if (Recents.getConfiguration().isGridEnabled) {
// Has the user tapped quickly?
boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
if (isQuickTap) {
EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
} else {
EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
}
} else {
// Launch the next focused task
EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
}
} else {
// 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 (elapsedTime < MIN_TOGGLE_DELAY_MS) {
return;
}
EventBus.getDefault().post(new ToggleRecentsEvent());
mLastToggleTime = SystemClock.elapsedRealtime();
}
return;
} else {
// 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 (elapsedTime < MIN_TOGGLE_DELAY_MS) {
return;
}
// Otherwise, start the recents activity
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
isHomeStackVisible.value, true /* animate */, growTarget);
// Only close the other system windows if we are actually showing recents
ActivityManagerWrapper.getInstance().closeSystemWindows(
SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentsActivity", e);
}
}
protected void startRecentsActivityAndDismissKeyguardIfNeeded(
final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible,
final boolean animate, final int growTarget) {
// Preload only if device for current user is unlocked
final StatusBar statusBar = getStatusBar();
//如果是锁屏界面就延迟启动近期列表,否则就直接启动
if (statusBar != null && statusBar.isKeyguardShowing()) {
statusBar.executeRunnableDismissingKeyguard(() -> {
// Flush trustmanager before checking device locked per user when preloading
mTrustManager.reportKeyguardShowingChanged();
mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible,
animate, growTarget));
}, null, true /* dismissShade */, false /* afterKeyguardGone */,
true /* deferred */);
} else {
startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget);
}
}
private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
boolean isHomeStackVisible, boolean animate, int growTarget) {
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
? runningTask.id
: -1;
// In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
// should always preload the tasks now. If we are dragging in recents, reload them as
// the stacks might have changed.
if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
// Create a new load plan if preloadRecents() was never triggered
sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
}
if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
loader.preloadTasks(sInstanceLoadPlan, runningTaskId);//预加载近期任务栈的资源文件,比如截屏图片,标题等
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
hasRecentTasks;
// Update the launch state that we need in updateHeaderBarLayout()
launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
launchState.launchedFromPipApp = false;
launchState.launchedWithNextPipApp =
stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
launchState.launchedViaDockGesture = mLaunchedWhileDocking;
launchState.launchedViaDragGesture = mDraggingInRecents;
launchState.launchedToTaskId = runningTaskId;
launchState.launchedWithAltTab = mTriggeredFromAltTab;
// Disable toggling of recents between starting the activity and it is visible and the app
// has started its transition into recents.
setWaitingForTransitionStart(useThumbnailTransition);
// Preload the icon (this will be a null-op if we have preloaded the icon already in
// preloadRecents())
if(StatusBar.SYSTEMUI_RENCENT_DEBUG)Log.i(StatusBar.TAG_XIAO,"RecentsImpl startRecentsActivity1 preloadIcon runningTaskId:"+runningTaskId);
preloadIcon(runningTaskId);//预加载icon信息
// Update the header bar if necessary
Rect windowOverrideRect = getWindowRectOverride(growTarget);
updateHeaderBarLayout(stack, windowOverrideRect);
// Prepare the dummy stack for the transition
TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
// Update the remaining launch state
launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
if (!animate) {
startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1),
null /* future */);
return;
}
Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
if (useThumbnailTransition) {
// Try starting with a thumbnail transition
pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
} else {
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition
pair = new Pair<>(hasRecentTasks
? getHomeTransitionActivityOptions()
: getUnknownTransitionActivityOptions(), null);
}
startRecentsActivity(pair.first, pair.second);//启动RecentsActivity
mLastToggleTime = SystemClock.elapsedRealtime();
}
private void startRecentsActivity(ActivityOptions opts,
final AppTransitionAnimationSpecsFuture future) {
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
hideMenuEvent.addPostAnimationCallback(() -> {
Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
EventBus.getDefault().send(new RecentsActivityStartingEvent());
if (future != null) {
future.composeSpecsSynchronous();
}
});
EventBus.getDefault().send(hideMenuEvent);
// Once we have launched the activity, reset the dummy stack view tasks so we don't hold
// onto references to the same tasks consumed by the activity
if(StatusBar.SYSTEMUI_RENCENT_DEBUG)Log.i(StatusBar.TAG_XIAO,"RecentsImpl startRecentsActivity2 setTasks");
mDummyStackView.setTasks(mEmptyTaskStack, false /* notifyStackChanges */);
}
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
通过一系列的准备我们找到启动RecentsActivity的入口了
二、近期任务列表数据加载和创建
1.RecentsActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFinishedOnStartup = false;
// In the case that the activity starts up before the Recents component has initialized
// (usually when debugging/pushing the SysUI apk), just finish this activity.
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp == null) {
mFinishedOnStartup = true;
finish();
return;
}
// Register this activity with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the package monitor
mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
true /* externalStorage */);
// Select theme based on wallpaper colors
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
mUsingDarkText = mColorExtractor.getColors(ColorExtractor.TYPE_DARK,
WallpaperManager.FLAG_SYSTEM, true).supportsDarkText();
setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light
: R.style.RecentsTheme_Wallpaper);
// Set the Recents layout
setContentView(R.layout.recents);
takeKeyEvents(true);
mRecentsView = findViewById(R.id.recents_view);
mScrimViews = new SystemBarScrimViews(this);
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
if (Recents.getConfiguration().isLowRamDevice) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
// Set the window background
mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
// Create the home intent runnable
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_SWITCHED);
registerReceiver(mSystemBroadcastReceiver, filter);
getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
}
我们知道加载的layout :setContentView(R.layout.recents);
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Recents View -->
<com.android.systemui.recents.views.RecentsView
android:id="@+id/recents_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.android.systemui.recents.views.RecentsView>
<!-- Incompatible task overlay -->
<ViewStub android:id="@+id/incompatible_app_overlay_stub"
android:inflatedId="@+id/incompatible_app_overlay"
android:layout="@layout/recents_incompatible_app_overlay"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_gravity="center_horizontal|top" />
<!-- Nav Bar Scrim View -->
<ImageView
android:id="@+id/nav_bar_scrim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:scaleType="fitXY"
android:src="@drawable/recents_lower_gradient" />
</FrameLayout>
protected void onStart() {
super.onStart();
// Reload the stack view whenever we are made visible again
reloadStackView();
// Notify that recents is now visible
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY);
// Getting system scrim colors ignoring wallpaper visibility since it should never be grey.
ColorExtractor.GradientColors systemColors = mColorExtractor.getColors(
ColorExtractor.TYPE_DARK, WallpaperManager.FLAG_SYSTEM, true);
// We don't want to interpolate colors because we're defining the initial state.
// Gradient should be set/ready when you open "Recents".
mRecentsView.setScrimColors(systemColors, false);
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
// If Recents was restarted, then it should complete the enter animation with partially
// reset launch state with dock, app and home set to false
Object isRelaunching = getLastNonConfigurationInstance();
if (isRelaunching != null && isRelaunching instanceof Boolean && (boolean) isRelaunching) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
launchState.launchedViaDockGesture = false;
launchState.launchedFromApp = false;
launchState.launchedFromHome = false;
onEnterAnimationComplete();
}
mRecentsStartRequested = false;
}
private void reloadStackView() {
// If the Recents component has preloaded a load plan, then use that to prevent
// reconstructing the task stack
//加载资源
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
if (loadPlan == null) {
loadPlan = new RecentsTaskLoadPlan(this);
}
// Start loading tasks according to the load plan
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
mRecentsView.onReload(stack, mIsVisible);
// Update the nav bar scrim, but defer the animation until the enter-window event
boolean animateNavBarScrim = !launchState.launchedViaDockGesture;
mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null);
// If this is a new instance relaunched by AM, without going through the normal mechanisms,
// then we have to manually trigger the enter animation state
boolean wasLaunchedByAm = !launchState.launchedFromHome &&
!launchState.launchedFromApp;
if (wasLaunchedByAm) {
EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
}
// Keep track of whether we launched from the nav bar button or via alt-tab
if (launchState.launchedWithAltTab) {
MetricsLogger.count(this, "overview_trigger_alttab", 1);
} else {
MetricsLogger.count(this, "overview_trigger_nav_btn", 1);
}
// Keep track of whether we launched from an app or from home
if (launchState.launchedFromApp) {
Task launchTarget = stack.getLaunchTarget();
int launchTaskIndexInStack = launchTarget != null
? stack.indexOfTask(launchTarget)
: 0;
MetricsLogger.count(this, "overview_source_app", 1);
// If from an app, track the stack index of the app in the stack (for affiliated tasks)
MetricsLogger.histogram(this, "overview_source_app_index", launchTaskIndexInStack);
} else {
MetricsLogger.count(this, "overview_source_home", 1);
}
// Keep track of the total stack task count
int taskCount = mRecentsView.getStack().getTaskCount();
MetricsLogger.histogram(this, "overview_task_count", taskCount);
// After we have resumed, set the visible state until the next onStop() call
mIsVisible = true;
}
1.加载资源,我们知道 loader.preloadTasks这个函数是加载资源的。
2.mRecentsView.onReload(stack, mIsVisible);
public void onReload(TaskStack stack, boolean isResumingFromVisible) {
final RecentsConfiguration config = Recents.getConfiguration();
final RecentsActivityLaunchState launchState = config.getLaunchState();
final boolean isTaskStackEmpty = stack.getTaskCount() == 0;
if (mTaskStackView == null) {
isResumingFromVisible = false;
mTaskStackView = new TaskStackView(getContext());
mTaskStackView.setSystemInsets(mSystemInsets);
addView(mTaskStackView);//把TaskStackView添加到RecentView结构树中
}
// Reset the state
mAwaitingFirstLayout = !isResumingFromVisible;
// Update the stack
mTaskStackView.onReload(isResumingFromVisible);
updateStack(stack, true /* setStackViewTasks */);
updateBusyness();
if (isResumingFromVisible) {
// If we are already visible, then restore the background scrim
animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
} else {
// If we are already occluded by the app, then set the final background scrim alpha now.
// Otherwise, defer until the enter animation completes to animate the scrim alpha with
// the tasks for the home animation.
if (launchState.launchedViaDockGesture || launchState.launchedFromApp
|| isTaskStackEmpty) {
mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255));
} else {
mBackgroundScrim.setAlpha(0);
}
mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
}
}
上述比较关键的代码是把 addView(mTaskStackView);//把TaskStackView添加到RecentView结构树中
void onReload(boolean isResumingFromVisible) {
if (!isResumingFromVisible) {
// Reset the focused task
resetFocusedTask(getFocusedTask());
}
// Reset the state of each of the task views
List<TaskView> taskViews = new ArrayList<>();
Log.i("Systemui-xiao","TaskStackView onReload getTaskViews size:"+(getTaskViews()==null?getTaskViews():getTaskViews().size())+",mViewPool.getViews size:"+(mViewPool.getViews()==null?mViewPool.getViews():mViewPool.getViews().size()));
taskViews.addAll(getTaskViews());
taskViews.addAll(mViewPool.getViews());
for (int i = taskViews.size() - 1; i >= 0; i--) {
taskViews.get(i).onReload(isResumingFromVisible);
}
// Reset the stack state
readSystemFlags();
mTaskViewsClipDirty = true;
mUIDozeTrigger.stopDozing();
if (!isResumingFromVisible) {
mStackScroller.reset();
mStableLayoutAlgorithm.reset();
mLayoutAlgorithm.reset();
mLastScrollPPercent = -1;
}
// Since we always animate to the same place in (the initial state), always reset the stack
// to the initial state when resuming
mStackReloaded = true;
mFinishedLayoutAfterStackReload = false;
mLaunchNextAfterFirstMeasure = false;
mInitialState = INITIAL_STATE_UPDATE_ALL;
requestLayout();
}
数据加载完,父布局TaskStackView也添加完了,子布局TaskView(近期任务)什么时候才会加载呢?
2.TaskStackView .onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mInMeasureLayout = true;
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
Log.i("Systemui-xiao","TaskStackView onMeasure");
// Update the stable stack bounds, but only update the current stack bounds if the stable
// bounds have changed. This is because we may get spurious measures while dragging where
// our current stack bounds reflect the target drop region.
mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height),
mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left,
mLayoutAlgorithm.mSystemInsets.right, mTmpRect);
if (!mTmpRect.equals(mStableStackBounds)) {
mStableStackBounds.set(mTmpRect);
mStackBounds.set(mTmpRect);
mStableWindowRect.set(0, 0, width, height);
mWindowRect.set(0, 0, width, height);
}
// Compute the rects in the stack algorithm
mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(false /* boundScroll */);
// If this is the first layout, then scroll to the front of the stack, then update the
// TaskViews with the stack so that we can lay them out
boolean resetToInitialState = (width != mLastWidth || height != mLastHeight)
&& mResetToInitialStateWhenResized;
if (!mFinishedLayoutAfterStackReload || mInitialState != INITIAL_STATE_UPDATE_NONE
|| resetToInitialState) {
if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) {
updateToInitialState();//最终会执行onStackScrollChanged
mResetToInitialStateWhenResized = false;
}
if (mFinishedLayoutAfterStackReload) {
mInitialState = INITIAL_STATE_UPDATE_NONE;
}
}
// If we got the launch-next event before the first layout pass, then re-send it after the
// initial state has been updated
if (mLaunchNextAfterFirstMeasure) {
mLaunchNextAfterFirstMeasure = false;
EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
}
// Rebind all the views, including the ignore ones
bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */);
// Measure each of the TaskViews
mTmpTaskViews.clear();
mTmpTaskViews.addAll(getTaskViews());
mTmpTaskViews.addAll(mViewPool.getViews());
int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
measureTaskView(mTmpTaskViews.get(i));
}
if (mTaskViewFocusFrame != null) {
mTaskViewFocusFrame.measure();
}
setMeasuredDimension(width, height);
mLastWidth = width;
mLastHeight = height;
mInMeasureLayout = false;
}
void bindVisibleTaskViews(float targetStackScroll) {
bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */);
}
void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
ignoreTaskOverrides);
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
List<TaskView> taskViews = getTaskViews();
int lastFocusedTaskIndex = -1;
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
// Skip ignored tasks
if (mIgnoreTasks.contains(task.key)) {
continue;
}
// It is possible for the set of lingering TaskViews to differ from the stack if the
// stack was updated before the relayout. If the task view is no longer in the stack,
// then just return it back to the view pool.
int taskIndex = mStack.indexOfTask(task);
TaskViewTransform transform = null;
if (taskIndex != -1) {
transform = mCurrentTaskTransforms.get(taskIndex);
}
if (transform != null && transform.visible) {
mTmpTaskViewMap.put(task.key, tv);
} else {
if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
lastFocusedTaskIndex = taskIndex;
resetFocusedTask(task);
}
mViewPool.returnViewToPool(tv);
}
}
// Pick up all the newly visible children
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = mCurrentTaskTransforms.get(i);
// Skip ignored tasks
if (mIgnoreTasks.contains(task.key)) {
continue;
}
// Skip the invisible stack tasks
if (!transform.visible) {
continue;
}
TaskView tv = mTmpTaskViewMap.get(task.key);
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);//创建taskview
if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
AnimationProps.IMMEDIATE);
} else {
updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
AnimationProps.IMMEDIATE);
}
} else {
// Reattach it in the right z order
final int taskIndex = mStack.indexOfTask(task);
final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
if (insertIndex != getTaskViews().indexOf(tv)){
detachViewFromParent(tv);
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
Log.i("Systemui-xiao","TaskStackView bindVisibleTaskViews updateTaskViewsList");
updateTaskViewsList();
}
}
}
updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
? visibleTaskRange[1]
: visibleTaskRange[0];
setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */,
true /* requestViewFocus */);
TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
if (focusedTaskView != null) {
focusedTaskView.requestAccessibilityFocus();
}
}
}
V pickUpViewFromPool(T preferredData, T prepareData) {
V v = null;
boolean isNewView = false;
if (mPool.isEmpty()) {
v = mViewCreator.createView(mContext);
isNewView = true;
} else {
// Try and find a preferred view
Iterator<V> iter = mPool.iterator();
while (iter.hasNext()) {
V vpv = iter.next();
if (mViewCreator.hasPreferredData(vpv, preferredData)) {
v = vpv;
iter.remove();
break;
}
}
// Otherwise, just grab the first view
if (v == null) {
v = mPool.pop();
}
}
mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView);
return v;
}
public TaskView createView(Context context) {
if (Recents.getConfiguration().isGridEnabled) {
return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false);
} else {
return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
}
}
此次近期任务界面被创建成功。那我们看看recents_task_view
<com.android.systemui.recents.views.TaskView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true">
<com.android.systemui.recents.views.TaskViewThumbnail
android:id="@+id/task_view_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/recents_task_view_header" />
<!-- TODO: Move this into a view stub -->
<include layout="@layout/recents_task_view_lock_to_app"/>
<!-- The incompatible app toast -->
<include layout="@layout/recents_task_view_incompatible_app_toast"/>
</com.android.systemui.recents.views.TaskView>
TaskViewThumbnail这个是显示近期任务图片的View。
recents_task_view_header是图片上面部分也就是头部。
大家再结合我开头给的图片来看就能比较直观形象。
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
private final OnPreDrawListener mRecentsDrawnEventListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
EventBus.getDefault().post(new RecentsDrawnEvent());
if (LatencyTracker.isEnabled(getApplicationContext())) {
DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(
getApplicationContext()).onActionEnd(
LatencyTracker.ACTION_TOGGLE_RECENTS));
}
DejankUtils.postAfterTraversal(() -> {
Recents.getTaskLoader().startLoader(RecentsActivity.this);
Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
});
return true;
}
};
此处近期任务列表算讲解完成,有点粗糙,只讲了界面的加载流程,至于细节数据加载和显示都未涉及到,到时候有时间再详细讲解补充