Android R WindowManagerService模块(6) 屏幕旋转流程(1)

5,135 阅读11分钟

1.概述

Android中提供了非常灵活的屏幕旋转功能,系统可以根据各种传感器自动旋转,应用也可以根据场景自行设置Activity的方向,来覆盖系统的行为。从这篇文章开始,将对整个屏幕旋转流程进行详细的分析。

Android为应用提供了一系列用于控制屏幕方向的值,可以在manifest文件中通过android:screenOrientation属性来设置,也可以在Activity中通过setRequestedOrientation(int requestedOrientation)方法来设置,可以设置的值和含义如下表:

字段int值android:screenOrientation属性对应值含义
ActivityInfo.SCREEN_ORIENTATION_UNSET-2/该字段不向应用开放,表示没有设置指定方向
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED-1android:screenOrientation="unspecified"默认值由系统选择方向
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE0android:screenOrientation="landscape"屏幕方向为横向
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT1android:screenOrientation="portrait"屏幕方向为纵向
ActivityInfo.SCREEN_ORIENTATION_USER2android:screenOrientation="user"用户当前的首选方向
ActivityInfo.SCREEN_ORIENTATION_BEHIND3android:screenOrientation="behind"与 Activity 栈中紧接其后的 Activity 的方向相同
ActivityInfo.SCREEN_ORIENTATION_SENSOR4android:screenOrientation="sensor"屏幕方向由设备方向传感器决定。显示方向取决于用户如何手持设备,它会在用户旋转设备时发生变化。但在默认情况下,一些设备不会旋转为所有四种可能的方向。如要支持所有这四种方向,请使用"fullSensor"。即使用户锁定基于传感器的旋转,系统仍可使用传感器
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR5android:screenOrientation="nosensor"确定屏幕方向时不考虑物理方向传感器。系统会忽略传感器,因此显示内容不会随用户手持设备的方向而旋转
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE6android:screenOrientation="sensorLandscape"屏幕方向为横向,但可根据设备传感器调整为正常或反向的横向。即使用户锁定基于传感器的旋转,系统仍可使用传感器
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT7android:screenOrientation="sensorPortrait"屏幕方向为纵向,但可根据设备传感器调整为正常或反向的纵向。即使用户锁定基于传感器的旋转,系统仍可使用传感器
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE8android:screenOrientation="reverseLandscape"屏幕方向是与正常横向方向相反的横向
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT9android:screenOrientation="reversePortrait"屏幕方向是与正常纵向方向相反的纵向
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR10android:screenOrientation="fullSensor"屏幕方向由使用 4 种方向中任一方向的设备方向传感器决定。这与 "sensor" 类似,不同之处在于无论设备在正常情况下使用哪种方向,该值均支持所有 4 种可能的屏幕方向(例如,一些设备正常情况下不使用反向纵向或反向横向,但其支持这些方向)
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE11android:screenOrientation="userLandscape"屏幕方向为横向,但可根据设备传感器和用户首选项调整为正常或反向的横向
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT12android:screenOrientation="userPortrait"屏幕方向为纵向,但可根据设备传感器和用户首选项调整为正常或反向的纵向
ActivityInfo.SCREEN_ORIENTATION_FULL_USER13android:screenOrientation="fullUser"如果用户锁定基于传感器的旋转,则其行为与 user 相同,否则,其行为与 fullSensor 相同,并且支持所有 4 种可能的屏幕方向。
ActivityInfo.SCREEN_ORIENTATION_LOCKED14android:screenOrientation="locked"将屏幕方向锁定为其当前的任意旋转方向。

详细内容可参考:Android开发者文档指南

此外,还提供了一个方向旋转角度值,其值及含义如下:

字段int值含义
Surface.ROTATION_00默认自然方向,没有旋转
Surface.ROTATION_901机头方向逆时针旋转90度
Surface.ROTATION_1802机头方向逆时针旋转180度
Surface.ROTATION_2703机头方向逆时针旋转270度

该值我们后面统一称为“屏幕方向”。这两类值会作为判断条件和结果,应用在整个流程中。

2.类结构

在Framework中,屏幕旋转功能主要是由WMS模块中的DisplayRotation对象来完成,在启动WindowManagerService过程中,创建DisplayContent对象时,会创建一个对应的DisplayRotation负责屏幕旋转逻辑,一个DisiplayContent对象对应一个DisplayRotation对象,或者说DisplayContent对象中持有一个DisplayRotation对象。

在DisplayRotation中,将获取Sensor数据并转换成具体方向旋转角度值的逻辑交给了OrientationListener对象来负责。

