【Android 13源码分析】屏幕旋转-2

忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。

-- 服装学院的IT男【Android 13源码分析】

上一篇看到旋转的最初的一些处理,这个时候屏幕上有一个截图的图层挡住。

本篇会设计到屏幕旋转后,新的 Configuration 生成及派发的流程,但是 Configuration 不是重点。

但是需要提一个点,Configuration 是存储设置的,但根据代码分析发现不是全局唯一的,各个进程,或者各个容器容器类,都会有自己的 Configuration 。正常来说是应该和手机设备的 Configuration 一致,但是也有不一样的情况,比如单独对应用设置语言的时候。 关于 Configuration 具体的内容还是很多的,本篇可以以黑盒的概念去阅读,不影响主流程分析,后续会想洗单独介绍一下 Configuration 。

本篇的主要调用链如下:

arduino 复制代码
DisplayContent::sendNewConfiguration
    DisplayContent::updateDisplayOverrideConfigurationLocked
        Configuration::init
        DisplayContent::computeScreenConfiguration  -- 计算出最新的Configuration
            DisplayContent::updateDisplayAndOrientation  -- 返回新的DisplayInfo
        DisplayContent::updateDisplayOverrideConfigurationLocked    -- 更新全局Configuration(重点方法)
            ActivityTaskManagerService::updateGlobalConfigurationLocked       -- 更新Configuration
                ConfigurationContainer::getGlobalConfiguration          -- 拿到之前的Configuration
                Configuration::updateFrom                         -- 更新到Configuration
                WindowProcessController::onConfigurationChanged  -- 进程更新Configuration
                    WindowProcessController::updateConfiguration
                        WindowProcessController::dispatchConfiguration
                            ConfigurationChangeItem::init       -- 构建执行,后续由应用进程处理
                WindowContainer::onConfigurationChanged         -- 层级树更新Configuration
                    ConfigurationContainer::onConfigurationChanged
                        RootWindowContainer::dispatchConfigurationToChild
                            DisplayContent::performDisplayOverrideConfigUpdate
                            .....忽略
                                ScreenRotationAnimation::setRotation  -- 移动截图Surface
                                WindowState::onResize       -- 最终会到窗口的 onResize
            ActivityTaskManagerService::ensureConfigAndVisibilityAfterUpdate    -- 确保应用正确的显示
                ActivityRecord::ensureActivityConfiguration
                    ActivityRecord::shouldRelaunchLocked  -- 判断是否重启
                        ActivityRecord::startFreezingScreenLocked -- 开始冻屏
                        ActivityRecord::relaunchActivityLocked  -- 重启Activity
                            ActivityRelaunchItem::init          -- 重启事务
                RootWindowContainer::ensureActivitiesVisible    -- 常见ensureActivitiesVisible流程
            ActivityTaskManagerService::continueWindowLayout                    -- 触发一次layout

前面说了,各个进程,容器的 Configuration 不完全一致,屏幕旋转后计算出一个新的设备 Configuration 后,会传递到各个进程,容器树的成员再根据自己的状况,结合传递过来新的 Configuration 计算更新自己的 Configuration 。然后各个地方都有了自己最新的 Configuration 。

1. 准备新的 Configuration

本篇接着上篇末尾的 DisplayContent::sendNewConfiguration 继续分析

arduino 复制代码
# DisplayContent

    void sendNewConfiguration() {
        // 这个一般正常都是true
        if (!isReady()) {
            return;
        }
        // 接上篇末尾的分析,在DisplayRotation::continueRotation 设置为了false
        if (mDisplayRotation.isWaitingForRemoteRotation()) {
            return;
        }
        // 更新Configuration
        final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
        if (configUpdated) {
            return;
        }
        // 正常这里都是有配置改变的,所以不会执行到后面的逻辑
        ......
    }
