美文网首页
Android WindowManagerService--09

Android WindowManagerService--09

作者: DarcyZhou | 来源:发表于2023-11-14 08:21 被阅读0次

本文转载自:Android R WindowManagerService模块(6) 屏幕旋转流程(1)

本文基于Android 11.0源码分析

前言

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

1.概述

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

WMS14.png WMS15.png

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

WMS16.png

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

2.类结构

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

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

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

WMS17.PNG

下面就从创建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();
            // 1.创建OrientationListener对象
            mOrientationListener = new OrientationListener(mContext, uiHandler);
            // 初始化
            mOrientationListener.setCurrentRotation(mRotation);
            // 监听SettingsProvider中的变化
            mSettingsObserver = new SettingsObserver(uiHandler);
            mSettingsObserver.observe();
        }
    }

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

  • 初始化了几类场景下指定的旋转角度,如在收到lid_switch open事件后、插入基座时指定的旋转角度;

  • 创建OrientationListener对象,和Sensor建立连接关系;

  • 设置SettingsProvider中相关字段的监听。

3.1 创建OrientationListener对象

  屏幕旋转离不开Sensor的监听,而和Sensor打交道的业务,全权交给了OrientationListener来负责,它会获取Sensor对象、监听Sensor数据、将Sensor的数据转换成旋转角度,并通知WindowManagerService更新方向。

    private class OrientationListener extends WindowOrientationListener {
        final SparseArray<Runnable> mRunnableCache = new SparseArray<>(5);
        boolean mEnabled;

        OrientationListener(Context context, Handler handler) {
            super(context, handler);
        }

        private class UpdateRunnable implements Runnable {
            final int mRotation;

            UpdateRunnable(int rotation) {
                mRotation = rotation;
            }

            @Override
            public void run() {
                // Send interaction hint to improve redraw performance.
                mService.mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
                if (isRotationChoicePossible(mCurrentAppOrientation)) {
                    final boolean isValid = isValidRotationChoice(mRotation);
                    sendProposedRotationChangeToStatusBarInternal(mRotation, isValid);
                } else {
                    // 通知WindowManagerService更新方向
                    mService.updateRotation(false /* alwaysSendConfiguration */,
                            false /* forceRelayout */);
                }
            }
        }

        @Override
        public void onProposedRotationChanged(int rotation) {
            ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
            Runnable r = mRunnableCache.get(rotation, null);
            if (r == null) {
                r = new UpdateRunnable(rotation);
                mRunnableCache.put(rotation, r);
            }
            getHandler().post(r);
        }

        @Override
        public void enable(boolean clearCurrentRotation) {
            super.enable(clearCurrentRotation);
            mEnabled = true;
            ProtoLog.v(WM_DEBUG_ORIENTATION, "Enabling listeners");
        }

        @Override
        public void disable() {
            super.disable();
            mEnabled = false;
            ProtoLog.v(WM_DEBUG_ORIENTATION, "Disabling listeners");
        }
    }

在创建OrientationListener对象时,它父类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();
        }
    }

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

3.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();
        }

其中两个字段非常重要:

  1. Settings.System.ACCELEROMETER_ROTATION:该字段表示屏幕旋转模式,是否使用加速度传感器控制屏幕的方向旋转,开启时表示自由模式,关闭表示锁定模式;

  2. 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监听。

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

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

4.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.

相关文章

网友评论

      本文标题:Android WindowManagerService--09

      本文链接:https://www.haomeiwen.com/subject/epvvvdtx.html