Android11系統源碼分析:屏幕旋轉
一、概述
本文轉屏流程從自動旋轉這一場景出發,研究設備橫屏時系統框架的動作流程。
轉屏基於Sensor框架,在system_server進程的開機打開屏幕階段借助SensorManager注冊加速度傳感器的監聽,以66.66ms的節奏接收回調結果。當傳感器加速度數據回調時,會在WindowOrientationListener#onSensorChanged方法中算法處理是否需要旋轉,主要受角度和加速度的影響。如果算法確定要旋轉則通知到wms,在其updateRotationUnchecked方法中發起轉屏動畫和通知ATMS配置變動進而回調通知Activity。
本文關注流程,下一篇文章會聚焦耗時這一指標探討“轉屏”這一模塊的性能優化。
整體架構較之前版本沒有變化,流程有稍許變動。類關系如下(圖轉自Android中的轉屏流程)
圖:轉屏相關類關系圖
二、情景分析:加速度傳感器的注冊監聽
Android系統Sensor傳感器服務框架是另外一個課題,本文的轉屏基於此。推薦以下兩篇文章。
圖:傳感器注冊及數據交互流程
上面兩篇文章非常詳細地剖析了sensor服務框架。
其中上圖展示了上層(java)通過sdk接口:SensorManager.java#regitsterListener()監聽具體傳感器的調用流程。包括發起注冊和注冊后sensor數據傳回來的流程。
轉屏基於sensor框架,相對與sensor框架,轉屏這個塊更多可以理解為業務代碼:system_server發起監聽后收到sensor數據,之后才是wms中轉屏的處理。所以sensor的架構流程本文不重點討論。
下面簡單過下system_server中轉屏模塊對加速度傳感器的監聽注冊。
"android.ui@23798" prio=5 tid=0x14 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks Binder:490_1@24058
blocks Binder:490_2@24059
blocks android.display@24062
blocks android.anim@24063
at android.hardware.SystemSensorManager.registerListenerImpl(SystemSensorManager.java:184)
- locked <0x5d11> (a java.util.HashMap)
at android.hardware.SensorManager.registerListener(SensorManager.java:854)
at com.android.server.policy.WindowOrientationListener.enable(WindowOrientationListener.java:161)
- locked <0x5e61> (a java.lang.Object)
at com.android.server.wm.DisplayRotation$OrientationListener.enable(DisplayRotation.java:1498)
at com.android.server.wm.DisplayRotation.updateOrientationListenerLw(DisplayRotation.java:971)
at com.android.server.wm.DisplayRotation.updateOrientationListener(DisplayRotation.java:897)
- locked <0x5e60> (a com.android.server.wm.WindowManagerGlobalLock)
at com.android.server.policy.PhoneWindowManager.finishScreenTurningOn(PhoneWindowManager.java:4595)
at com.android.server.policy.PhoneWindowManager.finishWindowsDrawn(PhoneWindowManager.java:4589)
at com.android.server.policy.PhoneWindowManager.access$200(PhoneWindowManager.java:235)
at com.android.server.policy.PhoneWindowManager$PolicyHandler.handleMessage(PhoneWindowManager.java:661)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)
at com.android.server.UiThread.run(UiThread.java:45)
如上是一份開機調用棧,本節的初始化流程指這個轉屏傳感器的監聽注冊。
開機流程中system_server進程默認注冊傳感器的監聽。同樣的在updateOrientationListenerLw方法的注釋有詳細說明,幾種情況的監聽動作。
需要理清的是,我們此處探討system_server進程,而不是某個app使用了SensorManager來監聽傳感器。
所以當前的結論為,開機流程的開啟屏幕階段,wms會借助PhoneWindowManaegr在DisplayRotation中注冊監聽。如此傳感器上報信息時可以回調通知(WindowOrientationListener.java$AccelSensorJudge#onSensorChanged),進而做算法處理是否轉屏,做轉屏動畫,之后再通知atms做app的configChagne回調等。
下面就重點的代碼展開分析。
DisplayRotation#updateOrientationListenerLw
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
925 /**
926 * Various use cases for invoking this function:
927 * <li>Screen turning off, should always disable listeners if already enabled.</li>
928 * <li>Screen turned on and current app has sensor based orientation, enable listeners
929 * if not already enabled.</li>
930 * <li>Screen turned on and current app does not have sensor orientation, disable listeners
931 * if already enabled.</li>
932 * <li>Screen turning on and current app has sensor based orientation, enable listeners
933 * if needed.</li>
934 * <li>screen turning on and current app has nosensor based orientation, do nothing.</li>
935 */
936 private void updateOrientationListenerLw() {
937 if (mOrientationListener == null || !mOrientationListener.canDetectOrientation()) {
938 // If sensor is turned off or nonexistent for some reason.
939 return;
940 }
941
942 final boolean screenOnEarly = mDisplayPolicy.isScreenOnEarly();
943 final boolean awake = mDisplayPolicy.isAwake();
944 final boolean keyguardDrawComplete = mDisplayPolicy.isKeyguardDrawComplete();
945 final boolean windowManagerDrawComplete = mDisplayPolicy.isWindowManagerDrawComplete();
946
947 // Could have been invoked due to screen turning on or off or
948 // change of the currently visible window's orientation.
949 ProtoLog.v(WM_DEBUG_ORIENTATION,
950 "screenOnEarly=%b, awake=%b, currentAppOrientation=%d, "
951 + "orientationSensorEnabled=%b, keyguardDrawComplete=%b, "
952 + "windowManagerDrawComplete=%b",
953 screenOnEarly, awake, mCurrentAppOrientation, mOrientationListener.mEnabled,
954 keyguardDrawComplete, windowManagerDrawComplete);
955
956 boolean disable = true;
957 // Note: We postpone the rotating of the screen until the keyguard as well as the
958 // window manager have reported a draw complete or the keyguard is going away in dismiss
959 // mode.
960 if (screenOnEarly && awake && ((keyguardDrawComplete && windowManagerDrawComplete))) {
961 if (needSensorRunning()) {
962 disable = false;
963 // Enable listener if not already enabled.
964 if (!mOrientationListener.mEnabled) {
965 // Don't clear the current sensor orientation if the keyguard is going away in
966 // dismiss mode. This allows window manager to use the last sensor reading to
967 // determine the orientation vs. falling back to the last known orientation if
968 // the sensor reading was cleared which can cause it to relaunch the app that
969 // will show in the wrong orientation first before correcting leading to app
970 // launch delays.
971 mOrientationListener.enable(true /* clearCurrentRotation */);
972 }
973 }
974 }
975 // Check if sensors need to be disabled.
976 if (disable && mOrientationListener.mEnabled) {
977 mOrientationListener.disable();
978 }
979 }
方法頭的注釋是情況說明,什么時候會注冊、關閉對轉屏傳感器的監聽。
重點關注960行的分支,在開機流程中,其值均為true。
961行,needSensorRunning方法判斷是否需要傳感器開啟,答案為默認開啟,僅在關屏和用戶主動關閉情況才會關掉。
964行,開機時mEnabled為初值false
走到971行,enable方法里開始注冊。需要注意的是,OrientationListener是WindowOrientationListener的子類,enable的實現在父類中,如下。
WindowOrientationListener#enable
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
private class OrientationListener extends WindowOrientationListener {
@Override
public void enable(boolean clearCurrentRotation) {
super.enable(clearCurrentRotation);
mEnabled = true;
ProtoLog.v(WM_DEBUG_ORIENTATION, "Enabling listeners");
}
frameworks/base/services/core/java/com/android/server/policy/WindowOrientationListener.java
139 /**
140 * Enables the WindowOrientationListener so it will monitor the sensor and call
141 * {@link #onProposedRotationChanged(int)} when the device orientation changes.
142 *
143 * @param clearCurrentRotation True if the current proposed sensor rotation should be cleared as
144 * part of the reset.
145 */
146 public void enable(boolean clearCurrentRotation) {
147 synchronized (mLock) {
148 if (mSensor == null) {
149 Slog.w(TAG, "Cannot detect sensors. Not enabled");
150 return;
151 }
152 if (mEnabled) {
153 return;
154 }
155 if (LOG) {
156 Slog.d(TAG, "WindowOrientationListener enabled clearCurrentRotation="
157 + clearCurrentRotation);
158 }
159 mOrientationJudge.resetLocked(clearCurrentRotation);
160 if (mSensor.getType() == Sensor.TYPE_ACCELEROMETER) {
161 mSensorManager.registerListener(
162 mOrientationJudge, mSensor, mRate, DEFAULT_BATCH_LATENCY, mHandler);
163 } else {
164 mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler);
165 }
166 mEnabled = true;
167 }
168 }
注釋說的很清楚,enable方法注冊傳感器監聽,當傳感器數據上報時回調方法#onProposedRotationChanged。
走進161行分支,看下幾個關鍵的參數。
在WindowOrientationListener的構造函數中對mSensor和mOrientationJudge完成了初始化。
public WindowOrientationListener(Context context, Handler handler) {
this(context, handler, SensorManager.SENSOR_DELAY_UI);
}
private WindowOrientationListener(Context context, Handler handler, int rate) {
if (mOrientationJudge == null) {
mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
mOrientationJudge = new AccelSensorJudge(context);
- mSensor是TYPE_ACCELEROMETER加速度傳感器
模擬器上的傳感器參數是這樣:{Sensor name="Goldfish 3-axis Accelerometer", vendor="The Android Open Source Project", version=1, type=1, maxRange=39.300007, resolution=2.480159E-4, power=3.0, minDelay=10000}
-
mOrientationJudge是AccelSensorJudge類的實例
這個類很重要,轉屏算法就在他的onSensorChanged方法中。算法內容可以參考這篇博文:Android轉屏流程與優化(Google轉屏算法)
-
mRate
代表延遲水平,有這幾種定義。此處默認設為2,接收到的回調時間間隔66.666666ms。
frameworks/base/core/java/android/hardware/SensorManager.java
317 /** get sensor data as fast as possible */ 318 public static final int SENSOR_DELAY_FASTEST = 0; 319 /** rate suitable for games */ 320 public static final int SENSOR_DELAY_GAME = 1; 321 /** rate suitable for the user interface */ 322 public static final int SENSOR_DELAY_UI = 2; 323 /** rate (default) suitable for screen orientation changes */ 324 public static final int SENSOR_DELAY_NORMAL = 3;
-
DEFAULT_BATCH_LATENCY
最大批量報告延遲,us微秒
-
handler
是我們的UiThread的handler,我們當前是處於system_server進程的ui線程中。堆棧信息有寫。
最后階段,看下registerListener的實現
SensorManager#registerListener
frameworks/base/core/java/android/hardware/SensorManager.java
851 public boolean registerListener(SensorEventListener listener, Sensor sensor,
852 int samplingPeriodUs, int maxReportLatencyUs, Handler handler) {
853 int delayUs = getDelay(samplingPeriodUs);
854 return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
855 }
frameworks/base/core/java/android/hardware/SystemSensorManager.java
144 @Override
145 protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
146 int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
147 if (listener == null || sensor == null) {
148 Log.e(TAG, "sensor or listener is null");
149 return false;
150 }
151 // Trigger Sensors should use the requestTriggerSensor call.
152 if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
153 Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
154 return false;
155 }
156 if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
157 Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
158 return false;
159 }
160 if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
161 throw new IllegalStateException("register failed, "
162 + "the sensor listeners size has exceeded the maximum limit "
163 + MAX_LISTENER_COUNT);
164 }
165
166 // Invariants to preserve:
167 // - one Looper per SensorEventListener
168 // - one Looper per SensorEventQueue
169 // We map SensorEventListener to a SensorEventQueue, which holds the looper
170 synchronized (mSensorListeners) {
171 SensorEventQueue queue = mSensorListeners.get(listener);
172 if (queue == null) {
173 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
174 final String fullClassName =
175 listener.getClass().getEnclosingClass() != null
176 ? listener.getClass().getEnclosingClass().getName()
177 : listener.getClass().getName();
178 queue = new SensorEventQueue(listener, looper, this, fullClassName);
179 if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
180 queue.dispose();
181 return false;
182 }
183 mSensorListeners.put(listener, queue);
184 return true;
185 } else {
186 return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
187 }
188 }
189 }
兩個重點,178行SensorEventQueue初始化,179行addSensor方法,都主要在native里。
queue.addSensor
上面小節,走過漫長的queue初始化,下面開始使用這個初始化后的隊列,增加傳感器監聽。
frameworks/base/core/java/android/hardware/SystemSensorManager.java
629 private abstract static class BaseEventQueue {
663 public boolean addSensor(
664 Sensor sensor, int delayUs, int maxBatchReportLatencyUs) {
665 // Check if already present.
666 int handle = sensor.getHandle();
667 if (mActiveSensors.get(handle)) return false;
668
669 // Get ready to receive events before calling enable.
670 mActiveSensors.put(handle, true);
671 addSensorEvent(sensor);
672 if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
673 // Try continuous mode if batching fails.
674 if (maxBatchReportLatencyUs == 0
675 || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
676 removeSensor(sensor, false);
677 return false;
678 }
679 }
680 return true;
681 }
670行,mActiveSensors是個緩存。
671行addSensorEvent實現在子類SensorEventQueue中。
frameworks/base/core/java/android/hardware/SystemSensorManager.java
static final class SensorEventQueue extends BaseEventQueue {
@Override
public void addSensorEvent(Sensor sensor) {
SensorEvent t = new SensorEvent(Sensor.getMaxLengthValuesArray(sensor,
mManager.mTargetSdkLevel));
synchronized (mSensorsEvents) {
mSensorsEvents.put(sensor.getHandle(), t);
這里的mSensorsEvents也是個緩存array。
enableSensor
frameworks/base/core/java/android/hardware/SystemSensorManager.java
static final class SensorEventQueue extends BaseEventQueue {
private int enableSensor(
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
maxBatchReportLatencyUs);
native的代碼都略去了,詳細可以閱讀上面的兩篇推薦文。
system_server進程中開機流程的wms屏幕開啟階段,加速度傳感器的監聽注冊流程大致如此。
三、情景分析:點擊“自動旋轉”
設備中自動旋轉的開關在systemui下拉菜單中,如下圖
systemui進程
第一份binder trace如下
Trace: java.lang.Throwable
at android.os.BinderProxy.transact(BinderProxy.java:509)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:730)
at android.provider.Settings$NameValueCache.putStringForUser(Settings.java:2667)
at android.provider.Settings$System.putStringForUser(Settings.java:3258)
at android.provider.Settings$System.putStringForUser(Settings.java:3242)
at android.provider.Settings$System.putIntForUser(Settings.java:3367)
at com.android.internal.view.RotationPolicy.setRotationLockAtAngle(RotationPolicy.java:122)
at com.android.internal.view.RotationPolicy.setRotationLock(RotationPolicy.java:114)
at com.android.systemui.statusbar.policy.RotationLockControllerImpl.setRotationLocked(RotationLockControllerImpl.java:68)
at com.android.systemui.qs.tiles.RotationLockTile.handleClick(RotationLockTile.java:62)
at com.android.systemui.qs.tileimpl.QSTileImpl$H.handleMessage(QSTileImpl.java:561)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
可以看到,自動旋轉暴露給app層的接口為com.android.internal.view.RotationPolicy.setRotationLock
frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
109 /**
110 * Enables or disables rotation lock from the system UI toggle.
111 */
112 public static void setRotationLock(Context context, final boolean enabled) {
113 final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
114 setRotationLockAtAngle(context, enabled, rotation);
115 }
116
117 /**
118 * Enables or disables rotation lock at a specific rotation from system UI.
119 */
120 public static void setRotationLockAtAngle(Context context, final boolean enabled,
121 final int rotation) {
122 Settings.System.putIntForUser(context.getContentResolver(),
123 Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
124 UserHandle.USER_CURRENT);
125
126 setRotationLock(enabled, rotation);
127 }
跟到122行,修改的數據庫字段為Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY
和輔助服務有關,0代表不隱藏。此處不重點關注。
126行,在這個方法中使用AsyncTask向wms發起binder ipc
frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
146 private static void setRotationLock(final boolean enabled, final int rotation) {
147 AsyncTask.execute(new Runnable() {
148 @Override
149 public void run() {
150 try {
151 IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
152 if (enabled) {
153 wm.freezeRotation(rotation);
154 } else {
155 wm.thawRotation();
156 }
157 } catch (RemoteException exc) {
158 Log.w(TAG, "Unable to save auto-rotate setting");
159 }
160 }
161 });
162 }
同樣的在trace中體現如下
Trace: java.lang.Throwable
at android.os.BinderProxy.transact(BinderProxy.java:509)
at android.view.IWindowManager$Stub$Proxy.thawRotation(IWindowManager.java:3902)
at com.android.internal.view.RotationPolicy$1.run(RotationPolicy.java:155)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
接下來進入system_server進程,看下wms的處理
system_server進程
binder trace如下
Trace: java.lang.Throwable
at android.os.BinderProxy.transact(BinderProxy.java:509)
at com.android.internal.statusbar.IStatusBar$Stub$Proxy.setTopAppHidesStatusBar(IStatusBar.java:1652)
at com.android.server.statusbar.StatusBarManagerService$1.setTopAppHidesStatusBar(StatusBarManagerService.java:423)
at com.android.server.wm.StatusBarController.setTopAppHidesStatusBar(StatusBarController.java:102)
at com.android.server.wm.DisplayPolicy.finishPostLayoutPolicyLw(DisplayPolicy.java:2748)
at com.android.server.wm.DisplayContent.applySurfaceChangesTransaction(DisplayContent.java:3940)
at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:1068)
at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:845)
at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:802)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:127)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:116)
at com.android.server.wm.WindowManagerService.updateRotationUnchecked(WindowManagerService.java:3844)
at com.android.server.wm.WindowManagerService.thawDisplayRotation(WindowManagerService.java:3776)
at com.android.server.wm.WindowManagerService.thawRotation(WindowManagerService.java:3746)
at android.view.IWindowManager$Stub.onTransact(IWindowManager.java:1948)
at com.android.server.wm.WindowManagerService.onTransact(WindowManagerService.java:1350)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
binder trace、有時候並不能如你的意,因為只有ipc才會被收集調用棧,oneway的情況就只可以看到發起進程的棧了。另外,服務端進程的棧有些進程內部的動作,也無法體現在trace上。例如上面這份trace,顯示wms里bidner ipc溝通statusbar,這個動作不是我們主要關注的內容,wms內部的處理流程才是。
所以binder trace不一定完全切合我們需求,具體情景具體分析。下面我們跟蹤這份trace看wms內部的處理。
WindowManagerService.thawRotation
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
3754 @Override
3755 public void thawRotation() {
3756 thawDisplayRotation(Display.DEFAULT_DISPLAY);
3757 }
3763 @Override
3764 public void thawDisplayRotation(int displayId) {
3765 if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
3766 "thawRotation()")) {
3767 throw new SecurityException("Requires SET_ORIENTATION permission");
3768 }
3769
3770 ProtoLog.v(WM_DEBUG_ORIENTATION, "thawRotation: mRotation=%d", getDefaultDisplayRotation());
3771
3772 long origId = Binder.clearCallingIdentity();
3773 try {
3774 synchronized (mGlobalLock) {
3775 final DisplayContent display = mRoot.getDisplayContent(displayId);
3776 if (display == null) {
3777 Slog.w(TAG, "Trying to thaw rotation for a missing display.");
3778 return;
3779 }
3780 display.getDisplayRotation().thawRotation();
3781 }
3782 } finally {
3783 Binder.restoreCallingIdentity(origId);
3784 }
3785
3786 updateRotationUnchecked(false, false);
3787 }
3765行鑒權,這是系統api。
3780行,在DisplayRotation中設置settings lib 數據庫。
3786行,updateRotationUnchecked方法是重點。
DisplayRotation.thawRotation
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
826 void thawRotation() {
827 setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
828 }
790 @VisibleForTesting
791 void setUserRotation(int userRotationMode, int userRotation) {
792 if (isDefaultDisplay) {
793 // We'll be notified via settings listener, so we don't need to update internal values.
794 final ContentResolver res = mContext.getContentResolver();
795 final int accelerometerRotation =
796 userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;
797 Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,
798 accelerometerRotation, UserHandle.USER_CURRENT);
799 Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,
800 UserHandle.USER_CURRENT);
801 return;
802 }
803
804 boolean changed = false;
805 if (mUserRotationMode != userRotationMode) {
806 mUserRotationMode = userRotationMode;
807 changed = true;
808 }
809 if (mUserRotation != userRotation) {
810 mUserRotation = userRotation;
811 changed = true;
812 }
813 mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
814 userRotation);
815 if (changed) {
816 mService.updateRotation(true /* alwaysSendConfiguration */,
817 false /* forceRelayout */);
818 }
819 }
797、799行更新兩個重要的數據庫值
Settings.System.ACCELEROMETER_ROTATION
Settings.System.USER_ROTATION
設置之后監聽這些值的地方會觸發ContentResolver回調,例如進程外的systemui、launcher和進程內DisplayRotation。
我們關注系統進程的數據庫監聽。
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
1511 private class SettingsObserver extends ContentObserver {
1512 SettingsObserver(Handler handler) {
1513 super(handler);
1514 }
1515
1516 void observe() {
1517 final ContentResolver resolver = mContext.getContentResolver();
1518 resolver.registerContentObserver(Settings.Secure.getUriFor(
1519 Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
1520 UserHandle.USER_ALL);
1521 resolver.registerContentObserver(Settings.System.getUriFor(
1522 Settings.System.ACCELEROMETER_ROTATION), false, this,
1523 UserHandle.USER_ALL);
1524 resolver.registerContentObserver(Settings.System.getUriFor(
1525 Settings.System.USER_ROTATION), false, this,
1526 UserHandle.USER_ALL);
1527 updateSettings();
1528 }
1529
1530 @Override
1531 public void onChange(boolean selfChange) {
1532 if (updateSettings()) {
1533 mService.updateRotation(true /* alwaysSendConfiguration */,
1534 false /* forceRelayout */);
1535 }
1536 }
1537 }
1531行的回調得到觸發。1532行默認為true,代表需要wms做進一步處理。
走到1533行,wms的updateRotation方法。參數1代表要更新配置,參數2為不強制更新布局。
WindowManagerService.updateRotation
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
3806 /**
3807 * Recalculate the current rotation.
3808 *
3809 * Called by the window manager policy whenever the state of the system changes
3810 * such that the current rotation might need to be updated, such as when the
3811 * device is docked or rotated into a new posture.
3812 */
3813 @Override
3814 public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
3815 updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
3816 }
3818 private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
3819 ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
3820 + " alwaysSendConfiguration=%b forceRelayout=%b",
3821 alwaysSendConfiguration, forceRelayout);
3822
3823 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
3824
3825 long origId = Binder.clearCallingIdentity();
3826
3827 try {
3828 synchronized (mGlobalLock) {
3829 boolean layoutNeeded = false;
3830 final int displayCount = mRoot.mChildren.size();
3831 for (int i = 0; i < displayCount; ++i) {
3832 final DisplayContent displayContent = mRoot.mChildren.get(i);
3833 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
3834 final boolean rotationChanged = displayContent.updateRotationUnchecked();
3835 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3836
3837 if (rotationChanged) {
3838 mAtmService.getTaskChangeNotificationController()
3839 .notifyOnActivityRotation(displayContent.mDisplayId);
3840 }
3841
3842 if (!rotationChanged || forceRelayout) {
3843 displayContent.setLayoutNeeded();
3844 layoutNeeded = true;
3845 }
3846 if (rotationChanged || alwaysSendConfiguration) {
3847 displayContent.sendNewConfiguration();
3848 }
3849 }
3850
3851 if (layoutNeeded) {
3852 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
3853 "updateRotation: performSurfacePlacement");
3854 mWindowPlacerLocked.performSurfacePlacement();
3855 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3856 }
3857 }
3858 } finally {
3859 Binder.restoreCallingIdentity(origId);
3860 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3861 }
3862 }
3834行displayContent.updateRotationUnchecked()計算是否需要旋轉。由於這次我們的點擊操作是在屏幕豎起時,所以計算不做旋轉,如下圖。那另一種情況,橫屏時點擊的話,自然就觸發旋轉了。這也符合我們的認知。
圖:displayContent.updateRotationUnchecked檢查不做旋轉
3837行,rotationChanged為false,所以不需要更新配置。
3842、3846、3851行本次流程沒什么需要關注的。
總結一下
- 點擊自動旋轉涉及兩個進程三個角色。
兩個進程:systemui、system_server
三個角色:systemui界面按鈕、settingslib數據庫、DisplayRotation
- 動作流程
人點擊systemui的自動旋轉按鈕,binder溝通wm.thawRotation
wms寫settingslib:Settings.System.ACCELEROMETER_ROTATION、Settings.System.USER_ROTATION
DisplayRotation監聽這倆數據庫值,觸發轉屏檢查mService.updateRotation
四、情景分析:轉屏流程
轉屏從人的操作體驗上來說,可以這樣描述:把屏幕橫過來,經過短暫的等待看到屏幕上圖像開始旋轉,然后是圖像旋轉結束。
從代碼流程上看,可以分為三部分
一是硬件、驅動檢測到加速度變化然后傳給監聽者(WindowOrientationListener)
二是WindowOrientationListener的轉屏算法,確定是否要轉
三是wms里做轉屏動畫
3.1硬件、驅動參數上報
本文不討論這塊
3.2轉屏算法確認是否轉屏
見性能優化篇。
3.3 轉屏動畫
"android.ui@23942" prio=5 tid=0x14 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at com.android.server.wm.DisplayRotation$OrientationListener.onProposedRotationChanged(DisplayRotation.java:1487)
at com.android.server.policy.WindowOrientationListener$AccelSensorJudge.onSensorChanged(WindowOrientationListener.java:818)
at android.hardware.SystemSensorManager$SensorEventQueue.dispatchSensorEvent(SystemSensorManager.java:837)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loop(Looper.java:183)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)
at com.android.server.UiThread.run(UiThread.java:45)
這是一份堆棧信息,展示了轉屏流程的第一階段,是在system_server進程android.ui線程。
回調是looper的epoll對fd的監聽,詳細可以看第一節的兩篇文章和學習下looper的監聽機制。
轉屏算法處理在WindowOrientationListener$AccelSensorJudge.onSensorChanged回調方法中,本文不展開。
走出了onSensorChanged方法即代表算法認可當前需要旋轉,所以下一步是wms做處理動作,我們繼續往下看。
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
@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);
}
執行runnable
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
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 {
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
if判斷為false,走到mService.updateRotation。這個是wms暴露的轉屏接口,上一節的自動轉屏也是走到這個API。
先放一下調用棧
"android.ui@23972" prio=5 tid=0x14 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks android.fg@24280
blocks android.display@24282
blocks android.anim@24283
blocks ActivityManager@24287
at com.android.server.wm.ScreenRotationAnimation.setRotationTransform(ScreenRotationAnimation.java:270)
at com.android.server.wm.ScreenRotationAnimation.setRotation(ScreenRotationAnimation.java:332)
at com.android.server.wm.ScreenRotationAnimation.<init>(ScreenRotationAnimation.java:254)
at com.android.server.wm.WindowManagerService.startFreezingDisplay(WindowManagerService.java:5610)
at com.android.server.wm.WindowManagerService.startFreezingDisplay(WindowManagerService.java:5562)
at com.android.server.wm.DisplayRotation.prepareNormalRotationAnimation(DisplayRotation.java:549)
at com.android.server.wm.DisplayRotation.updateRotationUnchecked(DisplayRotation.java:491)
at com.android.server.wm.DisplayContent.updateRotationUnchecked(DisplayContent.java:1700)
at com.android.server.wm.WindowManagerService.updateRotationUnchecked(WindowManagerService.java:3834)
- locked <0x5f48> (a com.android.server.wm.WindowManagerGlobalLock)
at com.android.server.wm.WindowManagerService.updateRotation(WindowManagerService.java:3815)
at com.android.server.wm.DisplayRotation$OrientationListener$UpdateRunnable.run(DisplayRotation.java:1479)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)
at com.android.server.UiThread.run(UiThread.java:45)
這是凍結屏幕准備參數的的調用棧
"android.display@23994" prio=5 tid=0x16 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks android.anim@23993
blocks Binder:486_D@24124
blocks Binder:486_E@23997
at com.android.server.wm.ScreenRotationAnimation$SurfaceRotationAnimationController.startScreenRotationAnimation(ScreenRotationAnimation.java:576)
at com.android.server.wm.ScreenRotationAnimation.startAnimation(ScreenRotationAnimation.java:427)
at com.android.server.wm.ScreenRotationAnimation.dismiss(ScreenRotationAnimation.java:445)
at com.android.server.wm.WindowManagerService.stopFreezingDisplayLocked(WindowManagerService.java:5682)
at com.android.server.wm.ActivityRecord.stopFreezingScreen(ActivityRecord.java:5311)
at com.android.server.wm.ActivityRecord.onAppFreezeTimeout(ActivityRecord.java:5272)
at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:4932)
- locked <0x5e42> (a com.android.server.wm.WindowManagerGlobalLock)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)
這是過渡動畫啟動的調用棧
動畫的啟動流程此處不展開,細節可參考:Android 11--橫豎屏旋轉時背景色異常?
需要關注的主要有兩處,一是動畫,二是通知ATMS做config的變動進而通知activity響應轉屏,都在wms的updateRotationUnchecked方法里
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
3818 private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
3819 ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
3820 + " alwaysSendConfiguration=%b forceRelayout=%b",
3821 alwaysSendConfiguration, forceRelayout);
3822
3823 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
3824
3825 long origId = Binder.clearCallingIdentity();
3826
3827 try {
3828 synchronized (mGlobalLock) {
3829 boolean layoutNeeded = false;
3830 final int displayCount = mRoot.mChildren.size();
3831 for (int i = 0; i < displayCount; ++i) {
3832 final DisplayContent displayContent = mRoot.mChildren.get(i);
3833 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
3834 final boolean rotationChanged = displayContent.updateRotationUnchecked();
3835 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3836
3837 if (rotationChanged) {
3838 mAtmService.getTaskChangeNotificationController()
3839 .notifyOnActivityRotation(displayContent.mDisplayId);
3840 }
3841
3842 if (!rotationChanged || forceRelayout) {
3843 displayContent.setLayoutNeeded();
3844 layoutNeeded = true;
3845 }
3846 if (rotationChanged || alwaysSendConfiguration) {
3847 displayContent.sendNewConfiguration();
3848 }
3849 }
3850
3851 if (layoutNeeded) {
3852 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
3853 "updateRotation: performSurfacePlacement");
3854 mWindowPlacerLocked.performSurfacePlacement();
3855 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3856 }
3857 }
3858 } finally {
3859 Binder.restoreCallingIdentity(origId);
3860 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3861 }
3862 }
重點1:3834行處理轉屏動畫
重點2:3838,3847行通知ATMS處理旋轉和配置變動