AOSP Android13 Launcher3 最近任务详解
本文档详细介绍了 Launcher3 中最近任务(Recents)的核心架构、手势交互流程、缩略图更新机制以及关键类的设计。
目录
- 1. 核心类概览
- 2. 类关系图
- 3. 手势上滑到最近任务流程
- 4. 缩略图实时更新机制
- 5. 关键类详细解析
- 6. 时序图
- 7. 关键方法说明
1. 核心类概览
1.1 AbsSwipeUpHandler
文件路径: quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
核心职责:
- 处理从应用底部上滑手势的全生命周期
- 协调手势动画、状态机转换和目标计算
- 管理 Recents 动画控制器和远程动画目标
- 控制窗口动画从当前应用过渡到 Launcher
关键字段:
protected RecentsAnimationController mRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity; // 泛型,通常是 Launcher 实例
protected Q mRecentsView; // 泛型,通常是 RecentsView 实例
private MultiStateCallback mStateCallback; // 状态机回调
private AnimatorControllerWithResistance mLauncherTransitionController;
主要状态标志:
STATE_LAUNCHER_PRESENT: Launcher 已准备好STATE_GESTURE_STARTED: 手势开始STATE_GESTURE_COMPLETED: 手势完成STATE_LAUNCHER_DRAWN: Launcher 已绘制STATE_APP_CONTROLLER_RECEIVED: 收到动画控制器STATE_SCALED_CONTROLLER_RECENTS: 缩放到 Recents 状态
1.2 RecentsView
文件路径: quickstep/src/com/android/quickstep/views/RecentsView.java
核心职责:
- 管理所有最近任务的 TaskView
- 处理任务的布局、滚动和动画
- 支持网格布局和轮播布局
- 处理任务的加载、卸载和可见性管理
关键属性:
protected RemoteTargetHandle[] mRemoteTargetHandles; // 远程动画目标句柄
protected RecentsAnimationController mRecentsAnimationController;
private final ViewPool<TaskView> mTaskViewPool; // TaskView 对象池
private int mRunningTaskViewId = -1; // 正在运行的任务 ID
private int mFocusedTaskViewId = -1; // 聚焦的任务 ID
private float mContentAlpha = 1; // 内容透明度
private float mFullscreenProgress = 0; // 全屏进度
private float mTaskModalness = 0; // 任务模态化程度
重要方法:
applyLoadPlan(): 应用任务加载计划,创建 TaskViewonRecentsAnimationStart(): Recents 动画开始时的回调updateThumbnail(): 更新任务缩略图onTaskThumbnailChanged(): 缩略图变化的回调
1.3 TaskView
文件路径: quickstep/src/com/android/quickstep/views/TaskView.java
核心职责:
- 表示单个最近任务的视图
- 包含缩略图视图 (TaskThumbnailView) 和图标视图 (TaskViewIcon)
- 处理任务的点击、长按交互
- 管理任务的视觉变换(缩放、平移、透明度等)
关键组件:
protected Task mTask; // 关联的任务数据
protected TaskThumbnailView mSnapshotView; // 缩略图视图
protected TaskViewIcon mIconView; // 图标视图
protected final FullscreenDrawParams mCurrentFullscreenParams; // 全屏绘制参数
private CancellableTask mThumbnailLoadRequest; // 缩略图加载请求
private CancellableTask mIconLoadRequest; // 图标加载请求
生命周期方法:
bind(): 绑定任务数据onTaskListVisibilityChanged(): 任务可见性变化时更新缩略图和图标launchTaskAnimated(): 启动任务并播放动画
1.4 TaskViewSimulator
文件路径: quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
核心职责:
- 模拟 TaskView 和 RecentsView 的布局行为
- 在手势过程中实时计算任务窗口的变换矩阵
- 不需要实际的 TaskView 实例就能计算动画参数
核心动画属性:
public final AnimatedFloat taskPrimaryTranslation; // 主轴平移
public final AnimatedFloat taskSecondaryTranslation; // 次轴平移
public final AnimatedFloat carouselScale; // 轮播缩放
public final AnimatedFloat recentsViewScale; // RecentsView 缩放
public final AnimatedFloat fullScreenProgress; // 全屏进度
public final AnimatedFloat recentsViewScroll; // RecentsView 滚动
关键方法:
apply(): 应用当前动画参数到 TransformParamsgetCurrentMatrix(): 获取当前变换矩阵getCurrentCropRect(): 获取当前裁剪区域
1.5 RemoteAnimationTargets
文件路径: quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
核心职责:
- 封装系统提供的远程动画目标
- 按类型过滤 (apps, wallpapers, nonApps)
- 管理动画目标的生命周期和释放
关键字段:
public final RemoteAnimationTarget[] apps; // 应用窗口目标
public final RemoteAnimationTarget[] wallpapers; // 壁纸目标
public final RemoteAnimationTarget[] nonApps; // 非应用窗口(如导航栏)
public final int targetMode; // 目标模式(打开/关闭)
1.6 RecentsAnimationController
文件路径: quickstep/src/com/android/quickstep/RecentsAnimationController.java
核心职责:
- 包装系统的 RecentsAnimationControllerCompat
- 提供线程安全的动画控制接口
- 管理动画的完成和清理
关键方法:
public void finishController(boolean toRecents, ...) // 完成动画
public ThumbnailData screenshotTask(int taskId) // 截取任务屏幕截图
public void setUseLauncherSystemBarFlags(boolean use) // 设置系统栏标志
public void enableInputConsumer() // 启用输入消费者
1.7 RecentsAnimationCallbacks
文件路径: quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
核心职责:
- 实现系统 RecentsAnimationListener 接口
- 将系统回调分发给多个监听器
- 确保回调在主线程执行
回调方法:
onAnimationStart() // 动画开始
onAnimationCanceled() // 动画取消
onTasksAppeared() // 任务出现
onSwitchToScreenshot() // 切换到截图
2. 类关系图
核心类之间的关系说明:
-
AbsSwipeUpHandler 是手势处理的核心协调者
- 创建和管理 TaskViewSimulator
- 控制 RecentsAnimationController
- 使用 RemoteAnimationTargets 进行窗口动画
- 管理 RecentsView 的可见性和状态
-
RecentsView 是任务容器
- 包含多个 TaskView
- 与 RecentsAnimationController 交互
- 使用对象池管理 TaskView 实例
-
TaskView 是单个任务的表现
- 绑定 Task 数据模型
- 包含 TaskThumbnailView 和 TaskViewIcon
- 处理缩略图和图标的加载
-
动画系统
- RecentsAnimationCallbacks 接收系统回调
- 创建 RecentsAnimationController
- 包装 RemoteAnimationTargets
3. 手势上滑到最近任务流程
3.1 流程概览
3.2 详细步骤
阶段 1: 手势初始化 (0-100ms)
关键文件: AbsSwipeUpHandler.java:391-471
-
AbsSwipeUpHandler 构造
public AbsSwipeUpHandler(Context context, ...) {mActivityInterface = gestureState.getActivityInterface();mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);initStateCallbacks(); // 初始化状态机 } -
状态机初始化 (
AbsSwipeUpHandler.java:413-471)private void initStateCallbacks() {mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,this::onLauncherPresentAndGestureStarted);mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,this::initializeLauncherAnimationController);// ... 更多状态回调 } -
手势开始 (
AbsSwipeUpHandler.java:1019-1054)public void onGestureStarted(boolean isLikelyToStartNewTask) {mActivityInterface.closeOverlay();notifyGestureStarted();setIsLikelyToStartNewTask(isLikelyToStartNewTask, false);mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);mGestureStarted = true; }
阶段 2: Recents 动画启动 (100-200ms)
关键文件: AbsSwipeUpHandler.java:946-997
-
接收动画回调
@Override public void onRecentsAnimationStart(RecentsAnimationController controller,RecentsAnimationTargets targets) {// 分配动画目标mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);mRecentsAnimationController = controller;mRecentsAnimationTargets = targets;// 初始化设备配置DeviceProfile dp = orientationState.getLauncherDeviceProfile();initTransitionEndpoints(dp);// 通知状态机mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); } -
Launcher 准备 (
AbsSwipeUpHandler.java:555-622)private void onLauncherStart() {mRecentsView.updateRecentsRotation();mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState, mWasLauncherAlreadyVisible,this::onAnimatorPlaybackControllerCreated);mStateCallback.setState(STATE_LAUNCHER_DRAWN); } -
RecentsView 初始化 (
RecentsView.java:664-685)protected void notifyGestureAnimationStartToRecents() {Task[] runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();if (mRecentsView != null) {mRecentsView.onGestureAnimationStart(runningTasks, rotationHelper);} }
阶段 3: 手势过程中的实时更新
关键文件: AbsSwipeUpHandler.java:892-913
-
当前偏移更新
@UiThread @Override public void onCurrentShiftUpdated() {// 更新全屏进度float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;setIsInAllAppsRegion(mCurrentShift.value >= threshold);// 更新系统 UI 标志updateSysUiFlags(mCurrentShift.value);// 应用滚动和变换applyScrollAndTransform();// 更新 Launcher 过渡进度updateLauncherTransitionProgress(); } -
窗口变换应用 (
SwipeUpAnimationLogic.java)protected void applyScrollAndTransform() {// 计算当前窗口位置for (RemoteTargetHandle handle : mRemoteTargetHandles) {TaskViewSimulator simulator = handle.getTaskViewSimulator();simulator.apply(handle.getTransformParams());} } -
TaskViewSimulator 计算 (
TaskViewSimulator.java:421-519)public void apply(TransformParams params) {// 应用缩略图矩阵mMatrix.set(mPositionHelper.getMatrix());// 应用 TaskView 变换mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, taskPrimaryTranslation.value);// 应用轮播缩放mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);// 应用 RecentsView 缩放mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);params.applySurfaceParams(params.createSurfaceParams(this)); }
阶段 4: 手势结束和目标计算
关键文件: AbsSwipeUpHandler.java:1103-1123
-
手势结束
public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {float flingThreshold = mContext.getResources().getDimension(R.dimen.quickstep_fling_threshold_speed);boolean isFling = mGestureStarted && !mIsMotionPaused&& Math.abs(endVelocityPxPerMs) > flingThreshold;mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);handleNormalGestureEnd(endVelocityPxPerMs, isFling, velocityPxPerMs, false); } -
计算结束目标 (
AbsSwipeUpHandler.java:1224-1264)private GestureEndTarget calculateEndTarget(PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {if (isCancel) {return LAST_TASK;} else if (isFlingY) {return calculateEndTargetForFlingY(velocityPxPerMs, endVelocityPxPerMs);} else {return calculateEndTargetForNonFling(velocityPxPerMs);} } -
目标类型:
HOME: 回到桌面RECENTS: 进入最近任务界面LAST_TASK: 返回原任务NEW_TASK: 切换到新任务ALL_APPS: 打开应用抽屉
阶段 5: 目标动画执行
关键文件: AbsSwipeUpHandler.java:1469-1677
-
动画到目标进度
private void animateToProgressInternal(float start, float end, long duration,Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {if (target == HOME) {// 创建回到 Home 的动画HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(...);RectFSpringAnim[] windowAnim = createWindowAnimationToHome(start, homeAnimFactory);windowAnim[0].start(mContext, dp, velocityPxPerMs);} else {// 创建到 Recents 或其他目标的动画ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);windowAnim.setDuration(duration).setInterpolator(interpolator);windowAnim.start();} } -
RecentsView 准备 (
RecentsView.java)public void onPrepareGestureEndAnimation(AnimatorSet animatorSet,GestureEndTarget endTarget, TaskViewSimulator[] simulators) {// 根据目标类型准备动画if (endTarget == RECENTS) {// 快照到最近页面snapToPage(getDestinationPage(), duration);} }
阶段 6: 动画完成和清理
关键文件: AbsSwipeUpHandler.java:1148-1204
-
目标到达处理
private void onSettledOnEndTarget() {final GestureEndTarget endTarget = mGestureState.getEndTarget();switch (endTarget) {case HOME:mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();break;case RECENTS:mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT);break;case NEW_TASK:mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);break;case LAST_TASK:mStateCallback.setState(STATE_RESUME_LAST_TASK);break;} } -
完成 Recents 动画
private void finishCurrentTransitionToRecents() {if (mRecentsAnimationController != null) {mRecentsAnimationController.finish(true /* toRecents */, () -> {// 动画完成回调});} }
4. 缩略图实时更新机制
4.1 缩略图更新流程
4.2 缩略图加载时机
关键文件: TaskView.java:1045-1098
可见时加载
public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {if (mTask == null) {return;}cancelPendingLoadTasks();if (visible) {RecentsModel model = RecentsModel.INSTANCE.get(getContext());TaskThumbnailCache thumbnailCache = model.getThumbnailCache();if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {// 在后台线程加载高分辨率缩略图mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask, thumbnail -> {// 回到主线程更新 UImSnapshotView.setThumbnail(mTask, thumbnail);});}} else {// 不可见时清除缩略图以节省内存if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {mSnapshotView.setThumbnail(null, null);mTask.thumbnail = null;}}
}
4.3 缩略图缓存机制
分层缓存策略:
-
内存缓存 (TaskThumbnailCache)
- 缓存最近使用的缩略图
- LRU 策略自动淘汰旧缩略图
-
系统缓存
- 通过 ActivityManagerWrapper 获取
- 系统维护的任务快照
-
实时截图
- 在 Recents 动画期间使用
screenshotTask() - 用于获取最新的任务画面
- 在 Recents 动画期间使用
关键代码: RecentsAnimationController.java:76-78
public ThumbnailData screenshotTask(int taskId) {return mController.screenshotTask(taskId);
}
4.4 缩略图更新路径
路径 1: 系统回调更新
触发时机: 任务快照在系统中更新时
流程:
TaskStackChangeListener.onTaskSnapshotChanged()被调用RecentsModel通知所有注册的监听器RecentsView.onTaskThumbnailChanged()查找对应的 TaskViewTaskView更新其TaskThumbnailView
关键代码: RecentsView.java:989-1004
@Override
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {if (mHandleTaskStackChanges) {TaskView taskView = getTaskViewByTaskId(taskId);if (taskView != null) {for (TaskIdAttributeContainer container :taskView.getTaskIdAttributeContainers()) {if (container != null && taskId == container.getTask().key.id) {container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);}}}}return null;
}
路径 2: 手势动画期间更新
触发时机: 在 Recents 动画控制器激活时
流程:
AbsSwipeUpHandler持有RecentsAnimationController- 需要时调用
screenshotTask(taskId)获取实时截图 - 将截图数据缓存到
mTaskSnapshotCache - 动画结束后更新到
RecentsView
关键代码: AbsSwipeUpHandler.java:1569-1577
UI_HELPER_EXECUTOR.execute(() -> {if (mRecentsAnimationController == null) {return;}final int taskId = mGestureState.getTopRunningTaskId();mTaskSnapshotCache.put(taskId,mRecentsAnimationController.screenshotTask(taskId));
});
路径 3: 从缓存异步加载
触发时机: TaskView 变为可见时
流程:
TaskView.onTaskListVisibilityChanged(true)被调用- 创建后台任务从
TaskThumbnailCache加载 - 加载完成后在主线程更新 UI
优势:
- 避免阻塞主线程
- 支持高分辨率缩略图的渐进式加载
- 缓存可在多个地方复用
4.5 缩略图切换到截图
场景: 当 Recents 动画被取消时,需要从实时窗口切换到静态截图
关键方法: RecentsView.switchToScreenshot()
调用时机:
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {if (mRecentsView == null) return;HashMap<Integer, ThumbnailData> snapshots =mGestureState.consumeRecentsAnimationCanceledSnapshot();if (snapshots != null) {mRecentsView.switchToScreenshot(snapshots, () -> {if (mRecentsAnimationController != null) {mRecentsAnimationController.cleanupScreenshot();}});}
});
切换逻辑:
- 使用最后截取的快照
- 将实时窗口替换为静态图片
- 清理动画资源
5. 关键类详细解析
5.1 AbsSwipeUpHandler 状态机
状态标志位 (Flags)
| 状态 | 说明 | 设置时机 |
|---|---|---|
| STATE_LAUNCHER_PRESENT | Launcher 已初始化 | onActivityInit() |
| STATE_LAUNCHER_STARTED | Launcher 已启动 | onLauncherStart() |
| STATE_LAUNCHER_DRAWN | Launcher 已绘制完成 | Launcher 首帧绘制后 |
| STATE_LAUNCHER_BIND_TO_SERVICE | 已绑定到触摸服务 | onLauncherBindToService() |
| STATE_GESTURE_STARTED | 手势已开始 | onGestureStarted() |
| STATE_GESTURE_COMPLETED | 手势已完成 | onGestureEnded() |
| STATE_APP_CONTROLLER_RECEIVED | 收到动画控制器 | onRecentsAnimationStart() |
| STATE_CAPTURE_SCREENSHOT | 需要截取屏幕 | 目标确定后 |
| STATE_SCREENSHOT_CAPTURED | 截图已完成 | 截图操作后 |
| STATE_SCALED_CONTROLLER_HOME | 缩放到 Home | 动画到 Home |
| STATE_SCALED_CONTROLLER_RECENTS | 缩放到 Recents | 动画到 Recents |
状态组合与回调
// 示例:Launcher 准备好且手势开始
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,this::onLauncherPresentAndGestureStarted);// 示例:完成到 Recents 的过渡
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_RECENTS,this::finishCurrentTransitionToRecents);
5.2 TaskViewSimulator 动画参数
核心动画属性详解
| 属性 | 类型 | 用途 | 取值范围 |
|---|---|---|---|
| fullScreenProgress | AnimatedFloat | 全屏进度 | 0=应用全屏, 1=Overview |
| recentsViewScale | AnimatedFloat | RecentsView 整体缩放 | 通常 < 1 |
| taskPrimaryTranslation | AnimatedFloat | 主轴(通常是 X)平移 | 像素值 |
| taskSecondaryTranslation | AnimatedFloat | 次轴(通常是 Y)平移 | 像素值 |
| carouselScale | AnimatedFloat | 轮播模式缩放 | 通常接近 1 |
| recentsViewScroll | AnimatedFloat | RecentsView 滚动偏移 | 像素值 |
变换矩阵计算
关键代码: TaskViewSimulator.java:462-489
public void apply(TransformParams params) {// 1. 基础缩略图变换mMatrix.set(mPositionHelper.getMatrix());// 2. TaskView 位置和平移mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, taskPrimaryTranslation.value);mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, taskSecondaryTranslation.value);// 3. 轮播缩放mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, carouselPrimaryTranslation.value);// 4. RecentsView 滚动mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);// 5. RecentsView 整体缩放mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);// 6. 应用窗口到 Home 的旋转applyWindowToHomeRotation(mMatrix);// 7. 应用到 Surfaceparams.applySurfaceParams(params.createSurfaceParams(this));
}
5.3 RecentsView 任务加载
applyLoadPlan 流程
关键代码: RecentsView.java:1699-1922
步骤:
-
清理旧任务
unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); removeAllViews(); -
创建 TaskView
for (int i = taskGroups.size() - 1; i >= 0; i--) {GroupTask groupTask = taskGroups.get(i);TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);addView(taskView);if (taskView instanceof GroupedTaskView) {((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, ...);} else {taskView.bind(groupTask.task1, mOrientationState);} } -
设置当前页
if (targetPage != -1 && mCurrentPage != targetPage) {runOnPageScrollsInitialized(() -> {setCurrentPage(targetPage);}); } -
更新任务视觉
resetTaskVisuals(); onTaskStackUpdated(); updateEnabledOverlays();
TaskView 对象池
优势:
- 避免频繁创建/销毁 View
- 减少 GC 压力
- 提升滚动性能
实现:
private final ViewPool<TaskView> mTaskViewPool;// 获取 TaskView
TaskView taskView = mTaskViewPool.getView();// 回收 TaskView
mTaskViewPool.recycle(taskView);
6. 时序图
6.1 完整手势流程时序图
6.2 缩略图更新时序图
7. 关键方法说明
7.1 AbsSwipeUpHandler 关键方法
onRecentsAnimationStart()
文件: AbsSwipeUpHandler.java:946-997
作用: 接收系统 Recents 动画回调,初始化动画目标和控制器
参数:
RecentsAnimationController controller: 动画控制器RecentsAnimationTargets targets: 远程动画目标
流程:
- 分配动画目标(支持分屏和桌面模式)
- 初始化设备配置
- 创建 TaskViewSimulator
- 设置状态标志
STATE_APP_CONTROLLER_RECEIVED
代码片段:
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,RecentsAnimationTargets targets) {super.onRecentsAnimationStart(controller, targets);// 根据模式分配目标if (isDesktopModeSupported() && targets.hasDesktopTasks()) {mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);} else {mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);}mRecentsAnimationController = controller;mRecentsAnimationTargets = targets;// 通知状态机mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
calculateEndTarget()
文件: AbsSwipeUpHandler.java:1224-1264
作用: 根据手势速度和位置计算手势结束目标
参数:
PointF velocityPxPerMs: X/Y 方向速度float endVelocityPxPerMs: 主方向速度boolean isFlingY: 是否为 Y 方向快速滑动boolean isCancel: 是否取消
返回: GestureEndTarget 枚举值
逻辑:
private GestureEndTarget calculateEndTarget(PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {if (isCancel) {return LAST_TASK;} else if (isFlingY) {// 快速滑动的判断if (mIsInAllAppsRegion) {return isSwipeUp ? ALL_APPS : LAST_TASK;}return isScrollingToNewTask() ? NEW_TASK : HOME;} else {// 慢速滑动的判断if (mIsInAllAppsRegion) {return ALL_APPS;} else if (mIsMotionPaused) {return RECENTS;} else if (isScrollingToNewTask()) {return NEW_TASK;}return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK;}
}
applyScrollAndTransform()
文件: SwipeUpAnimationLogic.java (基类)
作用: 应用当前滚动和变换到窗口
流程:
- 从
mCurrentShift计算进度 - 更新
RecentsView滚动 - 对每个
RemoteTargetHandle应用变换 - 通过
TaskViewSimulator.apply()计算矩阵 - 应用
SurfaceTransaction
7.2 RecentsView 关键方法
applyLoadPlan()
文件: RecentsView.java:1699-1922
作用: 应用任务加载计划,创建并绑定 TaskView
参数:
ArrayList<GroupTask> taskGroups: 任务组列表
流程:
- 如果列表为空,移除所有视图并重置状态
- 保存当前页面状态
- 卸载可见任务数据
- 移除所有现有视图
- 倒序遍历任务组(从最旧到最新)
- 从对象池获取 TaskView
- 根据任务类型绑定数据(单任务/分组任务/桌面任务)
- 添加 Clear All 按钮
- 恢复焦点和当前页
- 重置任务视觉效果
- 触发
onTaskStackUpdated()
关键代码:
protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {if (mPendingAnimation != null) {// 等待动画完成后再应用mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));return;}// 卸载旧数据unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);removeAllViews();// 创建新的 TaskViewfor (int i = taskGroups.size() - 1; i >= 0; i--) {GroupTask groupTask = taskGroups.get(i);TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);addView(taskView);taskView.bind(groupTask.task1, mOrientationState);}// 添加 Clear All 按钮if (!taskGroups.isEmpty()) {addView(mClearAllButton);}// 恢复页面状态if (targetPage != -1) {runOnPageScrollsInitialized(() -> setCurrentPage(targetPage));}resetTaskVisuals();onTaskStackUpdated();
}
onTaskThumbnailChanged()
文件: RecentsView.java:989-1004
作用: 当任务缩略图在系统中更新时的回调
参数:
int taskId: 任务 IDThumbnailData thumbnailData: 新的缩略图数据
返回: 更新的 Task 对象(或 null)
流程:
- 检查是否处理任务栈变化
- 通过
taskId查找对应的 TaskView - 遍历 TaskView 的任务属性容器
- 找到匹配的任务并更新其缩略图
代码:
@Override
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {if (mHandleTaskStackChanges) {TaskView taskView = getTaskViewByTaskId(taskId);if (taskView != null) {for (TaskIdAttributeContainer container :taskView.getTaskIdAttributeContainers()) {if (container != null && taskId == container.getTask().key.id) {container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);}}}}return null;
}
7.3 TaskView 关键方法
bind()
文件: TaskView.java:647-655
作用: 绑定任务数据到 TaskView
参数:
Task task: 要绑定的任务RecentsOrientedState orientedState: 方向状态
流程:
- 取消待处理的加载任务
- 设置
mTask字段 - 更新任务 ID 容器
- 调用
mSnapshotView.bind() - 设置方向状态
代码:
public void bind(Task task, RecentsOrientedState orientedState) {cancelPendingLoadTasks();mTask = task;mTaskIdContainer[0] = mTask.key.id;mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView, STAGE_POSITION_UNDEFINED);mSnapshotView.bind(task);setOrientationState(orientedState);
}
onTaskListVisibilityChanged()
文件: TaskView.java:1045-1098
作用: 当任务可见性改变时加载或卸载资源
参数:
boolean visible: 是否可见@TaskDataChanges int changes: 需要更新的标志
流程:
- 取消待处理的加载任务
- 如果可见:
- 从缓存或系统异步加载缩略图
- 异步加载图标
- 更新圆角半径
- 如果不可见:
- 清除缩略图
- 清除图标
- 释放内存
代码:
public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {if (mTask == null) {return;}cancelPendingLoadTasks();if (visible) {RecentsModel model = RecentsModel.INSTANCE.get(getContext());TaskThumbnailCache thumbnailCache = model.getThumbnailCache();TaskIconCache iconCache = model.getIconCache();if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {// 后台加载缩略图mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask, thumbnail -> {mSnapshotView.setThumbnail(mTask, thumbnail);});}if (needsUpdate(changes, FLAG_UPDATE_ICON)) {// 后台加载图标mIconLoadRequest = iconCache.updateIconInBackground(mTask, (task) -> {setIcon(mIconView, task.icon);setText(mIconView, task.title);mDigitalWellBeingToast.initialize(task);});}} else {// 清除资源if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {mSnapshotView.setThumbnail(null, null);mTask.thumbnail = null;}if (needsUpdate(changes, FLAG_UPDATE_ICON)) {setIcon(mIconView, null);setText(mIconView, null);}}
}
launchTaskAnimated()
文件: TaskView.java:855-891
作用: 启动任务并播放动画
返回: RunnableList 用于动画完成回调
流程:
- 检查任务是否存在
- 获取 Activity 启动选项
- 调用
ActivityManagerWrapper.startActivityFromRecents() - 如果在 Live Tile 模式:
- 添加侧边任务启动回调
- 返回回调列表
- 记录任务启动事件
代码:
@Nullable
public RunnableList launchTaskAnimated() {if (mTask != null) {ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);opts.options.setLaunchDisplayId(getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());if (ActivityManagerWrapper.getInstance().startActivityFromRecents(mTask.key, opts.options)) {RecentsView recentsView = getRecentsView();if (recentsView.getRunningTaskViewId() != -1) {// Live Tile 模式recentsView.onTaskLaunchedInLiveTileMode();RunnableList callbackList = new RunnableList();recentsView.addSideTaskLaunchCallback(callbackList);return callbackList;}return opts.onEndCallback;} else {notifyTaskLaunchFailed(TAG);return null;}}return null;
}
7.4 TaskViewSimulator 关键方法
apply()
文件: TaskViewSimulator.java:421-519
作用: 应用当前动画参数到远程动画目标
参数:
TransformParams params: 变换参数容器
流程:
- 检查设备配置和缩略图位置
- 如果布局无效,重新计算:
- 获取全屏缩放比例
- 更新缩略图矩阵
- 更新方向旋转
- 设置全屏进度
- 构建变换矩阵:
- 应用缩略图矩阵
- 应用 TaskView 平移
- 应用轮播缩放
- 应用 RecentsView 滚动
- 应用 RecentsView 缩放
- 应用窗口到 Home 的旋转
- 计算裁剪矩形
- 应用 Surface 参数
代码:
public void apply(TransformParams params) {if (mDp == null || mThumbnailPosition.isEmpty()) {return;}if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {mLayoutValid = true;mOrientationStateId = mOrientationState.getStateId();getFullScreenScale();mThumbnailData.rotation = mOrientationState.getDisplayRotation();// 更新缩略图矩阵mPositionHelper.updateThumbnailMatrix(mThumbnailPosition, mThumbnailData,mTaskRect.width(), mTaskRect.height(),mDp.isTablet, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);mPositionHelper.getMatrix().invert(mInversePositionMatrix);}float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,carouselScale.value);// 构建变换矩阵mMatrix.set(mPositionHelper.getMatrix());mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);// 应用各种变换mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, taskPrimaryTranslation.value);mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, taskSecondaryTranslation.value);mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);applyWindowToHomeRotation(mMatrix);// 计算裁剪区域mTempRectF.set(0, 0, mTaskRect.width(), mTaskRect.height());mInversePositionMatrix.mapRect(mTempRectF);mTempRectF.roundOut(mTmpCropRect);// 应用到 Surfaceparams.applySurfaceParams(params.createSurfaceParams(this));
}
7.5 RecentsAnimationController 关键方法
finish()
文件: RecentsAnimationController.java:125-195
作用: 完成 Recents 动画
参数:
boolean toRecents: 是否到 Recents(true)还是应用(false)Runnable onFinishComplete: 完成回调boolean sendUserLeaveHint: 是否发送用户离开提示
流程:
- 添加回调到待处理列表
- 如果已经请求完成,直接返回
- 设置完成标志
- 通知监听器
- 在后台线程执行完成操作
- 结束 Jank 监控
- 执行回调
代码:
@UiThread
public void finishController(boolean toRecents, Runnable callback,boolean sendUserLeaveHint, boolean forceFinish) {mPendingFinishCallbacks.add(callback);if (!forceFinish && mFinishRequested) {return; // 已经请求完成}mFinishRequested = true;mFinishTargetIsLauncher = toRecents;mOnFinishedListener.accept(this);Runnable finishCb = () -> {mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {@Overridepublic void send(int i, Bundle bundle) throws RemoteException {MAIN_EXECUTOR.execute(() -> {mPendingFinishCallbacks.executeAllAndDestroy();});}});// 结束 Jank 监控InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);};if (forceFinish) {finishCb.run();} else {UI_HELPER_EXECUTOR.execute(finishCb);}
}
8. 最佳实践和注意事项
8.1 性能优化
-
TaskView 对象池
- 复用 TaskView 实例,避免频繁创建
- 及时回收不可见的 TaskView
-
异步加载
- 缩略图和图标在后台线程加载
- 避免阻塞主线程
-
按需加载
- 只为可见任务加载高分辨率资源
- 不可见时释放内存
-
动画优化
- 使用
TaskViewSimulator避免实际 View 操作 - 直接操作 Surface 而非 View 层级
- 使用
8.2 状态管理
-
MultiStateCallback 使用
- 明确定义所有状态标志
- 使用状态组合触发回调
- 避免竞态条件
-
手势取消处理
- 正确处理
onRecentsAnimationCanceled() - 切换到截图避免闪烁
- 清理动画资源
- 正确处理
-
生命周期管理
- Activity 重建时正确恢复状态
- 及时注销监听器
- 避免内存泄漏
8.3 调试技巧
-
日志记录
- 使用
ActiveGestureLog记录关键事件 - 记录状态转换
- 记录动画参数
- 使用
-
测试协议
- 使用
TestProtocol标记关键点 - 支持自动化测试
- 使用
-
性能监控
- 使用
InteractionJankMonitorWrapper监控卡顿 - 追踪动画完成时间
- 使用
9. 常见问题和解决方案
Q1: 缩略图不更新?
原因:
- TaskView 不可见
- 缓存未失效
- 系统未发送更新通知
解决:
// 强制刷新缩略图
taskView.onTaskListVisibilityChanged(true, TaskView.FLAG_UPDATE_THUMBNAIL);
Q2: 动画卡顿?
原因:
- 主线程执行耗时操作
- Surface 事务过多
- GC 频繁触发
解决:
- 使用对象池
- 批量应用 Surface 事务
- 异步加载资源
Q3: 手势识别不准确?
原因:
- 速度阈值不合理
- 状态判断逻辑错误
- 触摸事件被拦截
解决:
- 调整
quickstep_fling_threshold_speed - 检查
calculateEndTarget()逻辑 - 确保
InputConsumer正确启用
Q4: Launcher 未正确显示?
原因:
- 状态机未到达必要状态
- Launcher 被其他窗口覆盖
- Activity 生命周期异常
解决:
- 检查
STATE_LAUNCHER_DRAWN标志 - 确保调用
clearForceInvisibleFlag() - 检查
onActivityInit()返回值
10. 总结
Launcher3 的最近任务系统是一个复杂但设计精良的架构,主要特点包括:
-
清晰的职责分离
- 手势处理(AbsSwipeUpHandler)
- UI 管理(RecentsView/TaskView)
- 动画计算(TaskViewSimulator)
- 系统交互(RecentsAnimationController)
-
高效的性能
- 对象池复用
- 异步资源加载
- 直接操作 Surface
- 按需加载策略
-
灵活的状态管理
- MultiStateCallback 机制
- 清晰的状态转换
- 可组合的状态标志
-
完善的动画系统
- 实时窗口变换
- 平滑过渡动画
- 支持多种手势结束目标
理解这些核心概念和流程,将有助于:
- 修复 Recents 相关 bug
- 添加新的手势功能
- 优化动画性能
- 定制 Launcher 行为
附录
A. 相关文件列表
| 文件 | 说明 | 行数 |
|---|---|---|
| AbsSwipeUpHandler.java | 手势处理核心 | ~2000 |
| RecentsView.java | 任务容器视图 | ~6000 |
| TaskView.java | 单个任务视图 | ~1943 |
| TaskViewSimulator.java | 动画计算器 | ~562 |
| RemoteAnimationTargets.java | 动画目标封装 | ~198 |
| RecentsAnimationController.java | 动画控制器 | ~282 |
| RecentsAnimationCallbacks.java | 动画回调 | ~262 |
B. 关键常量
// 动画时长
MAX_SWIPE_DURATION = 350ms // 最大滑动时长
RECENTS_ATTACH_DURATION = 300ms // Recents 附着时长// 进度阈值
MIN_PROGRESS_FOR_OVERVIEW = 0.7f // 进入 Overview 的最小进度
UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f // 更新系统 UI 的阈值// 状态标志
STATE_LAUNCHER_PRESENT = 1 << 0
STATE_GESTURE_STARTED = 1 << 5
STATE_APP_CONTROLLER_RECEIVED = 1 << 7
C. 参考资料
- Android Recents Animation 文档
- Launcher3 源码
- 手势导航设计文档
