AOSP Android13 Launcher3 最近任务详解

本文档详细介绍了 Launcher3 中最近任务(Recents)的核心架构、手势交互流程、缩略图更新机制以及关键类的设计。

目录


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(): 应用任务加载计划,创建 TaskView
  • onRecentsAnimationStart(): 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(): 应用当前动画参数到 TransformParams
  • getCurrentMatrix(): 获取当前变换矩阵
  • 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. 类关系图

manages
controls
uses
creates
contains multiple
interacts
contains
binds to
creates
wraps
AbsSwipeUpHandler
-RecentsAnimationController mRecentsAnimationController
-RecentsAnimationTargets mRecentsAnimationTargets
-RecentsView mRecentsView
-MultiStateCallback mStateCallback
+onGestureStarted()
+onGestureEnded()
+onRecentsAnimationStart()
+animateToProgress()
RecentsView
-RemoteTargetHandle[] mRemoteTargetHandles
-RecentsAnimationController mRecentsAnimationController
-ViewPool<TaskView> mTaskViewPool
-int mRunningTaskViewId
+applyLoadPlan()
+updateThumbnail()
+onRecentsAnimationStart()
+onTaskThumbnailChanged()
TaskView
-Task mTask
-TaskThumbnailView mSnapshotView
-TaskViewIcon mIconView
-CancellableTask mThumbnailLoadRequest
+bind()
+onTaskListVisibilityChanged()
+launchTaskAnimated()
TaskViewSimulator
+AnimatedFloat taskPrimaryTranslation
+AnimatedFloat recentsViewScale
+AnimatedFloat fullScreenProgress
+apply()
+getCurrentMatrix()
RemoteAnimationTargets
+RemoteAnimationTarget[] apps
+RemoteAnimationTarget[] wallpapers
+RemoteAnimationTarget[] nonApps
+findTask()
+release()
RecentsAnimationController
-RecentsAnimationControllerCompat mController
+finish()
+screenshotTask()
+setUseLauncherSystemBarFlags()
RecentsAnimationCallbacks
-Set<RecentsAnimationListener> mListeners
+addListener()
+onAnimationStart()
+onAnimationCanceled()
RecentsAnimationTargets
TaskThumbnailView
Task

核心类之间的关系说明:

  1. AbsSwipeUpHandler 是手势处理的核心协调者

    • 创建和管理 TaskViewSimulator
    • 控制 RecentsAnimationController
    • 使用 RemoteAnimationTargets 进行窗口动画
    • 管理 RecentsView 的可见性和状态
  2. RecentsView 是任务容器

    • 包含多个 TaskView
    • 与 RecentsAnimationController 交互
    • 使用对象池管理 TaskView 实例
  3. TaskView 是单个任务的表现

    • 绑定 Task 数据模型
    • 包含 TaskThumbnailView 和 TaskViewIcon
    • 处理缩略图和图标的加载
  4. 动画系统

    • RecentsAnimationCallbacks 接收系统回调
    • 创建 RecentsAnimationController
    • 包装 RemoteAnimationTargets

3. 手势上滑到最近任务流程

3.1 流程概览

快速上滑
慢速上滑
横向滑动
用户从底部上滑
TouchInteractionService 接收触摸事件
创建 AbsSwipeUpHandler
初始化状态机
onGestureStarted 被调用
请求 Recents 动画
系统回调 onRecentsAnimationStart
初始化 RemoteAnimationTargets
创建 TaskViewSimulator
手势过程中持续更新位置
手势结束判断
目标: HOME/RECENTS
目标: LAST_TASK/RECENTS
目标: NEW_TASK
执行目标动画
finishRecentsAnimation
更新缩略图
完成

3.2 详细步骤

阶段 1: 手势初始化 (0-100ms)

关键文件: AbsSwipeUpHandler.java:391-471

  1. AbsSwipeUpHandler 构造

    public AbsSwipeUpHandler(Context context, ...) {
        mActivityInterface = gestureState.getActivityInterface();
        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
        initStateCallbacks();  // 初始化状态机
    }
    
  2. 状态机初始化 (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);
        // ... 更多状态回调
    }
    
  3. 手势开始 (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

  1. 接收动画回调

    @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);
    }
    
  2. Launcher 准备 (AbsSwipeUpHandler.java:555-622)

    private void onLauncherStart() {
        mRecentsView.updateRecentsRotation();
        mAnimationFactory = mActivityInterface.prepareRecentsUI(
            mDeviceState, mWasLauncherAlreadyVisible,
            this::onAnimatorPlaybackControllerCreated);
    
        mStateCallback.setState(STATE_LAUNCHER_DRAWN);
    }
    
  3. 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

  1. 当前偏移更新

    @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();
    }
    
  2. 窗口变换应用 (SwipeUpAnimationLogic.java)

    protected void applyScrollAndTransform() {
        // 计算当前窗口位置
        for (RemoteTargetHandle handle : mRemoteTargetHandles) {
            TaskViewSimulator simulator = handle.getTaskViewSimulator();
            simulator.apply(handle.getTransformParams());
        }
    }
    
  3. 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

  1. 手势结束

    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);
    }
    
  2. 计算结束目标 (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);
        }
    }
    
  3. 目标类型:

    • HOME: 回到桌面
    • RECENTS: 进入最近任务界面
    • LAST_TASK: 返回原任务
    • NEW_TASK: 切换到新任务
    • ALL_APPS: 打开应用抽屉
