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 是计算原理,如果有高手知道,请赐教。

相关推荐
y = xⁿ18 分钟前
MySQL八股知识合集
android·mysql·adb
andr_gale1 小时前
04_rc文件语法规则
android·framework·aosp
祖国的好青年2 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴2 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭3 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首3 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil4 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙4 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白4 小时前
如何项目发布到github上
android·vue.js