本文基于Android 11.0源码分析
前言
Android中提供了非常灵活的屏幕旋转功能,系统可以根据各种传感器自动旋转,应用也可以根据场景自行设置Activity的方向,来覆盖系统的行为。从这篇文章开始,将对整个屏幕旋转流程进行详细的分析。
1.概述
Android为应用提供了一系列用于控制屏幕方向的值,可以在manifest文件中通过android:screenOrientation属性来设置,也可以在Activity中通过setRequestedOrientation(int requestedOrientation)方法来设置,可以设置的值和含义如下表:
![](https://img.haomeiwen.com/i18565088/cfe35430923d914c.png)
![](https://img.haomeiwen.com/i18565088/ac2c20ded340815f.png)
此外,还提供了一个方向旋转角度值,其值及含义如下:
![](https://img.haomeiwen.com/i18565088/faff0071352af724.png)
该值我们后面统一称为“屏幕方向”。这两类值会作为判断条件和结果,应用在整个流程中。
2.类结构
在Framework中,屏幕旋转功能主要是由WMS模块中的DisplayRotation对象来完成,在启动WindowManagerService过程中,创建DisplayContent对象时,会创建一个对应的DisplayRotation负责屏幕旋转逻辑,一个DisiplayContent对象对应一个DisplayRotation对象,或者说DisplayContent对象中持有一个DisplayRotation对象。
在DisplayRotation中,将获取Sensor数据并转换成具体方向旋转角度值的逻辑交给了OrientationListener对象来负责。
从整体来看,可以用一句话总结各自的职责:DisplayContent负责控制,DisplayRotation负责执行,OrientationListener负责获取数据。
下面就从创建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();
}
其中两个字段非常重要:
-
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;
}
这个方法中定义规则总结如下:
-
如果设置了固定使用用户设置方向旋转角度值(通过setFixedToUserRotation(int id, int value)方法设置),那么就是用用户设置的旋转角度,不再注册Sensor监听;
-
如果支持自动旋转,且当前Activity设置了4类和Sensor相关的方向(通过Activity#setRequestedOrientation()方法或者android:screenOrientation属性设置),这种情况下需要注册Sensor监听;
-
如果当前用户设置旋转状态为锁定模式,即Settings.System.ACCELEROMETER_ROTATION为0关闭了加速度控制方向旋转,但需要提醒状态是开启的,这种情况下需要注册Sensor监听;
-
不满足以上条件,说明用户设置旋转状态为自由模式,如果支持自动旋转,这种情况下需要注册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.
网友评论