从整体来看,可以用一句话总结各自的职责:DisplayContent负责控制,DisplayRotation负责执行,OrientationListener负责获取数据。

image2021-2-27_9-54-50.jpg

下面就从创建DisplayRotation对象开始分析。

3.DisplayRotation的初始化

DisplayRotation()方法如下:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java

    DisplayRotation(WindowManagerService service, DisplayContent displayContent,
            DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
            Context context, Object lock) {
	// 是否支持自动旋转
        mSupportAutoRotation =
                mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
	// lid open 时指定的旋转角度
        mLidOpenRotation = readRotation(R.integer.config_lidOpenRotation);
	// 放在car dock时指定的旋转角度
        mCarDockRotation = readRotation(R.integer.config_carDockRotation);
	// 放在desk dock时指定的旋转角度
        mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
	// Hdmi连接时指定的旋转角度
        mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);

        if (isDefaultDisplay) {
            final Handler uiHandler = UiThread.getHandler();
            // 创建OrientationListener对象
            mOrientationListener = new OrientationListener(mContext, uiHandler);
            // 初始化
            mOrientationListener.setCurrentRotation(mRotation);
            // 监听SettingsProvider中的变化
            mSettingsObserver = new SettingsObserver(uiHandler);
            mSettingsObserver.observe();
        }
    }

在创建DisplayRotation对象时,进行了一些参数的初始化,其中包括:

  • 初始化了几类场景下指定的旋转角度,如在收到lid_switch open事件后、插入基座时指定的旋转角度;
  • 创建OrientationListener对象,和Sensor建立连接关系;
  • 设置SettingsProvider中相关字段的监听;

1.1.创建OrientationListener对象

屏幕旋转离不开Sensor的监听,而和Sensor打交道的业务,全权交给了OrientationListener来负责,它会获取Sensor对象、监听Sensor数据、将Sensor的数据转换成旋转角度,并通知WindowManagerService更新方向。在创建该对象时,它父类WindowOrientationListener类的构造方法中,将会获得Sensor对象和SensorEventListener对象:

//  frameworks/base/services/core/java/com/android/server/policy/WindowOrientationListener.java

    private WindowOrientationListener(Context context, Handler handler, int rate) {
        mHandler = handler;
        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
        mRate = rate;
        List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
        Sensor wakeUpDeviceOrientationSensor = null;
        Sensor nonWakeUpDeviceOrientationSensor = null;
        
        for (Sensor s : l) {
            if (s.isWakeUpSensor()) {
                wakeUpDeviceOrientationSensor = s;
            } else {
                nonWakeUpDeviceOrientationSensor = s;
            }
        }

        if (wakeUpDeviceOrientationSensor != null) {
            mSensor = wakeUpDeviceOrientationSensor;
        } else {
            mSensor = nonWakeUpDeviceOrientationSensor;
        }

        // 创建SensorEventListener对象
        if (mSensor != null) {
            mOrientationJudge = new OrientationSensorJudge();
        }
    }

OrientationJudge类作为SensorEventListener的实现类来接收Sensor事件,根据不同的Sensor会有不同的OrientationJudge对象与之匹配,之所以这样做是因为不同的Sensor上报的原始数据不同,因此需要做不同的转换才能获得最终的旋转角度值。

1.2.监听SettingsProvider字段