scss 复制代码
# DisplayContent
    boolean updateDisplayOverrideConfigurationLocked() {
        ......
        // 创建一个新的Configuration对象
        Configuration values = new Configuration();
        // 计算配置
        computeScreenConfiguration(values);

        mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
                ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
                mDisplayId));

        Settings.System.clearConfiguration(values);
        // 更新
        updateDisplayOverrideConfigurationLocked(values, null /* starting */,
                false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
        return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
    }

这里先创建一个新的 Configuration ,这个时候他是空的,后面肯定会进行数据填充,填充的依据就是基于设备之前的配置,然后再把当前操作的修改进行更新,比如当前分析的是屏幕旋转, 那最终的操作无非就基于之前的配置,然后把最新的旋转角度修改过去,就得到了目前最新的设备配置,然后在应用到手机上。

1.1 计算 Configuration

arduino 复制代码
# DisplayContent
    void computeScreenConfiguration(Configuration config) {
        // 重点* 更新屏幕方向后,获取最新的DisplayInfo
        final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
        final int dw = displayInfo.logicalWidth;
        final int dh = displayInfo.logicalHeight;
        // 宽高设置到临时变量
        mTmpRect.set(0, 0, dw, dh);
        // 开始把数据更新到Configuration中
        config.windowConfiguration.setBounds(mTmpRect);
        config.windowConfiguration.setMaxBounds(mTmpRect);
        ......// 忽略一堆的config设置
        config.navigation = Configuration.NAVIGATION_NONAV;
        ......// 忽略一堆的config设置
        mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
    }

这里会根据之前没有改变的配置项,已经修改后最新的配置项,把最新的配置信息填充到参数 config 中,经过这一步后,config 下的属性就是最新的了

看一下 DisplayInfo 的更新。

1.1.1 更新 DisplayInfo

ini 复制代码
# DisplayContent
    private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
        // Use the effective "visual" dimensions based on current rotation
        // 获取到最新的旋转
        final int rotation = getRotation();
        // 如果旋转了 90或者270度,说明横竖屏切换了,则屏幕宽高需要交互
        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
        final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
        final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;

        // Update application display metrics.
        // 刘海屏,圆角的一些计算
        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
        final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
        final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);

        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
                displayCutout);
        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
                displayCutout);
        // 根据最新的的配置到mDisplayInfo下
        mDisplayInfo.rotation = rotation;
        mDisplayInfo.logicalWidth = dw;
        mDisplayInfo.logicalHeight = dh;
        ......// 忽略其他mDisplayInfo的设置
        // DisplayInfo 数据改变了,做响应处理
        onDisplayInfoChanged();

        return mDisplayInfo;
    }

DisplayContent::updateDisplayAndOrientation方法看着就是根据最新的角度更新了 DisplayInfo 的信息, 经过这个方法后 DisplayInfo 内存储的就是最新的屏幕信息了,在这步之前,还是旋转前的信息

csharp 复制代码
# DisplayContent
    @Rotation
    int getRotation() {
        return mDisplayRotation.getRotation();
    }

最开始的时候就知道 最新的角度被保存在了 DisplayRotation 下的 mRotation 中,这个方法就是获取的。

在这一小节先是 更新了 DisplayInfo ,然后把配置更新到了新创建的 Configuration 中。

2. 更新全局配置,并做UI处理

经过前面的处理,现在有了一个新的 Configuration ,就需要更新到设备上,注意这个方法是带参数的,和 1.1 开始分析的不带参数是同名函数。

这个带参数的方法,是全局配置改变都会执行的,除了横竖屏,比如设置语言,也会执行到这边。

不过debug发现设置语言传进来的参数 values 是个null。 当前旋转场景根据前面的分析肯定是有值的。

