Android orientation更新源码探索

1,119 阅读4分钟

本文基于AndroidS源码分析

Orientation和Rotation的区别

orientation标识此时界面以什么方向显示,可以理解为是横屏还是竖屏

rotation旋转方向是指界面相对于默认情况顺时针旋转的角度(平板一般默认横屏,而小屏幕设备默认竖屏)

orientation

//core/java/android/content/res/Configuration.java
    @IntDef(prefix = {"ORIENTATION_"}, value = {
            ORIENTATION_UNDEFINED,
            ORIENTATION_PORTRAIT,
            ORIENTATION_LANDSCAPE,
            ORIENTATION_SQUARE
    })
    public static final int ORIENTATION_UNDEFINED = 0;
    public static final int ORIENTATION_PORTRAIT = 1;
    public static final int ORIENTATION_LANDSCAPE = 2;
    @Deprecated public static final int ORIENTATION_SQUARE = 3;
    
// core/java/android/content/pm/ActivityInfo.java
    @IntDef(prefix = { "SCREEN_ORIENTATION_" }, value = {
            SCREEN_ORIENTATION_UNSET,
            SCREEN_ORIENTATION_UNSPECIFIED,
            SCREEN_ORIENTATION_LANDSCAPE,
            SCREEN_ORIENTATION_PORTRAIT,
            SCREEN_ORIENTATION_USER,
            SCREEN_ORIENTATION_BEHIND,
            SCREEN_ORIENTATION_SENSOR,
            SCREEN_ORIENTATION_NOSENSOR,
            SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
            SCREEN_ORIENTATION_SENSOR_PORTRAIT,
            SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
            SCREEN_ORIENTATION_REVERSE_PORTRAIT,
            SCREEN_ORIENTATION_FULL_SENSOR,
            SCREEN_ORIENTATION_USER_LANDSCAPE,
            SCREEN_ORIENTATION_USER_PORTRAIT,
            SCREEN_ORIENTATION_FULL_USER,
            SCREEN_ORIENTATION_LOCKED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScreenOrientation {}

ActivityInfo中的screenOrientation用于记录app强制设定的方向或旋转模式,

而Configuration.java中的orientation值可以认为只有横屏或竖屏,

当app具体渲染时候只需要区分横屏竖屏即可,不必在意具体旋转方向。

rotation

// core/java/android/view/Surface.java
    @IntDef(prefix = { "ROTATION_" }, value = {
            ROTATION_0,
            ROTATION_90,
            ROTATION_180,
            ROTATION_270
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Rotation {}
    public static final int ROTATION_0 = 0;
    public static final int ROTATION_90 = 1;
    public static final int ROTATION_180 = 2;
    public static final int ROTATION_270 = 3;

旋转流程:从Sensor到onConfigurationChanged

旋转锁定开关

系统的旋转锁定开关变化后会修改SettingsProvider数据,触发DisplayRotation的ContentObserver监听

// services/core/java/com/android/server/wm/DisplayRotation.java
    private boolean updateSettings() {
        final ContentResolver resolver = mContext.getContentResolver();
        boolean shouldUpdateRotation = false;

        synchronized (mLock) {
            boolean shouldUpdateOrientationListener = false;

            .... 
            // 省略的这段代码读取系统旋转相关设置确定是否需要启用OrientationListener监听sensor
            
            if (shouldUpdateOrientationListener) {
                // updateOrientationListenerLw方法回去真正注册sensor监听
                updateOrientationListenerLw(); // Enable or disable the orientation listener.
            }
            ...
        }

        return shouldUpdateRotation;
    }

发生屏幕旋转

发生屏幕旋转时,WindowOrientationListener的onSensorChanged会处理接收到的sensor event并计算出新的proposedRotation,进入DisplayRotation$OrientationListener$UpdateRunnable的run方法。

// services/core/java/com/android/server/wm/DisplayRotation.java
  public void run() {
      // Send interaction power boost to improve redraw performance.
      mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
      if (isRotationChoicePossible(mCurrentAppOrientation)) {
          final boolean isValid = isValidRotationChoice(mRotation);
          sendProposedRotationChangeToStatusBarInternal(mRotation, isValid);
      } else {
          mService.updateRotation(false /* alwaysSendConfiguration */,
                  false /* forceRelayout */);
      }
  }

关键方法:WMS#updateRotationUnchecked

  1. 方法首先调用displayContent#updateRotationUncheckedLocked()

->mDisplayRotation.updateRotationUnchecked进行rotation的更新。

  • 在这个方法中将通知container对于rotation的感知然后根据顶部的activity开始冻屏动画或者无缝动画

如果该方法返回值为true则必须调用DisplayContent#sendNewConfiguration完成屏幕旋转或解冻

  • 除此之外,updateRotationUnchecked方法还会调用

rotation = rotationForOrientation(lastOrientation, oldRotation)来计算新的rotation。

  1. rotationForOrientation是一个很长的方法,这个方法非常重要:前面的if-else主要是结合当前系统显示模式、旋转模式以及sensor监测到的旋转方向给出一个preferredRotation,后面的switch-case则是根据当前的orientation决定是否可以使用preferredRotation。
    /**
     * 给定一个方向常数,将传感器、DOCK模式、旋转锁定和其他因素考虑在内,返回适当的表面旋转。
     */
    @VisibleForTesting
    @Surface.Rotation
    int rotationForOrientation(@ScreenOrientation int orientation,
            @Surface.Rotation int lastRotation) {
        ...
        // 方向锁定
        if (isFixedToUserRotation()) {
            return mUserRotation;
        }
        // 传感器方向未确定
        int sensorRotation = mOrientationListener != null
                ? mOrientationListener.getProposedRotation() // may be -1
                : -1;
        if (sensorRotation < 0) {
            sensorRotation = lastRotation;
        }
      
        final int lidState = mDisplayPolicy.getLidState();
        final int dockMode = mDisplayPolicy.getDockMode();
        final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
        final boolean carDockEnablesAccelerometer =
                mDisplayPolicy.isCarDockEnablesAccelerometer();
        final boolean deskDockEnablesAccelerometer =
                mDisplayPolicy.isDeskDockEnablesAccelerometer();

        final int preferredRotation;
        if (!isDefaultDisplay) {
            // 对于外接显示,我们忽略了像显示传感器、对接模式和旋转锁定
            preferredRotation = mUserRotation;
        } else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
            // 翻盖开关打开时,强制旋转时忽略传感器。
            preferredRotation = mLidOpenRotation;
        } else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
                && (carDockEnablesAccelerometer || mCarDockRotation >= 0)) {
            // 车载模式如果明确指定启用传感器使用传感器方向
            preferredRotation = carDockEnablesAccelerometer ? sensorRotation : mCarDockRotation;
        } else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
                || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
                || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
                && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)) {
            // 在deskdock模式除非直接启用否则忽略sensor
            preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation;
        } else if ((hdmiPlugged || WFDRegisterStub.getWifiDisplayConnected()) && mDemoHdmiRotationLock) {
            // 插入HDMI或无线头屏时,如果HDMI旋转锁定启用,则使用这个rotation
            preferredRotation = mDemoHdmiRotation;
        } else if (hdmiPlugged && dockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
                && mUndockedHdmiRotation >= 0) {
            // 插入HDMI并且在configuratin里指定了undocked orientation时忽略sensor
            preferredRotation = mUndockedHdmiRotation;
        } else if (mDemoRotationLock) {
            // 启用demo旋转锁时用mDemoRotation方向
            preferredRotation = mDemoRotation;
        } else if (mDisplayPolicy.isPersistentVrModeEnabled()) {
            // VR时使用竖屏方向
            preferredRotation = mPortraitRotation;
        } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            // 应用锁定方向
            preferredRotation = lastRotation;
        } else if (!mSupportAutoRotation) {
            // 不支持自动旋转 这里直接退出
            preferredRotation = -1;
        } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
                        && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
                                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
                || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
            // 否则,仅在应用程序请求或默认启用USER或未指定模式时才使用传感器。
            if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
                // Can't read this during init() because the context doesn't have display metrics at
                // that time so we cannot determine tablet vs. phone then.
                mAllowAllRotations = mContext.getResources().getBoolean(
                        R.bool.config_allowAllRotations)
                                ? ALLOW_ALL_ROTATIONS_ENABLED
                                : ALLOW_ALL_ROTATIONS_DISABLED;
            }
            if (sensorRotation != Surface.ROTATION_180
                    || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
                preferredRotation = sensorRotation;
            } else {
                preferredRotation = lastRotation;
            }
        } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
                && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
                && orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
                && orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
                && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
            // 应用旋转锁。不适用于NOSENSOR或一些特定的旋转。
            preferredRotation = mUserRotation;
        } else {
            // No overriding preference.
            // We will do exactly what the application asked us to do.
            preferredRotation = WFDRegisterStub.selectPreferredRotation();
        }

        // 下面的逻辑:根据当前的orientation决定是否可以使用preferredRotation
        switch (orientation) {
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
                // Return portrait unless overridden.
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                return mPortraitRotation;

            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
                // Return landscape unless overridden.
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                return mLandscapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
                // Return reverse portrait unless overridden.
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                return mUpsideDownRotation;

            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
                // Return seascape unless overridden.
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                return mSeascapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
                // Return either landscape rotation.
                if (isLandscapeOrSeascape(preferredRotation)) {
                    return preferredRotation;
                }
                if (isLandscapeOrSeascape(lastRotation)) {
                    return lastRotation;
                }
                return mLandscapeRotation;

            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
                // Return either portrait rotation.
                if (isAnyPortrait(preferredRotation)) {
                    return preferredRotation;
                }
                if (isAnyPortrait(lastRotation)) {
                    return lastRotation;
                }
                return mPortraitRotation;

            default:
                // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,
                // just return the preferred orientation we already calculated.
                if (preferredRotation >= 0) {
                    return preferredRotation;
                }
                return Surface.ROTATION_0;
        }
    }

configuration更新:WMS->ATMS->Activity

前面的流程主要是用来更新DisplayRotation.mRotation,如果rotation有更新,则sendNewConfiguration()通知DisplayContent更新configuration。configuration更新流程由DisplayContent#sendNewConfiguration开始,调用到ActivityTaskManagerService,最终通过ActivityRecord将config组装成ActivityConfigurationChangeItem通知到AppThread端:

        mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                ActivityConfigurationChangeItem.obtain(config));

configuration更新流程的源码本文先不做解析,上时序图:

1053fc4efb939dc6ff9da859918d9c2f.png