在涉及到方向旋转功能上,DisplayRotation中监听了以下三个SettingsProvider中的字段:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java

        void observe() {
            final ContentResolver resolver = mContext.getContentResolver();
            // 方向旋转关闭时,是否进行旋转提示
            resolver.registerContentObserver(Settings.Secure.getUriFor(
                    Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
                    UserHandle.USER_ALL);
            // 方向旋转模式
            resolver.registerContentObserver(Settings.System.getUriFor(
                    Settings.System.ACCELEROMETER_ROTATION), false, this,
                    UserHandle.USER_ALL);
            // 不使用ACCELEROMETER_ROTATION且Activity没有优先设置的旋转值时,使用该值
            resolver.registerContentObserver(Settings.System.getUriFor(
                    Settings.System.USER_ROTATION), false, this,
                    UserHandle.USER_ALL);
            updateSettings();
        }

其中两个字段非常重要:

  • Settings.System.ACCELEROMETER_ROTATION:该字段表示屏幕旋转模式,是否使用加速度传感器控制屏幕的方向旋转,开启时表示自由模式,关闭表示锁定模式;
  • Settings.System.USER_ROTATION:用户设置的屏幕旋转方向值,当没有使用加速度传感器,且顶层Activity没有指定旋转方向时作为默认值使用。 当其中以上值发生变化后,会进行更新操作: 
// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java

    private boolean updateSettings() {
	// 是否更新旋转方向值
        boolean shouldUpdateRotation = false;

        synchronized (mLock) {
            // 是否更新旋转方向监听
            boolean shouldUpdateOrientationListener = false;
            // 获取用户设置旋转方向
            final int userRotation = Settings.System.getIntForUser(resolver,
                    Settings.System.USER_ROTATION, Surface.ROTATION_0,
                    UserHandle.USER_CURRENT);
            // Settings.System.USER_ROTATION的值发生变化
            if (mUserRotation != userRotation) {
                mUserRotation = userRotation;
                shouldUpdateRotation = true;
            }

            final int userRotationMode = Settings.System.getIntForUser(resolver,
                    Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0
                            ? WindowManagerPolicy.USER_ROTATION_FREE
                            : WindowManagerPolicy.USER_ROTATION_LOCKED;
			// Settings.System.ACCELEROMETER_ROTATION值发生变化
            if (mUserRotationMode != userRotationMode) {
                mUserRotationMode = userRotationMode;
                shouldUpdateOrientationListener = true;
                shouldUpdateRotation = true;
            }
            // 更新方向旋转监听状态
            if (shouldUpdateOrientationListener) {
                updateOrientationListenerLw(); // Enable or disable the orientation listener.
            }
        }

        return shouldUpdateRotation;
    }

以上方法中,shouldUpdateOrientationListener为true表示要更新旋转方向的监听状态,mUserRotationMode表示当前的方向旋转模式,shouldUpdateRotation为true则表示需要更新旋转角度。

这三个局部变量根据Settings.System.ACCELEROMETER_ROTATION和Settings.System.USER_ROTATION而定,他俩在什么场景下会更新呢? 是由系统更新还是用户可以自己更新呢?

既然是SettingsProvider中提供的值,那么用户单独更新当然是没问题的,而在WMS中提供了两个接口——freezeRotation()方法和thawRotation()方法,其中的原理就是通过控制这俩字段来切换方向旋转模式。

比如Miui中状态栏的"方向锁定"功能就是调用的以上俩接口,在开启方向锁定时,freezeRotation()方法将会把Settings.System.ACCELEROMETER_ROTATION的值将设置为0,关闭加速度传感器的监听,同时Settings.System.USER_ROTATION将设置为指定的旋转角度;关闭方向锁定时,thawRotation()方法又会打开加速度传感器监听等,这部分详细流程在后面专门进行分析。

4. 方向旋转Sensor监听的注册与解除

旋转角度监听状态的更新在DisplayRotation#updateOrientationListenerLw()方法中,这里会进行旋转角度相关Sensor的注册和解除流程,这部分流程不仅和以上几个设置值相关,还和系统当前一些状态也有关联,查找代码可以发现,在亮灭屏流程中,当keyguard绘制状态、window状态发生变化后,也都会通过DisplayRotation#updateOrientationListener()方法更新方向旋转Sensor的监听状态,下面就来看一下,系统在什么场景下需要监听相关Sensor来进行方向旋转,什么情况下不需要监听。

updateOrientationListenerLw()方法如下:

//  frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
    private void updateOrientationListenerLw() {
        // 是否正在进行点亮屏幕的操作
        final boolean screenOnEarly = mDisplayPolicy.isScreenOnEarly();	
		// 是否唤醒系统
        final boolean awake = mDisplayPolicy.isAwake();
		// keyguard绘制是否完成
        final boolean keyguardDrawComplete = mDisplayPolicy.isKeyguardDrawComplete();
		// 窗口绘制是否完成
        final boolean windowManagerDrawComplete = mDisplayPolicy.isWindowManagerDrawComplete();

        boolean disable = true;
		// 只有在屏幕唤醒状态,且keyguard和窗口全部绘制完成的情况下,才会有资格注册sensor监听
        if (screenOnEarly && awake && ((keyguardDrawComplete && windowManagerDrawComplete))) {
            if (needSensorRunning()) {
                disable = false;
                // 注册Sensor监听
                if (!mOrientationListener.mEnabled) {
                    mOrientationListener.enable(true /* clearCurrentRotation */);
                }
            }
        }
        // 解除Sensor监听
        if (disable && mOrientationListener.mEnabled) {
            mOrientationListener.disable();
        }
    }
以上方法中可以看到,对于旋转角度Sensor的注册/解除,会有多个因素决定,如是否亮屏、Keyguard绘制是否完成等。其规则是,在屏幕唤醒状态,且keyguard和窗口全部绘制完成的情况下,如果needSensorRunning()方法返回true,就会注册Sensor去监听方向旋转,再来看看needSensorRunning()方法:

// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
    private boolean needSensorRunning() {
	// 是否固定了屏幕方向
        if (isFixedToUserRotation()) {
            return false;
        }
	// 支持屏幕自动旋转功能
        if (mSupportAutoRotation) {
            // 当前app请求的方向
            if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                    || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                    || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
                    || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
                return true;
            }
        }
		.......
        // 关闭方向旋转时是否需要提示
        if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
            return mSupportAutoRotation &&
                    mShowRotationSuggestions == Settings.Secure.SHOW_ROTATION_SUGGESTIONS_ENABLED;
        }
        return mSupportAutoRotation;
    }