ini 复制代码
# DisplayContent
    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
            ActivityRecord starting, boolean deferResume,
            ActivityTaskManagerService.UpdateConfigurationResult result) {
        // 标志位,看看哪些设置改变了
        int changes = 0;
        boolean kept = true;

        mAtmService.deferWindowLayout();
        try {
            if (values != null) {
                if (mDisplayId == DEFAULT_DISPLAY) {
                    // Override configuration of the default display duplicates global config, so
                    // we're calling global config update instead for default display. It will also
                    // apply the correct override config.
                    // 1. 更改全局配置
                    changes = mAtmService.updateGlobalConfigurationLocked(values,
                            false /* initLocale */, false /* persistent */,
                            UserHandle.USER_NULL /* userId */);
                } else {
                    changes = performDisplayOverrideConfigUpdate(values);
                }
            }

            if (!deferResume) {
                // 2. 确保设置更新后正确的显示
                kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
            }
        } finally {
            // 3. 执行一次layout
            mAtmService.continueWindowLayout();
        }

        if (result != null) {
            result.changes = changes;
            result.activityRelaunched = !kept;
        }
        return kept;
    }

这里有2条主线:

    1. 更新全局的配置
    1. 配置更新后,做相应的UI处理

2.1 更新配置

java 复制代码
# ActivityTaskManagerService

    // 临时存储Configuration
    private final Configuration mTempConfig = new Configuration();
    // 更新默认的全局配置并通知到各个监听
    /** Update default (global) configuration and notify listeners about changes. */
    int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
            boolean persistent, int userId) {
        // 获取到现在的设置,保存到临时变量中
        mTempConfig.setTo(getGlobalConfiguration());
        // 重点* 1. updateFrom 方法会对把传进来的Configuration 更新到 mTempConfig中
        // 并返回更新了哪些设置项
        final int changes = mTempConfig.updateFrom(values);
        if (changes == 0) {
            // 如果没有更新,则返回
            return 0;
        }
        // 日志
        ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
            + "to: %s", values);
        ......
        // 通知进程Configuration改变了
        SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
        for (int i = pidMap.size() - 1; i >= 0; i--) {
            final int pid = pidMap.keyAt(i);
            final WindowProcessController app = pidMap.get(pid);
            // 日志
            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
                    + "config %s", app.mName, mTempConfig);
            // 重点* 2. 通知进程更新Configuration
            app.onConfigurationChanged(mTempConfig);
        }
        ......
        // 重点* 3. 层级树更新Configuration
        mRootWindowContainer.onConfigurationChanged(mTempConfig);
        return changes;
    }

这个方法是 Configuration 修改的核心方法,分为以下3步:

    1. 得到一个新的 Configuration ,并记录那些设置项发生了改变
    1. 把新的 Configuration 传递到各个应用
    1. 把新的 Configuration 传递到层级树

2.1.1 获取到新的 Configuration

这是三步的第一步,先要得到一个最新的 Configuration 对象,这个对象后续也将传递给各个进程和窗口树使用。 首先是通过 getGlobalConfiguration 方法获取到了当前使用的 Configuration 对象

csharp 复制代码
# ActivityTaskManagerService
    
    Configuration getGlobalConfiguration() {
        // Return default configuration before mRootWindowContainer initialized, which happens
        // while initializing process record for system, see {@link
        // ActivityManagerService#setSystemProcess}.
        return mRootWindowContainer != null ? mRootWindowContainer.getConfiguration()
                : new Configuration();
    }

// 正常情况 mRootWindowContainer 是不为空的,这边返回的其实就是设备之前的 Configuration 。 getConfiguration 方法是 定义在 WindowContainer 父类 ConfigurationContainer 下的一个方法

csharp 复制代码
# ConfigurationContainer

    /**
     * Contains full configuration applied to this configuration container. Corresponds to full
     * parent's config with applied {@link #mResolvedOverrideConfiguration}.
     */
    private Configuration mFullConfiguration = new Configuration();

    @NonNull
    public Configuration getConfiguration() {
        return mFullConfiguration;
    }

ConfigurationContainer 下定义了好几个 Configuration 对象,都有各自的意义 。 这里知道 mFullConfiguration 才是当前容器配置保存的变量就好了。