阶段 5: 目标动画执行

关键文件: AbsSwipeUpHandler.java:1469-1677

  1. 动画到目标进度

    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();
        }
    }
    
  2. RecentsView 准备 (RecentsView.java)

    public void onPrepareGestureEndAnimation(AnimatorSet animatorSet,
            GestureEndTarget endTarget, TaskViewSimulator[] simulators) {
        // 根据目标类型准备动画
        if (endTarget == RECENTS) {
            // 快照到最近页面
            snapToPage(getDestinationPage(), duration);
        }
    }
    
阶段 6: 动画完成和清理

关键文件: AbsSwipeUpHandler.java:1148-1204

  1. 目标到达处理

    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;
        }
    }
    
  2. 完成 Recents 动画

    private void finishCurrentTransitionToRecents() {
        if (mRecentsAnimationController != null) {
            mRecentsAnimationController.finish(true /* toRecents */, () -> {
                // 动画完成回调
            });
        }
    }
    

4. 缩略图实时更新机制

4.1 缩略图更新流程

Android系统 TaskStackListener RecentsModel ThumbnailCache RecentsView TaskView TaskThumbnailView onTaskSnapshotChanged(taskId) 通知缩略图变化 更新缓存 onTaskThumbnailChanged(taskId, thumbnailData) 查找 TaskView by taskId setThumbnail(task, thumbnailData) 更新位图并刷新显示 Android系统 TaskStackListener RecentsModel ThumbnailCache RecentsView TaskView TaskThumbnailView

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 -> {
                    // 回到主线程更新 UI
                    mSnapshotView.setThumbnail(mTask, thumbnail);
                });
        }
    } else {
        // 不可见时清除缩略图以节省内存
        if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
            mSnapshotView.setThumbnail(null, null);
            mTask.thumbnail = null;
        }
    }
}

4.3 缩略图缓存机制

分层缓存策略:

  1. 内存缓存 (TaskThumbnailCache)

    • 缓存最近使用的缩略图
    • LRU 策略自动淘汰旧缩略图
  2. 系统缓存

    • 通过 ActivityManagerWrapper 获取
    • 系统维护的任务快照
  3. 实时截图

    • 在 Recents 动画期间使用 screenshotTask()
    • 用于获取最新的任务画面

关键代码: RecentsAnimationController.java:76-78

public ThumbnailData screenshotTask(int taskId) {
    return mController.screenshotTask(taskId);
}

4.4 缩略图更新路径

路径 1: 系统回调更新

触发时机: 任务快照在系统中更新时

流程:

  1. TaskStackChangeListener.onTaskSnapshotChanged() 被调用
  2. RecentsModel 通知所有注册的监听器
  3. RecentsView.onTaskThumbnailChanged() 查找对应的 TaskView
  4. TaskView 更新其 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 动画控制器激活时

流程:

  1. AbsSwipeUpHandler 持有 RecentsAnimationController
  2. 需要时调用 screenshotTask(taskId) 获取实时截图
  3. 将截图数据缓存到 mTaskSnapshotCache
  4. 动画结束后更新到 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 变为可见时

流程:

  1. TaskView.onTaskListVisibilityChanged(true) 被调用
  2. 创建后台任务从 TaskThumbnailCache 加载
  3. 加载完成后在主线程更新 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();
            }
        });
    }
});

切换逻辑:

  1. 使用最后截取的快照
  2. 将实时窗口替换为静态图片
  3. 清理动画资源

5. 关键类详细解析

5.1 AbsSwipeUpHandler 状态机

状态标志位 (Flags)
状态说明设置时机
STATE_LAUNCHER_PRESENTLauncher 已初始化onActivityInit()
STATE_LAUNCHER_STARTEDLauncher 已启动onLauncherStart()
STATE_LAUNCHER_DRAWNLauncher 已绘制完成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 动画参数