这个方法中定义规则总结如下:

  1. 如果设置了固定使用用户设置方向旋转角度值(通过setFixedToUserRotation(int id, int value)方法设置),那么就是用用户设置的旋转角度,不再注册Sensor监听;
  2. 如果支持自动旋转,且当前Activity设置了4类和Sensor相关的方向(通过Activity#setRequestedOrientation()方法或者android:screenOrientation属性设置),这种情况下需要注册Sensor监听;
  3. 如果当前用户设置旋转状态为锁定模式,即Settings.System.ACCELEROMETER_ROTATION为0关闭了加速度控制方向旋转,但需要提醒状态是开启的,这种情况下需要注册Sensor监听;
  4. 不满足以上条件,说明用户设置旋转状态为自由模式,如果支持自动旋转,这种情况下需要注册Sensor监听;

2.1.加速度传感器原始数据转换方向旋转角度值

现在我们知道,屏幕方向旋转是通过加速度传感器或者重力传感器来获取,但Sensor上报的原始数据还得通过一系列计算得到最终的旋转角度,这部分逻辑在WindowOrientationListener.AccelSensorJudge类中进行计算转换,当Sensor收到onSensorChanged()回调后,对原始数据进行加工,最终得到屏幕方向旋转角度,并当方向旋转值发生变化时,调用WindowOrientationListener#onProposedRotationChanged()方法发起更新。

2.2.方向旋转值变化后发起屏幕方向更新

上面已经说了,当Sensor上报了原始数据,并经过转换后如果屏幕方向值发生了变化,那么将调用WindowOrientationListener#onProposedRotationChanged()方法,并在该方法中,在android.ui线程中post一个Runnable,通知WMS发起更新操作,我们直接来看Runnable中的流程:

//  frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java

        private class UpdateRunnable implements Runnable {
           
            @Override
            public void run() {
		// 向StatusBar中发送rotation suggestion
                if (isRotationChoicePossible(mCurrentAppOrientation)) {
                    final boolean isValid = isValidRotationChoice(mRotation);
                    sendProposedRotationChangeToStatusBarInternal(mRotation, isValid);
                } else {
                    // WMS中发起更新
                    mService.updateRotation(false /* alwaysSendConfiguration */,
                            false /* forceRelayout */);
                }
            }
        }

可以看到,屏幕方向值发生变化后,将调用WindowManagerService#updateRotation(false, false)方法开始进行更新。

WindowManagerService#updateRotation(false, false)的执行,也就意味这屏幕方向更新的开始。之后的流程,将会在下一篇进行分析。

顺便说一下,WMS中也提供了cmd命令,方便开发者在开发过程中进行调试,其中就包括来设置方向的set-user-rotation:

$ adb shell cmd window -h
Window manager (window) commands:
help
Print this help text.
size [reset|WxH|WdpxHdp] [-d DISPLAY_ID]
Return or override display size.
width and height in pixels unless suffixed with 'dp'.
density [reset|DENSITY] [-d DISPLAY_ID]
Return or override display density.
folded-area [reset|LEFT,TOP,RIGHT,BOTTOM]
Return or override folded area.
scaling [off|auto] [-d DISPLAY_ID]
Set display scaling mode.
dismiss-keyguard
Dismiss the keyguard, prompting user for auth if necessary.
set-user-rotation [free|lock] [-d DISPLAY_ID] [rotation]
Set user rotation mode and user rotation.
dump-visible-window-views
Dumps the encoded view hierarchies of visible windows
set-fix-to-user-rotation [-d DISPLAY_ID] [enabled|disabled]
Enable or disable rotating display for app requested orientation.