总之,就是获取到了设置之前的 Configuration 对象,然后保存到临时变量 mTempConfig 中。

然后执行 "final int changes = mTempConfig.updateFrom(values);" 看一下 Configuration::updateFrom 方法

less 复制代码
# Configuration
    /**
     * Copies the fields from delta into this Configuration object, keeping
     * track of which ones have changed. Any undefined fields in {@code delta}
     * are ignored and not copied in to the current Configuration.
     *
     * @return a bit mask of the changed fields, as per {@link #diff}
     */
    public @Config int updateFrom(@NonNull Configuration delta) {
        // 定义个flag,看看哪些设置项改变了
        int changed = 0;
        ......
        if (delta.orientation != ORIENTATION_UNDEFINED
                && orientation != delta.orientation) {
            changed |= ActivityInfo.CONFIG_ORIENTATION;
            orientation = delta.orientation;
        }
        return changed;
        ......
    }

看注释就是把 delta 改变的复制到当前的 Configuration 对象中,并且记录那些改变了。 这个方法代码很长,是把 Configuration 下的每个属性都做了判断,但是当前只关心屏幕方法,所以只保留了一小块代码。 如果新的 Configuration 屏幕方向和当前的不一样,则说明改变了,就更新过来,并且记录到 changed 中,最终这个方法返回 changed。

mTempConfig 现在代表着最新的 Configuration ,稍后会给应用进程,以及层级树使用。

2.1.2 进程更新Configuration

WindowProcessController 的父类也是 ConfigurationContainer

scss 复制代码
# WindowProcessController
    @Override
    public void onConfigurationChanged(Configuration newGlobalConfig) {
        // 执行父类的方法更新 Configuration
        super.onConfigurationChanged(newGlobalConfig);
        updateConfiguration();
    }
    private void updateConfiguration() {
        final Configuration config = getConfiguration();
        ......
        dispatchConfiguration(config);
    }

    void dispatchConfiguration(Configuration config) {
        ......
        // 日志
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
                config);
        try {
            config.seq = mAtm.increaseConfigurationSeqLocked();
            // 构建执行 ConfigurationChangeItem 
            mAtm.getLifecycleManager().scheduleTransaction(mThread,
                    ConfigurationChangeItem.obtain(config));
            setLastReportedConfiguration(config);
        } catch (Exception e) {
            Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
        }
    }

看一眼这个 ConfigurationChangeItem

typescript 复制代码
# ConfigurationChangeItem

    @Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        client.handleConfigurationChanged(mConfiguration);
    }

这边的逻辑就是把新的 Configuration 发生到各个进程,后面的逻辑就在应用进程的 ActivityThread 处理了。

2.1.3 层级树更新 Configuration

在 2.1.1 小节获取 Configuration 的时候看到最终其实是从 ConfigurationContainer 下取的 mFullConfiguration 变量,由此也能知道原来每个容器类都有自己的一个 Configuration 变量。

容器更新自己的 Configuration 流程是一个从上往下的流程,从 RootWindowContainer 开始接收一个新的 Configuration ,然后不断派发到层级树到各个子容器,子容器根据父容器传递过来的这个新的 Configuration 再结合自己的情况更新出自己的 Configuration 。

这部分的逻辑其实应该是 Configuration 专题的,所以不详细介绍各个容器是如何更新自己 Configuration 的,但是当前场景这条调用链有2个需要注意的点:

    1. 再次调用 setRotation 来设置截图图层的位置
    1. 通知窗口进行 resize 操作。

2.1.3.1 setRotation

ScreenRotationAnimation::setRotation 方法之前看过一次,但是由于当时新的旋转角度没更新到 DisplayInfo 中,所以相当于没有执行什么,但是这次 DisplayContent::sendNewConfiguration 触发的调用连前面看 DisplayInfo 已经更新了,所以这次这次触发 ScreenRotationAnimation::setRotation 方法时,是会真正移动 Surface 位置的。