核心动画属性详解
属性类型用途取值范围
fullScreenProgressAnimatedFloat全屏进度0=应用全屏, 1=Overview
recentsViewScaleAnimatedFloatRecentsView 整体缩放通常 < 1
taskPrimaryTranslationAnimatedFloat主轴(通常是 X)平移像素值
taskSecondaryTranslationAnimatedFloat次轴(通常是 Y)平移像素值
carouselScaleAnimatedFloat轮播模式缩放通常接近 1
recentsViewScrollAnimatedFloatRecentsView 滚动偏移像素值
变换矩阵计算

关键代码: 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. 应用到 Surface
    params.applySurfaceParams(params.createSurfaceParams(this));
}

5.3 RecentsView 任务加载

applyLoadPlan 流程

关键代码: RecentsView.java:1699-1922

步骤:

  1. 清理旧任务

    unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
    removeAllViews();
    
  2. 创建 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);
        }
    }
    
  3. 设置当前页

    if (targetPage != -1 && mCurrentPage != targetPage) {
        runOnPageScrollsInitialized(() -> {
            setCurrentPage(targetPage);
        });
    }
    
  4. 更新任务视觉

    resetTaskVisuals();
    onTaskStackUpdated();
    updateEnabledOverlays();
    
TaskView 对象池

优势:

  • 避免频繁创建/销毁 View
  • 减少 GC 压力
  • 提升滚动性能

实现:

private final ViewPool<TaskView> mTaskViewPool;

// 获取 TaskView
TaskView taskView = mTaskViewPool.getView();

// 回收 TaskView
mTaskViewPool.recycle(taskView);

6. 时序图

6.1 完整手势流程时序图

用户 TouchInteractionService AbsSwipeUpHandler RecentsAnimationController RecentsView TaskView 系统 从底部上滑 创建 Handler initStateCallbacks() onGestureStarted() 请求 Recents 动画 onRecentsAnimationStart(controller, targets) 创建 Controller 设置 STATE_APP_CONTROLLER_RECEIVED onGestureAnimationStart(runningTasks) 创建占位 TaskView bind(task) buildAnimationController() 创建 TaskViewSimulator par [Launcher 初始化] [动画准备] 手指移动 onCurrentShiftUpdated() applyScrollAndTransform() 更新窗口变换 应用 SurfaceTransaction loop [手势移动中] 手指抬起 onGestureEnded(velocity) calculateEndTarget() animateToProgress(target) onPrepareGestureEndAnimation() snapToPage() finish(toRecents=true) 完成动画 更新缩略图 createWindowAnimationToHome() finish(toRecents=false) 完成动画 finish(toRecents=false) 恢复任务 alt [目标是 RECENTS] [目标是 HOME] [目标是 LAST_TASK] reset() 用户 TouchInteractionService AbsSwipeUpHandler RecentsAnimationController RecentsView TaskView 系统

6.2 缩略图更新时序图

系统 TaskStackListener RecentsModel ThumbnailCache RecentsView TaskView TaskThumbnailView 任务快照更新 onTaskSnapshotChanged(taskId) 通知缩略图变化 updateThumbnailInBackground() 在后台线程加载 解码位图 更新 LRU 缓存 onTaskThumbnailChanged(taskId, data) getTaskViewByTaskId(taskId) setThumbnail(task, data) updateThumbnailMatrix() invalidate() par [缓存更新] [UI 通知] 缩略图显示更新 系统 TaskStackListener RecentsModel ThumbnailCache RecentsView TaskView TaskThumbnailView

7. 关键方法说明

7.1 AbsSwipeUpHandler 关键方法

onRecentsAnimationStart()

文件: AbsSwipeUpHandler.java:946-997

作用: 接收系统 Recents 动画回调,初始化动画目标和控制器

参数:

  • RecentsAnimationController controller: 动画控制器
  • RecentsAnimationTargets targets: 远程动画目标

流程:

  1. 分配动画目标(支持分屏和桌面模式)
  2. 初始化设备配置
  3. 创建 TaskViewSimulator
  4. 设置状态标志 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 (基类)

作用: 应用当前滚动和变换到窗口

流程:

  1. mCurrentShift 计算进度
  2. 更新 RecentsView 滚动
  3. 对每个 RemoteTargetHandle 应用变换
  4. 通过 TaskViewSimulator.apply() 计算矩阵
  5. 应用 SurfaceTransaction

7.2 RecentsView 关键方法

applyLoadPlan()

文件: RecentsView.java:1699-1922

作用: 应用任务加载计划,创建并绑定 TaskView

参数:

  • ArrayList<GroupTask> taskGroups: 任务组列表

流程:

  1. 如果列表为空,移除所有视图并重置状态
  2. 保存当前页面状态
  3. 卸载可见任务数据
  4. 移除所有现有视图
  5. 倒序遍历任务组(从最旧到最新)
  6. 从对象池获取 TaskView
  7. 根据任务类型绑定数据(单任务/分组任务/桌面任务)
  8. 添加 Clear All 按钮
  9. 恢复焦点和当前页
  10. 重置任务视觉效果
  11. 触发 onTaskStackUpdated()

