一、概述
Android 系统的屏幕能旋转的前提是设备需要具有sensor硬件设备,sensor实时将设备的旋转数据上报给上层,上层对上包上来的数据进行处理,并且改变屏幕的坐标和方向,最后呈现在我们面前的就是屏幕的正常旋转。所以下面将从Android Setting中自动旋转开关、AndroidManifest中Activity指定screenOrientation标签、通过Activity的setRequestedOrientation方法设置以及底层sensor上报到上层屏幕旋转四个流程分析屏幕的旋转,并且会在第六步分析常见的需求。
二、Android Setting中自动旋转开关流程
Android 原生Settings中的辅助功能中有很多关于显示的接口,比如三指放大等等,而屏幕旋转的开关也在其中。具体就是AccessibilitySettings.java(packages/apps/Settings//src/com/android/settings/accessibility/AccessibilitySettings.java)中,整体的流程图如下图2-1所示,下面从Setting模块开始分析自动旋转开关流程。
//packages/apps/Settings//src/com/android/settings/accessibility/AccessibilitySettings.java
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (mToggleHighTextContrastPreference == preference) {
handleToggleTextContrastPreferenceClick();
return true;
....
} else if (mToggleLockScreenRotationPreference == preference) {
handleLockScreenRotationPreferenceClick();
return true;
....
}
return super.onPreferenceTreeClick(preference);
}
//最后Settings是调用RotationPolicy中的相关方法进一步处理
private void handleLockScreenRotationPreferenceClick() {
RotationPolicy.setRotationLockForAccessibility(getActivity(),
!mToggleLockScreenRotationPreference.isChecked());
}
Settings中实现很简单最后调用到 RotationPolicy.java(frameworks/base/core/java/com/android/internal/view/RotationPolicy.java)中的setRotationLockForAccessibility方法实现屏幕是否自动旋转。
//frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
public static final int NATURAL_ROTATION = getNaturalRotation();
//通过ro.primary_display.user_rotation属性来获取默认的屏幕方向
private static int getNaturalRotation() {
int rotation = SystemProperties.getInt("ro.primary_display.user_rotation", 0);
switch (rotation) {
case 90:
return Surface.ROTATION_90;
case 180:
return Surface.ROTATION_180;
case 270:
return Surface.ROTATION_270;
default:
break;
}
return Surface.ROTATION_0;
}
public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
//最后调用setRotationLock来实现,NATURAL_ROTATION为默认的屏幕方向
setRotationLock(enabled, NATURAL_ROTATION);
}
该方法中会设置Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY字段:
- 0 表示自动旋转屏幕打开,屏幕解冻,可以自动旋转
- 1 表示自动旋转屏幕关闭,屏幕固定锁死
可以通过下面手动adb 命令打开和关闭屏幕旋转功能
adb shell settings put system hide_rotation_lock_toggle_for_accessibility 0
我们继续回到setRotationLockForAccessibility方法中,最后调用setRotationLock这个接口继续执行,注意此方法的第二个参数是默认的屏幕方向,是通过surfaceFlinger在初始化时候的设置的ro.primary_display.user_rotation这个属性获得。
//frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
private static void setRotationLock(final boolean enabled, final int rotation) {
new AsyncTask<Boolean, Void, Void>() {
@Override
protected Void doInBackground(Boolean... params){
try {
IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
//这里的params[0]就是传入的第一个参数enable,如果为true,就是冻结屏幕,否则解冻
if (params[0]) {
wm.freezeRotation(rotation);
} else {
wm.thawRotation();
}
} catch (RemoteException exc) {
Log.w(TAG, "Unable to save auto-rotate setting");
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, enabled);
}
IWindowManager的实现类为WMS(WindowMangerService),最后调用到WMS中的freezeRotation方法进行冻结屏幕,防止屏幕自动旋转,调用thawRotation方法解冻屏幕,注意此时freezeRotation方法传入屏幕旋转的方向,作为固定的屏幕的方向,例如 冻结前是竖屏,冻结后就是竖屏,反之为横屏。
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void freezeRotation(int rotation) {
freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
}
@Override
public void freezeDisplayRotation(int displayId, int rotation) {
// TODO(multi-display): Track which display is rotated.
....
try {
synchronized (mGlobalLock) {
//1: 调用DisplayContent的getDisplayRotation()获取到DisplayRotation
//然后调用DisplayRotation的freezeRotation方法设置当前用户选择的屏幕方向
display.getDisplayRotation().freezeRotation(rotation);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
//2: 判断屏幕是否旋转,通知Config发生变化,如果布局改变,需要重新绘制布局
updateRotationUnchecked(false, false);
}
//解冻屏幕的方式类似
@Override
public void thawRotation() {
thawDisplayRotation(Display.DEFAULT_DISPLAY);
}
/**
* Thaw rotation changes. (Disable "rotation lock".)
* Persists across reboots.
*/
@Override
public void thawDisplayRotation(int displayId) {
...
long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
...
//1: 调用DisplayRotation的thawRotation()方法解冻屏幕
display.getDisplayRotation().thawRotation();
}
} finally {
Binder.restoreCallingIdentity(origId);
}
//2:判断屏幕是否旋转,通知Config发生变化,如果布局改变,需要重新绘制布局
updateRotationUnchecked(false, false);
}
WMS中屏幕锁定和屏幕自动旋转两个实现很相似,主要是调用DisplayRotation的freezeRotation方法锁定用户指定的屏幕方向,调用thawRotation方法,解锁用户固定屏幕,恢复屏幕自动旋转。最后调用updateRotationUnchecked,发送新的Configuration变化,以及如果布局发生变化,也会重新计算布局。
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
void freezeRotation(int rotation) {
rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
}
void thawRotation() {
setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
}
DisplayRotation中最后都是调用setUserRotation方法处理,传入的参数不通,第一个参数为用户选择的选择模式(自动旋转或者屏幕锁定);第二个参数为用户选择的屏幕方向。
//frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
/** When not otherwise specified by the activity's screenOrientation, rotation should be
* determined by the system (that is, using sensors). */
public final int USER_ROTATION_FREE = 0;
/** When not otherwise specified by the activity's screenOrientation, rotation is set by
* the user. */
public final int USER_ROTATION_LOCKED = 1;
- WindowManagerPolicy.USER_ROTATION_LOCKED: 屏幕的旋转将和系统的sensor保持一致。值得注意的是,这个生效的前提是activity没有设置screenOrientation属性。
- WindowManagerPolicy.USER_ROTATION_FREE :屏幕的旋转是由用户选择而定,前提也是需要activity没有设置screenOrientation属性。
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
private void setUserRotation(int userRotationMode, int userRotation) {
//1: 默认屏幕显示,通过settings数据库的监听,不需要更新内部的值。
if (isDefaultDisplay) {
// We'll be notified via settings listener, so we don't need to update internal values.
final ContentResolver res = mContext.getContentResolver();
final int accelerometerRotation =
userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;
Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,
accelerometerRotation, UserHandle.USER_CURRENT);
Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,
UserHandle.USER_CURRENT);
return;
}
boolean changed = false;
if (mUserRotationMode != userRotationMode) {
mUserRotationMode = userRotationMode;
changed = true;
}
if (mUserRotation != userRotation) {
mUserRotation = userRotation;
changed = true;
}
//2: 将屏幕显示配置信息保存到/data/system/display_settings.xml中
mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
userRotation);
if (changed) {
//3: 调用WMS的updateRotation更新屏幕的状态信息
mService.updateRotation(true /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
如果是默认屏幕,不会修改内部的状态,而是直接通过设置监听Settings数据库实现,对应的Settings数据库字段主要两个:“accelerometer_rotation”和“user_rotation”。
- accelerometer_rotation: 屏幕锁定为0,支持自动旋转为1;
- user_rotation:屏幕旋转的方向:0表示竖屏,1表示横屏
//可以通过adb 命令获取该setting字段的值
adb shell settings get system accelerometer_rotation 0
adb shell settings get system user_rotation
如果不是默认屏幕,则会修改内部的状态值,首先判断用户旋转模式或者屏幕方向是否改变,只要有一个改变就会,下面第三部都是需要通过WMS来更新屏幕的状态的信息(Configuration的改变和是否重新计算布局)。下面调用DisplayWindowSettings的setUserRotation方法在/data/system/display_settings.xml中保存用户设置的屏幕显示状态信息。最后调用WMS的updateRotation来更新屏幕的状态信息。而updateRotation最终还是调用WMS的updateRotationUnchecked方法,只是传入的第一参数为true,即总是需要通知其他组件Configuration的改变。
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
...
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
//这里主要考虑是多屏幕的操作
for (int i = 0; i < displayCount; ++i) {
final DisplayContent displayContent = mRoot.mChildren.get(i);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
//1: 调用DisplayContent的updateRotationUnchecked方法更新屏幕旋转状态,返回屏幕旋转是否
//改变,每个屏幕都有唯一一个DisplayContent实例对应
final boolean rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
//2:如果屏幕旋转角度改变,或者需要发送Configuration改变,都会调用DisplayContent的
//sendNewConfiguration方法,通知Configuration的改变
if (rotationChanged || alwaysSendConfiguration) {
displayContent.sendNewConfiguration();
}
}
if (layoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"updateRotation: performSurfacePlacement");
//3: 如果需要重新计算和绘制布局,则调用WindowSurfacePlacer的performSurfacePlacement
//计算和重新布局layout
mWindowPlacerLocked.performSurfacePlacement();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
和屏幕旋转相关的主要是第一点,DisplayContent的updateRotationUnchecked方法更新屏幕旋转状态,并且触发屏幕旋转的动画等一系列动作。
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
boolean updateRotationUnchecked(boolean forceUpdate) {
ScreenRotationAnimation screenRotationAnimation;
//1: 如果不是强制状态更新,则会有以下三种情况,不会更新状态
// a:屏幕旋转暂停状态时,不能旋转;
// b:屏幕旋转动画还没有结束时,不能旋转;
// c: 屏状态没有完全更新完,不能旋转
if (!forceUpdate) {
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until
// updates have been resumed.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
return false;
}
screenRotationAnimation =
mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
// Rotation updates cannot be performed while the previous rotation change
// animation is still in progress. Skip this update. We will try updating
// again after the animation is finished and the display is unfrozen.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
return false;
}
if (mWmService.mDisplayFrozen) {
// Even if the screen rotation animation has finished (e.g. isAnimating
// returns false), there is still some time where we haven't yet unfrozen
// the display. We also need to abort rotation here.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
"Deferring rotation, still finishing previous rotation");
return false;
}
}
//2: 如果显示处于disable状态,没法旋转。
if (!mWmService.mDisplayEnabled) {
// No point choosing a rotation if the display is not enabled.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
return false;
}
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
//3: 通过屏幕方向得到屏幕旋转角度,即从orientation到rotation
final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);
boolean mayRotateSeamlessly = mDisplayPolicy.shouldRotateSeamlessly(mDisplayRotation,
oldRotation, rotation);
if (mayRotateSeamlessly) {
final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
if (seamlessRotated != null && !forceUpdate) {
// We can't rotate (seamlessly or not) while waiting for the last seamless rotation
// to complete (that is, waiting for windows to redraw). It's tempting to check
// w.mSeamlessRotationCount but that could be incorrect in the case of
// window-removal.
return false;
}
// In the presence of the PINNED stack or System Alert
// windows we unfortunately can not seamlessly rotate.
if (hasPinnedStack()) {
mayRotateSeamlessly = false;
}
for (int i = 0; i < mWmService.mSessions.size(); i++) {
if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
mayRotateSeamlessly = false;
break;
}
}
}
final boolean rotateSeamlessly = mayRotateSeamlessly;
if (oldRotation == rotation) {
// No change.
return false;
}
if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
mWaitingForConfig = true;
}
mRotation = rotation;
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
this, WINDOW_FREEZE_TIMEOUT_DURATION);
setLayoutNeeded();
//4:启动屏幕旋转动画
final int[] anim = new int[2];
mDisplayPolicy.selectRotationAnimationLw(anim);
if (!rotateSeamlessly) {
mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);
// startFreezingDisplayLocked can reset the ScreenRotationAnimation.
} else {
// The screen rotation animation uses a screenshot to freeze the screen
// while windows resize underneath.
// When we are rotating seamlessly, we allow the elements to transition
// to their rotated state independently and without a freeze required.
mWmService.startSeamlessRotation();
}
return true;
}
该方法首先说了以下四种情况不能进行屏幕旋转:
-
如果不是强制状态更新,屏幕旋转暂停状态时,不能旋转;
-
如果不是强制状态更新,屏幕旋转动画还没有结束时,不能旋转;
-
如果不是强制状态更新,屏状态没有完全更新完,不能旋转;
-
如果显示处于disable状态,没法旋转。
除了以上四种情况外,都是可以进行屏幕旋转,然后,调用DisplayRotation的rotationForOrientation方法,从屏幕旋转角度得到屏幕的方向,即从rotation得到orientation。
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
int rotationForOrientation(int orientation, int lastRotation) {
//1: 如果是屏幕是用户锁定,禁止自动旋转,则直接返回用户设置的rotation。
//这个接口主要是通过adb shell wm set-fix-to-user-rotation调用
if (isFixedToUserRotation()) {
return mUserRotation;
}
//2:获取底层sensor上报的rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
if (sensorRotation < 0) {
sensorRotation = lastRotation;
}
//Dock,HDMI以及Lid等一些状态的设置
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();
//3 获取surfaceFlinger设置的默认的rotation
int user_rotation = SystemProperties.getInt("ro.primary_display.user_rotation", 0);
int defaultRotation = Surface.ROTATION_0;
final int preferredRotation;
switch (user_rotation) {
case 90:
defaultRotation = Surface.ROTATION_90;
break;
case 180:
defaultRotation = Surface.ROTATION_180;
break;
case 270:
defaultRotation = Surface.ROTATION_270;
break;
default:
break;
}
//4: 根据不同的情况,获取preferredRotation,作为后面获取最终rotation的依据
if (!isDefaultDisplay) {
// For secondary displays we ignore things like displays sensors, docking mode and
// rotation lock, and always prefer user rotation.
//多屏幕是不支持旋转,只有默认显示屏上支持(display 0)
preferredRotation = mUserRotation;
//中间主要HDMI,VR,Dock的一些特殊情况处理
...
//应用在activity标签中自定义了screenOrientation与上一次保持一致
} else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// Application just wants to remain locked in the last rotation.
preferredRotation = lastRotation;
//如果不支持自动旋转,则忽略sensor和settings的设置
//mSupportAutoRotationton是通过config.xml中的config_supportAutoRotation配置得到
} else if (!mSupportAutoRotation) {
// If we don't support auto-rotation then bail out here and ignore
// the sensor and any rotation lock settings.
preferredRotation = -1;
//当Settings中屏幕可以自动旋转时,且满足一下条件中任意一条,rotation取决于sensor数据,否则为上一次rotation
} 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) {
if (sensorRotation != Surface.ROTATION_180
|| mAllowAllRotations == 1
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
preferredRotation = sensorRotation;
} else {
preferredRotation = lastRotation;
}
//如果Settings中屏幕可以自动旋转关闭,且ActivityInfo为SCREEN_ORIENTATION_NOSENSOR时,
//使用用户设置的rotation
} else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
&& orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// Apply rotation lock. Does not apply to NOSENSOR.
// The idea is that the user rotation expresses a weak preference for the direction
// of gravity and as NOSENSOR is never affected by gravity, then neither should
// NOSENSOR be affected by rotation lock (although it will be affected by docks).
preferredRotation = mUserRotation;
//其他情况都由应用自己决定
} else {
// No overriding preference.
// We will do exactly what the application asked us to do.
preferredRotation = -1;
}
//5: 计算最后的rotation
switch (orientation) {
//如果应用设置了SCREEN_ORIENTATION_PORTRAIT,则roation有两种情况
//mPortraitRotation或者mUpsideDownRotation
case ActivityInfoDE .SCREEN_ORIENTATION_PORTRAIT:
// Return portrait unless overridden.
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
return mPortraitRotation;
//如果应用设置了SCREEN_ORIENTATION_LANDSCAPE,则roation有两种情况
//mLandscapeRotation或者mSeascapeRotation
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
// Return landscape unless overridden.
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
return mLandscapeRotation;
//如果应用设置了SCREEN_ORIENTATION_REVERSE_PORTRAIT,则roation有两种情况
//mUpsideDownRotation或者mPortraitRotation
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
// Return reverse portrait unless overridden.
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
return mUpsideDownRotation;
//如果应用设置了SCREEN_ORIENTATION_REVERSE_LANDSCAPE,则roation有两种情况
//mSeascapeRotation或者mLandscapeRotation
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
// Return seascape unless overridden.
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
return mSeascapeRotation;
//如果应用设置了SCREEN_ORIENTATION_SENSOR_LANDSCAPE或者SCREEN_ORIENTATION_USER_LANDSCAPE,
//则roation有两种情况mLandscapeRotation、preferredRotation或者lastRotation
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;
//如果应用设置了SCREEN_ORIENTATION_SENSOR_PORTRAIT或者SCREEN_ORIENTATION_USER_PORTRAIT,
//则roation有两种情况mPortraitRotation、preferredRotation或者lastRotation
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 defaultRotation;
}
}
该方法主要功能有以下五点:
-
如果通过adb shell wm set-fix-to-user-rotation锁定了屏幕,直接返回用户设置的rotation;
-
通过OrientationListener获取底层sensor上报的屏幕旋转角度;
-
获取surfaceFlinger设置的默认的rotation,通过ro.primary_display.user_rotation属性获得;
-
根据不同的情况,计算preferredRotation,作为第5点中真正计算rotation使用;
-
真正计算rotation的情况,完全有应用自己的Activity标签决定,这个我们将在下一章详细分析
三、Activity标签指定screenOrientation属性作用
Android应用程序中,android:screenOrientation用于控制activity启动时方向,取值主要下面的值:
- unspecified,默认值,由系统决定,不同手机可能不一致;
- landscape,强制横屏显示;
- portrait,强制竖屏显;
- behind,与前一个activity方向相同;
- sensor,根据物理传感器方向转动,用户90度、180度、270度旋转手机方向,activity都更着变化;
- sensorLandscape,横屏旋转,一般横屏游戏会这样设置;
- sensorPortrait,竖屏旋转;
- nosensor,旋转设备时候,界面不会跟着旋转。初始化界面方向由系统控制;
- user,用户当前设置的方向;
应用在扫描安装过程中解析AndroidManifest中各个标签,其中会调用PackageParser的parseActivity方法解析Activity的标签,其中screenOrientation属性标签就是从这个方法中获得,最后赋值给ActivityInfo的screenOrientation中。
//frameworks/base/core/java/android/content/pm/PackageParser.java
private Activity parseActivity(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
Activity a = new Activity(cachedArgs.mActivityArgs, new ActivityInfo());
...
a.info.screenOrientation = sa.getInt(
R.styleable.AndroidManifestActivity_screenOrientation,
SCREEN_ORIENTATION_UNSPECIFIED);
...
return a;
}
而我们平时在AndroidManifest定义的screenOrientation属性的value值所对应的int值是在attrs_manifest.xml中定义。
<!--frameworks/base/core/res/res/values/attrs_manifest.xml-->
<attr name="screenOrientation">
<!-- No preference specified: let the system decide the best
orientation. This will either be the orientation selected
by the activity below, or the user's preferred orientation
if this activity is the bottom of a task. If the user
explicitly turned off sensor based orientation through settings
sensor based device rotation will be ignored. If not by default
sensor based orientation will be taken into account and the
orientation will changed based on how the user rotates the device.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. -->
<enum name="unspecified" value="-1" />
<!-- Would like to have the screen in a landscape orientation: that
is, with the display wider than it is tall, ignoring sensor data.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}. -->
<enum name="landscape" value="0" />
<!-- Would like to have the screen in a portrait orientation: that
is, with the display taller than it is wide, ignoring sensor data.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}. -->
<enum name="portrait" value="1" />
<!-- Use the user's current preferred orientation of the handset.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}. -->
<enum name="user" value="2" />
<!-- Keep the screen in the same orientation as whatever is behind
this activity.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_BEHIND}. -->
<enum name="behind" value="3" />
<!-- Orientation is determined by a physical orientation sensor:
the display will rotate based on how the user moves the device.
Ignores user's setting to turn off sensor-based rotation.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR}. -->
<enum name="sensor" value="4" />
<!-- Always ignore orientation determined by orientation sensor:
the display will not rotate when the user moves the device.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_NOSENSOR}. -->
<enum name="nosensor" value="5" />
<!-- Would like to have the screen in landscape orientation, but can
use the sensor to change which direction the screen is facing.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_LANDSCAPE}. --
<enum name="sensorLandscape" value="6" />
<!-- Would like to have the screen in portrait orientation, but can
use the sensor to change which direction the screen is facing.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_PORTRAIT}. -->
<enum name="sensorPortrait" value="7" />
<!-- Would like to have the screen in landscape orientation, turned in
the opposite direction from normal landscape.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. -->
<enum name="reverseLandscape" value="8" />
<!-- Would like to have the screen in portrait orientation, turned in
the opposite direction from normal portrait.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_PORTRAIT}. -->
<enum name="reversePortrait" value="9" />
<!-- Orientation is determined by a physical orientation sensor:
the display will rotate based on how the user moves the device.
This allows any of the 4 possible rotations, regardless of what
the device will normally do (for example some devices won't
normally use 180 degree rotation).
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_SENSOR}. -->
<enum name="fullSensor" value="10" />
<!-- Would like to have the screen in landscape orientation, but if
the user has enabled sensor-based rotation then we can use the
sensor to change which direction the screen is facing.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_LANDSCAPE}. -->
<enum name="userLandscape" value="11" />
<!-- Would like to have the screen in portrait orientation, but if
the user has enabled sensor-based rotation then we can use the
sensor to change which direction the screen is facing.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_PORTRAIT}. -->
<enum name="userPortrait" value="12" />
<!-- Respect the user's sensor-based rotation preference, but if
sensor-based rotation is enabled then allow the screen to rotate
in all 4 possible directions regardless of what
the device will normally do (for example some devices won't
normally use 180 degree rotation).
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_USER}. -->
<enum name="fullUser" value="13" />
<!-- Screen is locked to its current rotation, whatever that is.
Corresponds to
{@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LOCKED}. -->
<enum name="locked" value="14" />
</attr>
下面我们从解析到的ActivityInfo中screenOrientation属性使用的地方来分析AndroidManifest中Activity的screenOrientation标签属性的作用。
3.1 在创建App Window token的过程中使用
我们知道Android 每一个Window对应于一个token,每一个应用有唯一的一个token对应,这个在后续分析应用的启动流程window篇中在继续分析。
//framework/base/services/core/java/com/android/server/wm/ActivityRecord.java
void createAppWindowToken() {
.....
mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,
task.voiceSession != null, container.getDisplayContent(),
ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this)
* 1000000L, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, appInfo.targetSdkVersion,
info.screenOrientation, mRotationAnimationHint,
mLaunchTaskBehind, isAlwaysFocusable());
.....
}
这里会创建AppWindowToken对象,并将对应的screenOrientation参数传入赋值给mOrientation成员变量。AppWindowToken里面用到orientation,都是这里获得,并且通过下面几个方法提供给其他模块。
//framework/base/services/core/java/com/android/server/wm/AppWindowToken.java
int getOrientation(int candidate) {
if (candidate == SCREEN_ORIENTATION_BEHIND) {
// Allow app to specify orientation regardless of its visibility state if the current
// candidate want us to use orientation behind. I.e. the visible app on-top of this one
// wants us to use the orientation of the app behind it.
return mOrientation;
}
// The {@link AppWindowToken} should only specify an orientation when it is not closing or
// going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
// an Activity in another task being started in the wrong orientation during the transition.
if (!(sendingToBottom || getDisplayContent().mClosingApps.contains(this))
&& (isVisible() || getDisplayContent().mOpeningApps.contains(this))) {
return mOrientation;
}
return SCREEN_ORIENTATION_UNSET;
}
/** Returns the app's preferred orientation regardless of its currently visibility state. */
int getOrientationIgnoreVisibility() {
return mOrientation;
}
在DisplayContent中会通过getOrientation来获取orientation给mLastOrientation。最后调用到 updateRotationUnchecked方法中,该方法已经在第一部分中分析,此处不再分析。值得注意的是,最后调用WMS的updateRotationUnchecked中调用sendNewConfiguration最后同样会调用DisplayContent的applyRotationLocked方法完成最后的设置
//framework/base/services/core/java/com/android/server/wm/DisplayContent.java
int getOrientation() {
final WindowManagerPolicy policy = mWmService.mPolicy;
if (mIgnoreRotationForApps) {
return SCREEN_ORIENTATION_USER;
}
if (mWmService.mDisplayFrozen) {
if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
+ " is frozen, return " + mLastWindowForcedOrientation);
// If the display is frozen, some activities may be in the middle of restarting, and
// thus have removed their old window. If the window has the flag to hide the lock
// screen, then the lock screen can re-appear and inflict its own orientation on us.
// Keep the orientation stable until this all settles down.
return mLastWindowForcedOrientation;
} else if (policy.isKeyguardLocked()) {
// Use the last orientation the while the display is frozen with the keyguard
// locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
// window. We don't want to check the show when locked window directly though as
// things aren't stable while the display is frozen, for example the window could be
// momentarily unavailable due to activity relaunch.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
+ " is frozen while keyguard locked, return " + mLastOrientation);
return mLastOrientation;
}
} else {
int orientation = mAboveAppWindowsContainers.getOrientation();
if("homlet".equals(SystemProperties.get("ro.product.platform", "null"))){
orientation = "1".equals(SystemProperties.get("ro.sf.disablerotation","0"))?ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:orientation;
}
if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
}
if (orientation != SCREEN_ORIENTATION_UNSET) {
return orientation;
}
}
// Top system windows are not requesting an orientation. Start searching from apps.
return mTaskStackContainers.getOrientation();
}
四 、调用setRequestedOrientation设置
每个应用也可以在自己的Activity中调用setRequestedOrientation方法设置屏幕方向。下面从Activity开始分析这个调用流程。
//framework/base/core/java/android/app/Activity.java
public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
if (mParent == null) {
try {
......
ActivityTaskManager.getService().setRequestedOrientation(
mToken, requestedOrientation);
} catch (RemoteException e) {
// Empty
}
} else {
mParent.setRequestedOrientation(requestedOrientation);
}
}
这里很简单,ActivityTaskManager.getService()获取的是ActivityTaskManagerService的对象,调用其setRequestedOrientation最后调用是ActivityRecord的setRequestedOrientation方法。
//framework/base/services/core/java/com/android/server/wm/ActivityRecord.java
void setRequestedOrientation(int requestedOrientation) {
.....
setOrientation(requestedOrientation, mayFreezeScreenLocked(app));
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
task.taskId, requestedOrientation);
}
private void setOrientation(int requestedOrientation, boolean freezeScreenIfNeeded) {
....
//调用AppWindowToken的setOrientation方法设置屏幕方向
mAppWindowToken.setOrientation(requestedOrientation, binder, this);
....
}
ActivityRecord的setRequestedOrientation方法最后调用AppWindowToken的setOrientation方法,我们直接分析setOrientation方法。
//framework/base/services/core/java/com/android/server/wm/WindowContainer
void setOrientation(int orientation) {
setOrientation(orientation, null /* freezeDisplayToken */,
null /* ActivityRecord */);
}
void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,
@Nullable ConfigurationContainer requestingContainer) {
final boolean changed = mOrientation != orientation;
mOrientation = orientation;
....
}
其实最后仅仅是将最后设置的值赋值给mOrientation,通过getOrientation获取给其他模块使用。
五、相关需求
1 如果是自己开发的应用,则直接在AndroidManifest中将activity的screenOrientation属性设置为sensorLandscape。
2 如果是所有应用都需要修改,则需要在系统代码里面做过滤,根据上面介绍的流程需要做以下修改
//frameworks/base/core/java/android/content/pm/PackageParser.java
//解析应用AndroidMenifest中的Activity的标签
private Activity parseActivity(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
.....
a.info.screenOrientation = sa.getInt(
R.styleable.AndroidManifestActivity_screenOrientation,
SCREEN_ORIENTATION_UNSPECIFIED);
//screenOrientation only display sensorLandscape for Mate
if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {
Slog.d(TAG,"DTEN Mate: force to sensorLandscape");
a.info.screenOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
}
.....
}
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
//在用户主动设置地方过滤掉
void setRequestedOrientation(int requestedOrientation) {
/*aw: avoid setting orientation for some product*/
if(SystemProperties.get("ro.product.platform").equals("homlet")) {
/*if (info.toString().contains("com.XX.Activity")*/
Slog.d(TAG_CONFIGURATION,"homlet skip setRequestedOrientation");
return;
}
/*aw: end*/
/*DTEN: avoid setting orientation for DTEN Mate product*/
if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {
Slog.d(TAG_CONFIGURATION,"DTEN Mate skip setRequestedOrientation");
return;
}
/*DTEN: end*/
setOrientation(requestedOrientation, mayFreezeScreenLocked(app));
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
task.taskId, requestedOrientation);
}
//获取屏幕旋转状态时候,强制返回为SCREEN_ORIENTATION_SENSOR_LANDSCAPE
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
int getOrientation() {
.....
int orientation = mAboveAppWindowsContainers.getOrientation();
/*DTEN: avoid setting orientation for DTEN Mate product*/
if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
}
/*DTEN: end*/
if (orientation != SCREEN_ORIENTATION_UNSET) {
return orientation;
}
}
// Top system windows are not requesting an orientation. Start searching from apps.
return mTaskStackContainers.getOrientation();
}
七、总结
至此,Android 屏幕旋转的流程源码分析就结束了,后面会继续发布一些Android 系统源码框架的相关文章,欢迎大家批评指正。
参考:
https://blog.youkuaiyun.com/u010871962/article/details/108749099