这次的调用链如下:

less 复制代码
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation.setRotation(ScreenRotationAnimation.java:326)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.applyRotation(DisplayContent.java:2025)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.applyRotationAndFinishFixedRotation(DisplayContent.java:6008)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.onRequestedOverrideConfigurationChanged(DisplayContent.java:5978)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.performDisplayOverrideConfigUpdate(DisplayContent.java:5948)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.dispatchConfigurationToChild(RootWindowContainer.java:640)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.dispatchConfigurationToChild(RootWindowContainer.java:169)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.ConfigurationContainer.onConfigurationChanged(ConfigurationContainer.java:152)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.WindowContainer.onConfigurationChanged(WindowContainer.java:519)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.ActivityTaskManagerService.updateGlobalConfigurationLocked(ActivityTaskManagerService.java:4360)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:5918)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:5894)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayContent.sendNewConfiguration(DisplayContent.java:1468)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayRotation.continueRotation(DisplayRotation.java:607)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayRotation.-$$Nest$mcontinueRotation(Unknown Source:0)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayRotation$2.lambda$continueRotateDisplay$0(DisplayRotation.java:233)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.server.wm.DisplayRotation$2$$ExternalSyntheticLambda0.accept(Unknown Source:10)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.internal.util.function.pooled.PooledLambdaImpl.doInvoke(PooledLambdaImpl.java:295)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.internal.util.function.pooled.PooledLambdaImpl.invoke(PooledLambdaImpl.java:204)
04-21 16:11:07.823  6355  6376 E biubiubiu: 	at com.android.internal.util.function.pooled.OmniFunction.run(OmniFunction.java:97)

2.1.3.2 onResize

这里的代码调用链也是 DisplayContent::sendNewConfiguration 触发的,当前主要分析的是横竖屏切换后,屏幕宽高肯定是是改变了宽高的(交换),那窗口大小是要改变的,也就是说会执行WindowState::onResize

所有的调用链以及部分解释在下面这张图里。

看一下 WindowState::onResize

ini 复制代码
# WindowState
    @Override
    void onResize() {
        final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;
        if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
            ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
            resizingWindows.add(this);
        }
        if (isGoneForLayout()) {
            mResizedWhileGone = true;
        }

        super.onResize();
    }

这里有log打印

    1. 需要符合条件,才会进入if逻辑,最重要的就是 mHasSurface
    1. 把需要 resize 的窗口添加到 resizingWindows 集合,后续统一执行 resize 操作

在layout的时候会触发 RootWindowContainer::handleResizingWindows 对需要的窗口进行 resize 操作

csharp 复制代码
# RootWindowContainer
    private void handleResizingWindows() {
        // 遍历集合中的窗口
        for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
            WindowState win = mWmService.mResizingWindows.get(i);
            if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
                // Don't remove this window until rotation has completed and is not waiting for the
                // complete configuration.
                continue;
            }
            // 窗口执行resize
            win.reportResized();
            mWmService.mResizingWindows.remove(i);
        }
    }

配置相关的更新到这就结束了。

2.2 配置更新后确保显示正常

这部分的逻辑就涉及到 Activity 相关了,也是黑屏容易出现问题的部分。

java 复制代码
# ActivityTaskManagerService
    // Applies latest configuration and/or visibility updates if needed
    // 如果需要,应用最新的配置和/或可见性更新
    boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
        boolean kept = true;
        //  拿到最顶层的task
        final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
        // mainRootTask is null during startup.
        if (mainRootTask != null) {
            if (changes != 0 && starting == null) {
                // If the configuration changed, and the caller is not already
                // in the process of starting an activity, then find the top
                // activity to check if its configuration needs to change.
                // 拿到顶部的 ActivityRecord
                starting = mainRootTask.topRunningActivity();
            }

            if (starting != null) {
                // 处理正确的 Configuration
                kept = starting.ensureActivityConfiguration(changes,
                        false /* preserveWindow */);
                // And we need to make sure at this point that all other activities
                // are made visible with the correct configuration.
                // 处理正确的可见效 
                mRootWindowContainer.ensureActivitiesVisible(starting, changes,
                        !PRESERVE_WINDOWS);
            }
        }

        return kept;
    }

