背景:在平板设备上,当锁定平板设备为横屏时,然后打开一个强制竖屏的应用时,发现平板设备变为默认竖屏了, 下面分析一下原因
查看当前显示方向:
adb shell settings get system user_rotation
通过分析代码发现在锁定屏幕方向时,打开竖屏应用后再设备竖屏显示应用后SystemUI 会将系统 user_rotation
设置为 0 , 即竖屏模式
看一下SystemUI 是如何修改user_rotation
的, 我们的入手点是 NavBarHelper
, 因为在 NavBarHelper
中向 WindowManagerService
中注册了一个监听,监听屏幕方向
frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
private final IWindowManager mWm;
@Inject
public NavBarHelper(
IWindowManager wm,
) {
mWm = wm;
}
在 NavBarHelper
初始化的时候设置 WindowManagerService
的 IWindowManager
binder调用, 看一下 vm 是在哪里注入的
frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@Module
public class FrameworkServicesModule {
@Provides
@Singleton
static IWindowManager provideIWindowManager() {
return WindowManagerGlobal.getWindowManagerService();
}
}
这向NavBarHelper注入了一个 IWindowManager
在回到 NavBarHelper
中在 setupOnFirstBar()
方法中设置显示方向变化的观察回调
private void setupOnFirstBar() {
// Setup display rotation watcher
try {
mWm.watchRotation(mRotationWatcher, mDefaultDisplayId);
} catch (Exception e) {
Log.w(TAG, "Failed to register rotation watcher", e);
}
}
mRotationWatcher
这个实现也在 NavBarHelper
中, 具体代码如下:
private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(final int rotation) {
// 省略部分代码
// We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change.
mHandler.postAtFrontOfQueue(() -> {
mRotationWatcherRotation = rotation;
dispatchRotationChanged(rotation);
});
}
};
private void dispatchRotationChanged(int rotation) {
for (NavbarTaskbarStateUpdater listener : mStateListeners) {
listener.updateRotationWatcherState(rotation);
}
}
在接收到显示变化回调后, 会通过d ispatchRotationChanged
方法把状态分发出去, 下面看一下在 NavBarHelper
中添加监听的方法
public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
mStateListeners.add(listener);
// 省略部分代码
}
整理一下上面的代码,主要逻辑是 NavBarHelper
通过想 WindowManagerService
添加显示方向变化监听,在显示方向发生变化时进行分发
下面看一下在显示发生变化事会分发到哪里。查看源码发现会分发的 NavigationBar
, NavigationBar
在创建时会把 NavBarHelper
注入, 在 onInt
方法中调用 NavBarHelper
中 registerNavTaskStateUpdater
方法设置监听
frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
public void onInit() {
// This currently MUST be called after mHomeButtonLongPressDurationMs is initialized since
// the registration callbacks will trigger code that uses it
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
// 省略部分代码
}
private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
new NavBarHelper.NavbarTaskbarStateUpdater() {
// 省略部分代码
@Override
public void updateRotationWatcherState(int rotation) {
if (mIsOnDefaultDisplay && mView != null) {
mView.getRotationButtonController().onRotationWatcherChanged(rotation);
if (mView.needsReorient(rotation)) {
repositionNavigationBar(rotation);
}
}
}
};
在接收到方向变化回调时调用了 RotationButtonController
中的 onRotationWatcherChanged
方法,下面看一下 RotationButtonController
中的实现
frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
public static final int NATURAL_ROTATION = Surface.ROTATION_0; // ROTATION_0 = 0
public void onRotationWatcherChanged(int rotation) {
if (!mListenersRegistered) {
// Ignore if not registered
return;
}
// If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
boolean rotationLocked = isRotationLocked();
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (rotationLocked || mRotationButton.isVisible()) {
// Do not allow a change in rotation to set user rotation when docked.
if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
}
}
private boolean shouldOverrideUserLockPrefs(final int rotation) {
if (mSkipOverrideUserLockPrefsOnce) {
mSkipOverrideUserLockPrefsOnce = false;
return false;
}
// Only override user prefs when returning to the natural rotation (normally portrait).
// Don't let apps that force landscape or 180 alter user lock.
return rotation == NATURAL_ROTATION;
}
public void setRotationLockedAtAngle(int rotationSuggestion) {
RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
/* rotation= */ rotationSuggestion);
}
上面代码执行完后,就会将系统显示方向设置为竖屏显示(user_rotation = 0
), 需要注意的地方是 shouldOverrideUserLockPrefs
方法,只有当 rotation 为 0 时,才会返回true , 也就是,只有当 rotation 为 0 的时候才会调用 setRotationLockedAtAngle
方法设置显示方向为竖屏, 当 rotation 为非 0 的时候, 不会调用 setRotationLockedAtAngle
方法设置显示方向, 也就解释了为平板默认设置为横屏,打开一个竖屏应用后,系统变为默认竖屏了。