Android 系统的屏幕能旋转的前提是设备需要具有sensor硬件设备,sensor实时将设备的旋转数据上报给上层,上层对上包上来的数据进行处理,并且改变屏幕的坐标和方向,最后呈现在我们面前的就是屏幕的正常旋转。所以下面将从Android Setting中自动旋转开关、AndroidManifest中Activity指定screenOrientation标签、通过Activity的setRequestedOrientation方法设置以及底层sensor上报到上层屏幕旋转四个流程分析屏幕的旋转,并且会在第六步分析常见的需求。
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字段:
可以通过下面手动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;
//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”。
//可以通过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标签决定,这个我们将在下一章详细分析
Android应用程序中,android:screenOrientation用于控制activity启动时方向,取值主要下面的值:
应用在扫描安装过程中解析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中定义。
<attr name="screenOrientation">
<enum name="unspecified" value="-1" />
<enum name="landscape" value="0" />
<enum name="portrait" value="1" />
<enum name="user" value="2" />
<enum name="behind" value="3" />
<enum name="sensor" value="4" />
<enum name="nosensor" value="5" />
<enum name="sensorPortrait" value="7" />
<enum name="reverseLandscape" value="8" />
<enum name="reversePortrait" value="9" />
<enum name="fullSensor" value="10" />
<enum name="userLandscape" value="11" />
<enum name="userPortrait" value="12" />
<enum name="fullUser" value="13" />
<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();
}
每个应用也可以在自己的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.csdn.net/u010871962/article/details/108749099