这里又分2步:

    1. ensureActivityConfiguration 来确保Activity 有正确的 Configuration ,还有判断是否要重启 Activity
    1. 执行一次 ensureActivitiesVisible 来处理显示逻辑

2.2.1 Activity 处理(重启还是回调)

我当前的 demo 没有在清单文件处理,所以走的是重启逻辑。

arduino 复制代码
# ActivityRecord
    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
            boolean ignoreVisibility) {
                
                ......//忽略其他代码,有很多的ProtoLog
                // 计算出改变
                
                final int changes = getConfigurationChanges(mTmpConfig);
                // 这里会输出这次配置的改变,由此可判断当前Activity清单文件是否配置了忽略,要不要走重启流程
                ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration changes for %s, "
                        + "allChanges=%s", this, Configuration.configurationDiffToString(changes));
                ......
                // 重点 根据清单文件的配置判断是不是走重启逻辑
                if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
                    // Aha, the activity isn't handling the change, so DIE DIE DIE.
                    configChangeFlags |= changes;
                    // 冻屏
                    startFreezingScreenLocked(globalChanges);
                    ......
                    if (mState == PAUSING) {
                        ......
                    } else {
                        // 根据日志走的这
                        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
                                this);
                        if (!mVisibleRequested) {
                            ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
                                    + "activity %s called by %s", this, Debug.getCallers(4));
                        }
                        // 重启处理
                        relaunchActivityLocked(preserveWindow);
                    }
                    return false;
                }
                ......
            }

比如我当前是 relaunch 的流程,这个方法会打印下面这些日志,这也是判断是否走了重启流程重要关注的部分。

scss 复制代码
04-21 16:11:07.832  6355  6376 V WindowManager: Ensuring correct configuration: ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}
04-21 16:11:07.832  6355  6376 V WindowManager: Configuration changes for ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}, allChanges={CONFIG_ORIENTATION, CONFIG_SCREEN_SIZE}
04-21 16:11:07.832  6355  6376 V WindowManager: Checking to restart com.google.android.dialer.extensions.GoogleDialtactsActivity: changed=0x480, handles=0x3, mLastReportedConfiguration={mGlobalConfig={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw411dp w826dp h371dp 280dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1600, 720) mAppBounds=Rect(70, 0 - 1516, 720) mMaxBounds=Rect(0, 0 - 1600, 720) mDisplayRotation=ROTATION_90 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_90} s.120 fontWeightAdjustment=0} mOverrideConfig={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw411dp w826dp h371dp 280dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1600, 720) mAppBounds=Rect(70, 0 - 1516, 720) mMaxBounds=Rect(0, 0 - 1600, 720) mDisplayRotation=ROTATION_90 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_90} s.2 fontWeightAdjustment=0}}
04-21 16:11:07.832  6355  6376 I WindowManager: Set freezing of Token{cae837 ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}}: visible=true freezing=false visibleRequested=true. java.lang.RuntimeException
04-21 16:11:07.832  7881  7881 D TransactionExecutor: tId:1663825981 End resolving transaction
04-21 16:11:07.832  6355  6376 V WindowManager: Config is relaunching ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}

2.2.1.1 Activity 重启

java 复制代码
# ActivityRecord

    private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
        int configChanged = info.getRealConfigChanged();
        boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);

        // Override for apps targeting pre-O sdks
        // If a device is in VR mode, and we're transitioning into VR ui mode, add ignore ui mode
        // to the config change.
        // For O and later, apps will be required to add configChanges="uimode" to their manifest.
        if (info.applicationInfo.targetSdkVersion < O
                && requestedVrComponent != null
                && onlyVrUiModeChanged) {
            configChanged |= CONFIG_UI_MODE;
        }

        return (changes&(~configChanged)) != 0;
    }