关键代码:

protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
    if (mPendingAnimation != null) {
        // 等待动画完成后再应用
        mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
        return;
    }

    // 卸载旧数据
    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);
        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: 任务 ID
  • ThumbnailData thumbnailData: 新的缩略图数据

返回: 更新的 Task 对象(或 null)

流程:

  1. 检查是否处理任务栈变化
  2. 通过 taskId 查找对应的 TaskView
  3. 遍历 TaskView 的任务属性容器
  4. 找到匹配的任务并更新其缩略图

代码:

@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: 方向状态

流程:

  1. 取消待处理的加载任务
  2. 设置 mTask 字段
  3. 更新任务 ID 容器
  4. 调用 mSnapshotView.bind()
  5. 设置方向状态

代码:

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: 需要更新的标志

流程:

  1. 取消待处理的加载任务
  2. 如果可见:
    • 从缓存或系统异步加载缩略图
    • 异步加载图标
    • 更新圆角半径
  3. 如果不可见:
    • 清除缩略图
    • 清除图标
    • 释放内存

代码:

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 用于动画完成回调

流程:

  1. 检查任务是否存在
  2. 获取 Activity 启动选项
  3. 调用 ActivityManagerWrapper.startActivityFromRecents()
  4. 如果在 Live Tile 模式:
    • 添加侧边任务启动回调
    • 返回回调列表
  5. 记录任务启动事件

代码:

@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: 变换参数容器

流程:

  1. 检查设备配置和缩略图位置
  2. 如果布局无效,重新计算:
    • 获取全屏缩放比例
    • 更新缩略图矩阵
    • 更新方向旋转
  3. 设置全屏进度
  4. 构建变换矩阵:
    • 应用缩略图矩阵
    • 应用 TaskView 平移
    • 应用轮播缩放
    • 应用 RecentsView 滚动
    • 应用 RecentsView 缩放
    • 应用窗口到 Home 的旋转
  5. 计算裁剪矩形
  6. 应用 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);

    // 应用到 Surface
    params.applySurfaceParams(params.createSurfaceParams(this));
}

7.5 RecentsAnimationController 关键方法

finish()

文件: RecentsAnimationController.java:125-195

作用: 完成 Recents 动画

参数:

  • boolean toRecents: 是否到 Recents(true)还是应用(false)
  • Runnable onFinishComplete: 完成回调
  • boolean sendUserLeaveHint: 是否发送用户离开提示

流程:

  1. 添加回调到待处理列表
  2. 如果已经请求完成,直接返回
  3. 设置完成标志
  4. 通知监听器
  5. 在后台线程执行完成操作
  6. 结束 Jank 监控
  7. 执行回调

代码:

@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() {
            @Override
            public 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 性能优化

  1. TaskView 对象池

    • 复用 TaskView 实例,避免频繁创建
    • 及时回收不可见的 TaskView
  2. 异步加载

    • 缩略图和图标在后台线程加载
    • 避免阻塞主线程
  3. 按需加载

    • 只为可见任务加载高分辨率资源
    • 不可见时释放内存
  4. 动画优化

    • 使用 TaskViewSimulator 避免实际 View 操作
    • 直接操作 Surface 而非 View 层级

8.2 状态管理

  1. MultiStateCallback 使用

    • 明确定义所有状态标志
    • 使用状态组合触发回调
    • 避免竞态条件
  2. 手势取消处理

    • 正确处理 onRecentsAnimationCanceled()
    • 切换到截图避免闪烁
    • 清理动画资源
  3. 生命周期管理

    • Activity 重建时正确恢复状态
    • 及时注销监听器
    • 避免内存泄漏

8.3 调试技巧

  1. 日志记录

    • 使用 ActiveGestureLog 记录关键事件
    • 记录状态转换
    • 记录动画参数
  2. 测试协议

    • 使用 TestProtocol 标记关键点
    • 支持自动化测试
  3. 性能监控

    • 使用 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 的最近任务系统是一个复杂但设计精良的架构,主要特点包括:

  1. 清晰的职责分离

    • 手势处理(AbsSwipeUpHandler)
    • UI 管理(RecentsView/TaskView)
    • 动画计算(TaskViewSimulator)
    • 系统交互(RecentsAnimationController)
  2. 高效的性能

    • 对象池复用
    • 异步资源加载
    • 直接操作 Surface
    • 按需加载策略
  3. 灵活的状态管理

    • MultiStateCallback 机制
    • 清晰的状态转换
    • 可组合的状态标志
  4. 完善的动画系统

    • 实时窗口变换
    • 平滑过渡动画
    • 支持多种手势结束目标

理解这些核心概念和流程,将有助于:

  • 修复 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. 参考资料

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值