Android U WMS: 近期任务动画(2)

为了不把"战线"拉得太长,上一篇文章省略了 fixed rotation 的分析,本文就重点分析这个。

fixed rotation

java 复制代码
    // r 为 近期任务 activity
    // orientationSrc 也为 launcher activity
    // checkOpening 为 true
    private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
            @NonNull ActivityRecord orientationSrc, boolean checkOpening) {
        // ...

        
        // 1.使用实际的 orientation source 提供的 orientation,计算新的旋转方向
        final int rotation = rotationForActivityInDifferentOrientation(orientationSrc);
        
        // ...

        // 2. 
        setFixedRotationLaunchingApp(r, rotation);

        return true;
    }    

由于是从一个横屏的 app 返回竖屏的桌面,这里利用 launcher 提供的 orientation,计算出的旋转方向为0,因此接下来就会走 fixed rotation 流程。

java 复制代码
// DisplayContent.java

    void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) {
        // ...

        if (!r.hasFixedRotationTransform()) {
            // 1. 根据新的旋转方向 rotation,创建 fixed rotation transform
            startFixedRotationTransform(r, rotation);
        }

        // 2. 保存 fixed rotation app
        setFixedRotationLaunchingAppUnchecked(r, rotation);


        // ...
    }

    private void startFixedRotationTransform(WindowToken token, int rotation) {
        mTmpConfiguration.unset();
        
        // 根据竖屏旋转方向,计算配置,并保存到参数 mTmpConfiguration
        // 同时返回新的 display info
        final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
        
        // 下面都是根据竖屏方向计算出的数据
        final DisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
        final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
        final PrivacyIndicatorBounds indicatorBounds =
                calculatePrivacyIndicatorBoundsForRotation(rotation);
        final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
        final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
                cutout, roundedCorners, indicatorBounds, displayShape);

        // 1.1  apply fixed rotation transform
        token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
    }


    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
        if (mFixedRotationLaunchingApp == null && r != null) {
            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
            // Delay the hide animation to avoid blinking by clicking navigation bar that may
            // toggle fixed rotation in a short time.
            final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents
                    || mTransitionController.isTransientLaunch(r);
            // 2.1 start async rotation
            startAsyncRotation(shouldDebounce);
        } else if (mFixedRotationLaunchingApp != null && r == null) {
            
        }

        // 2.2 set fixed rotation app
        mFixedRotationLaunchingApp = r;
    }        

fixed rotation 流程主要有三步

  1. apply fixed rotation transform : 根据新方向计算一些数据,例如,配置,display info,等等,然后 apply 这些数据。
  2. start async rotation : 这是为了控制图层高于 app 的窗口动画,例如 statusbar。
  3. set fixed rotation app: 使用 DisplayContent#mFixedRotationLaunchingApp 保存 fixed rotation app,也就是近期任务 ActivityRecord。

apply fixed rotation transform

java 复制代码
// WindowToken
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
        Configuration config) {
    if (mFixedRotationTransformState != null) {
        mFixedRotationTransformState.disassociate(this);
    }
    config = new Configuration(config);
    
    // 1. FixedRotationTransformState 包装数据
    mFixedRotationTransformState = mTransitionController.isShellTransitionsEnabled()
            ? new FixedRotationTransformState(info, displayFrames, config)
            : new FixedRotationTransformStateLegacy(info, displayFrames, config,
                    mDisplayContent.getRotation());
                    
    mFixedRotationTransformState.mAssociatedTokens.add(this);
    
    mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
       
    // 就绪
    onFixedRotationStatePrepared();
}

private void onFixedRotationStatePrepared() {
    // 2.Resolve the rotated configuration.
    onConfigurationChanged(getParent().getConfiguration());
    
    final ActivityRecord r = asActivityRecord();
    if (r != null && r.hasProcess()) {
        // WPC 重新监听 ActivityReocord 配置,并获得一次最新的配置
        r.app.registerActivityConfigurationListener(r);
    }
}

根据新方向计算出的数据,最终被包装到 ActivityRecord#mFixedRotationTransformState,然后 ActivityRecord 要利用这些数据更新配置

java 复制代码
// WindowContainer.java

public void onConfigurationChanged(Configuration newParentConfig) {
    // 1. update config
    super.onConfigurationChanged(newParentConfig);
    
    //2. update surface position
    updateSurfacePositionNonOrganized();
    scheduleAnimation();
    if (mOverlayHost != null) {
        mOverlayHost.dispatchConfigurationChanged(getConfiguration());
    }
}

update config

配置更新的流程,我在屏幕旋转系列文章中已经分析过,这里只重点分析与 fixed rotation 相关的配置解析。

java 复制代码
// WindowToken.java

void resolveOverrideConfiguration(Configuration newParentConfig) {
    super.resolveOverrideConfiguration(newParentConfig);
    if (isFixedRotationTransforming()) {
        // resolved override config 从 fixed rotration config 中更新
        getResolvedOverrideConfiguration().updateFrom(
                mFixedRotationTransformState.mRotatedOverrideConfiguration);
    }
}

ActivityRecord 的 resolved override config 从 fixed rotation 配置更新后,就会导致 full config, merged override config 等的更新,然后还会通知监听者配置改变,如下

java 复制代码
// WindowContainer.java

public void onConfigurationChanged(Configuration newParentConfig) {
    mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
    
    // 解析 resolved override config
    resolveOverrideConfiguration(newParentConfig);
    
    // 更新 full config
    mFullConfiguration.setTo(newParentConfig);
    mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
    mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
    
    // 更新 merged override config
    onMergedOverrideConfigurationChanged();
    
    if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
            // 通知监听者 resolved override config change
            mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
                    mResolvedOverrideConfiguration);
        }
    }
    
    for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
        // 通知监听者,mergd override config change
        mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                mMergedOverrideConfiguration);
    }
    
    for (int i = getChildCount() - 1; i >= 0; --i) {
        // 把 ActivityRecord 的 full config,分发给 WindowState,让他们也更新配置
        dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
    }
}

监听者其中之一就是 WPC,它是进程在 WMS 中的代表,它会把新配置发送给 app 端,如下

java 复制代码
// ConfigurationContainer.java

public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    // 就是给 mRequestedOverrideConfiguration 赋值
    updateRequestedOverrideConfiguration(overrideConfiguration);
    
    final ConfigurationContainer parent = getParent();
    // WPC 处理配置改变
    // WPC 没有 parent,因此传入参数为 EMPTY
    onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
}
java 复制代码
// WindowProcessController.java

public void onConfigurationChanged(Configuration newGlobalConfig) {
    // 通过基类更新配置
    super.onConfigurationChanged(newGlobalConfig);

    // ...

    // 分发配置
    dispatchConfiguration(config);
}

void dispatchConfiguration(Configuration config) {
    // ...

    scheduleConfigurationChange(mThread, config);
}

private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
    ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
            config);
    mHasCachedConfiguration = false;
    try {
        // 通知 app 配置改变
        mAtm.getLifecycleManager().scheduleTransaction(thread,
                ConfigurationChangeItem.obtain(config, mLastTopActivityDeviceId));
    } catch (Exception e) {
        Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
    }
}

update surface position

ActivityRecord 更新了 fixed rotation 的配置后,还需要更新 surface position

java 复制代码
// WindowContainer.java

final void updateSurfacePositionNonOrganized() {
    if (isOrganized()) return;

    // 通过 sync transaction 来 update surface position
    // 虽然 WindowToken 有复写,但是仍然是通过基类实现
    updateSurfacePosition(getSyncTransaction());
}
java 复制代码
// WindowContianer.java

void updateSurfacePosition(Transaction t) {
    if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
        return;
    }

    if (isClosingWhenResizing()) {

    } else {
        // 获取 ActivityRecord surface 相对于 parent surface 的偏移x,y 坐标
        getRelativePosition(mTmpPos);
    }
    
    // 计算旋转角度差
    final int deltaRotation = getRelativeDisplayRotation();
    if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
        return;
    }

    // 先设置 ActivityRecord surface 相对于 parent surface 的偏移 x,y 坐标
    t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
    // set first, since we don't want rotation included in this (for now).
    mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);

    if (mTransitionController.isShellTransitionsEnabled()
            && !mTransitionController.useShellTransitionsRotation()) {
        if (deltaRotation != Surface.ROTATION_0) {
            // 根据旋转角度差,update surface position
            // 虽然 WindowToken 有复写,但是还是通过基类实现
            updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
            
            getPendingTransaction().setFixedTransformHint(mSurfaceControl,
                    getWindowConfiguration().getDisplayRotation());
        } else if (deltaRotation != mLastDeltaRotation) {
           
        }
    }
    mLastDeltaRotation = deltaRotation;
}

update ActivityRecord surface position

  1. 先计算 ActivityRecord surface 相对于 parent 的坐标偏移,然后设置给 ActivityRecord surface。ActivityRecord 一般都是填充 parent 的,因此这个偏移量都是 0 。
  2. 计算旋转角度差,然后根据它来 update surface position。

来看下如何计算旋转角度差

java 复制代码
// WindowContainer.java

    int getRelativeDisplayRotation() {
        final WindowContainer parent = getParent();
        if (parent == null) return Surface.ROTATION_0;

        // 获取自己的旋转角度
        // 此时返回的是 fixed rotation 的旋转角度
        final int rotation = getWindowConfiguration().getDisplayRotation();

        // 获取 parent 的旋转角度
        final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();

        // 计算旋转角度差
        // 注意,是从 parent rotation 减去自己的 rotation
        return RotationUtils.deltaRotation(rotation, parentRotation);
    }    
java 复制代码
// RotationUtils.java
    public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
        int delta = newRotation - oldRotation;
        if (delta < 0) delta += 4;
        return delta;
    }

计算出旋转角度差后,再来 update ActivityRecord surface position,也就是先 rotation 再 translate。

java 复制代码
// WindowContainer.java

    protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
            @Nullable SurfaceControl positionLeash) {
        // parent must be non-null otherwise deltaRotation would be 0.
        // 1. rotate surface
        RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
        
        mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
        final Rect parentBounds = getParent().getBounds();
        final boolean flipped = (deltaRotation % 2) != 0;
        
        // 2. 计算偏移,并设置为 surface
        RotationUtils.rotatePoint(mTmpPos, deltaRotation,
                flipped ? parentBounds.height() : parentBounds.width(),
                flipped ? parentBounds.width() : parentBounds.height());
        t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
                mTmpPos.x, mTmpPos.y);
    }

// RotationUtils.java

    /**
     * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
     * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
     * corner appropriately.
     */
    public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
            @Rotation int rotation) {
        // Note: the matrix values look inverted, but they aren't because our coordinate-space
        // is actually left-handed.
        // Note: setMatrix expects values in column-major order.
        switch (rotation) {
            // ...

            case ROTATION_90:
                t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f);
                break;
        }
    }

    /**
     * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the
     * origin as if the point is stuck to the rectangle. The rectangle is transformed such that
     * it's top/left remains at the origin after the rotation.
     */
    public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
            int parentW, int parentH) {
        int origX = inOutPoint.x;
        switch (rotation) {
            // ...

            case ROTATION_90:
                inOutPoint.x = inOutPoint.y;
                inOutPoint.y = parentW - origX;
                return;
        }
    }    

TODO: 我并没有看懂这里的 rotation 和 translate 是计算原理,如果有高手知道,请赐教。

相关推荐
繁依Fanyi1 小时前
Animaster:一次由 CodeBuddy 主导的 CSS 动画编辑器诞生记
android·前端·css·编辑器·codebuddy首席试玩官
奔跑吧 android3 小时前
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
android·bluetooth·bt·aosp13·storagemodule
田一一一7 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
androidwork11 小时前
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
android·kotlin
田一一一12 小时前
Android framework 中间件开发(二)
android·中间件·framework
追随远方12 小时前
FFmpeg在Android开发中的核心价值是什么?
android·ffmpeg
神探阿航13 小时前
HNUST湖南科技大学-安卓Android期中复习
android·安卓·hnust
千里马-horse15 小时前
android vlc播放rtsp
android·media·rtsp·mediaplayer·vlc
難釋懷15 小时前
Android开发-文本输入
android·gitee
志存高远6617 小时前
(面试)Android各版本新特性
android