这个方法其实就是根据清单文件配置 Activity 忽略哪些设置的改变而不重启,当前案例就是默认的配置,所以会走重启流程。

然后根据日志走的是 ActivityRecord::relaunchActivityLocked

scss 复制代码
# ActivityRecord
    void relaunchActivityLocked(boolean preserveWindow) {
        ......
        // 正常为true
        final boolean andResume = shouldBeResumed(null /*activeActivity*/);
        ......
        // 这里的events 日志很重要,可以知道到底走的哪个逻辑
        if (andResume) {
            // wm_relaunch_resume_activity
            EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
                    task.mTaskId, shortComponentName);
        } else {
            // wm_resume_activity
            EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
                    task.mTaskId, shortComponentName);
        }
        // 重点 开始冻屏
        startFreezingScreenLocked(0);
        try {
            // 状态改变
            ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
            forceNewConfig = false;
            // 设置 allDrawn 和 mLastAllDrawn 为false
            startRelaunching();
            // 构建执行 ActivityRelaunchItem 事务
            final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
                    pendingNewIntents, configChangeFlags,
                    new MergedConfiguration(getProcessGlobalConfiguration(),
                            getMergedOverrideConfiguration()),
                    preserveWindow);
            final ActivityLifecycleItem lifecycleItem;
            if (andResume) {
                lifecycleItem = ResumeActivityItem.obtain(isTransitionForward());
            } else {
                lifecycleItem = PauseActivityItem.obtain();
            }
            final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
            transaction.addCallback(callbackItem);
            transaction.setLifecycleStateRequest(lifecycleItem);
            // 执行
            mAtmService.getLifecycleManager().scheduleTransaction(transaction);
            // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
            // request resume if this activity is currently resumed, which implies we aren't
            // sleeping.
        } catch (RemoteException e) {
            ProtoLog.i(WM_DEBUG_STATES, "Relaunch failed %s", e);
        }
        if (andResume) {
                // 日志
                ProtoLog.d(WM_DEBUG_STATES, "Resumed after relaunch %s", this);
                results = null;
                newIntents = null;
                mAtmService.getAppWarningsLocked().onResumeActivity(this);
            } else {
                removePauseTimeout();
                setState(PAUSED, "relaunchActivityLocked");
        }
        // 从stop集合移除
        mTaskSupervisor.mStoppingActivities.remove(this);
        ......
    }

2.2.2 执行一次 ensureActivitiesVisible

这个里是常见的 ensureActivitiesVisible 流程,目的就是确保当前屏幕上正确的 Activity 显示,这流程说通用且常见的。 在【Activity启动流程-2】 1.1.2 ensureActivitiesVisible分支 介绍过了

3. 总结

这部分处理的事情还是挺多的

    1. 计算新的 DisplayInfo ,Configuration
    1. 把新计算的 Configuration 分发到各个进程和容器,让他们根据新的 Configuration 更新自己的 Configuration
    1. 移动截图的图层,并处理窗口的 resize 逻辑
    1. 冻屏
    1. 常见的判断 Activity 旋转后是否需要走重启流程
    1. 在执行一次 ensureActivitiesVisible 流程确保显示正确

下一篇开始看旋转动画的相关逻辑

相关推荐
CYRUS STUDIO11 分钟前
ARM64汇编寻址、汇编指令、指令编码方式
android·汇编·arm开发·arm·arm64
weixin_449310841 小时前
高效集成:聚水潭采购数据同步到MySQL
android·数据库·mysql
Zender Han1 小时前
Flutter自定义矩形进度条实现详解
android·flutter·ios
白乐天_n3 小时前
adb:Android调试桥
android·adb
姑苏风7 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k10 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1011 小时前
JavaWeb项目-----博客系统
android
风和先行11 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.12 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰13 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder