本文共 44984 字,大约阅读时间需要 149 分钟。
本文以Activity.setRequestedOrientation为入口梳理下横竖屏切换的详细流程。 代码均是基于最新的11.0版本。
第一篇主要讲了横竖屏切换时的准备操作: 更新方向,执行冻屏,截图显示以及计算更新基于新的方向的DisplayInfo和Configuration。
第二篇主要讲下如何将更新后的Configuration通知到树形的窗口结构以及应用中。
第三篇主要讲下解冻的流程以及横竖屏旋转动画的流程。
场景:假设此时手机处于横屏90度状态,当前Activity处于前台resumed状态,点击button设置竖屏, 逻辑如下:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
整个流程如下图所示:
下面来一步步进行梳理:
Step 1. Activity.setRequestedOrientation
/** * Change the desired orientation of this activity. If the activity * is currently in the foreground or otherwise impacting the screen * orientation, the screen will immediately be changed (possibly causing * the activity to be restarted). Otherwise, this will be used the next * time the activity is visible. 注释已经解释得很详细了,就是改变activity的期望方向 * * @param requestedOrientation An orientation constant as used in * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. */ public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { if (mParent == null) { // 现在版本已经不使用ActivityParent,因此mParent一直为null try { ActivityTaskManager.getService().setRequestedOrientation( mToken, requestedOrientation); } catch (RemoteException e) { // Empty } } else { mParent.setRequestedOrientation(requestedOrientation); } }
先看下设置的参数: AcitivityInfo.screenOrientation
/** * Information you can retrieve about a particular application * activity or receiver. This corresponds to information collected * from the AndroidManifest.xml's <activity> and * <receiver> tags. */public class ActivityInfo extends ComponentInfo implements Parcelable { /** @hide */ @IntDef(prefix = { "SCREEN_ORIENTATION_" }, value = { SCREEN_ORIENTATION_UNSET, SCREEN_ORIENTATION_UNSPECIFIED, SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_PORTRAIT, SCREEN_ORIENTATION_USER, SCREEN_ORIENTATION_BEHIND, SCREEN_ORIENTATION_SENSOR, SCREEN_ORIENTATION_NOSENSOR, SCREEN_ORIENTATION_SENSOR_LANDSCAPE, SCREEN_ORIENTATION_SENSOR_PORTRAIT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_PORTRAIT, SCREEN_ORIENTATION_FULL_SENSOR, SCREEN_ORIENTATION_USER_LANDSCAPE, SCREEN_ORIENTATION_USER_PORTRAIT, SCREEN_ORIENTATION_FULL_USER, SCREEN_ORIENTATION_LOCKED }) @Retention(RetentionPolicy.SOURCE) public @interface ScreenOrientation { }}
一个int型变量,可以设置的参数上面都已经列出来,详细的解释可以到ActivityInfo.java文件中查看。然后就通过binder调用到系统服务端。
Step 2. ActivityTaskManagerService.setRequestedOrientation
/** * System service for managing activities and their containers (task, stacks, displays,... ). * * {@hide} */public class ActivityTaskManagerService extends IActivityTaskManager.Stub { }通过代码来看,系统服务端就是ActivityTaskManagerService @Override public void setRequestedOrientation(IBinder token, int requestedOrientation) { synchronized (mGlobalLock) { // 整个过程是在锁里执行的 ActivityRecord r = ActivityRecord.isInStackLocked(token); // 通过token找到对应的ActivityRecord实例 if (r == null) { return; } final long origId = Binder.clearCallingIdentity(); try { r.setRequestedOrientation(requestedOrientation); } finally { Binder.restoreCallingIdentity(origId); } } }
Step 3. ActivityRecord.setRequestedOrientation
/** * An entry in the history stack, representing an activity. 应用端的Acitivity在系统am侧是以ActivityRecord形式存在的。 */final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener { } void setRequestedOrientation(int requestedOrientation) { // mayFreezeScreenLocked()方法就是判断Activity所在进程是否处于可freeze的状态 // return hasProcess() && !app.isCrashing() && !app.isNotResponding(); setOrientation(requestedOrientation, mayFreezeScreenLocked()); // 通知相应的listener mAtmService.getTaskChangeNotificationController(). notifyActivityRequestedOrientationChanged(task.mTaskId, requestedOrientation); } private void setOrientation(int requestedOrientation, boolean freezeScreenIfNeeded) { final IBinder binder = (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null; // 通过代码来看, ActivityRecord,WindowToken都并未复写该方法, 因此直接看WindowContainer. setOrientation(requestedOrientation, binder, this); // Push the new configuration to the requested app in case where it's not pushed, e.g. when // the request is handled at task level with letterbox. if (!getMergedOverrideConfiguration().equals( mLastReportedConfiguration.getMergedConfiguration())) { ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); } }
Step 4. WindowContainer.setOrientation
/** * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * * @param orientation the specified orientation. Needs to be one of {@link * android.content.pm.ActivityInfo.ScreenOrientation}. * @param freezeDisplayToken uses this token to freeze display if orientation change is not * done. Display will not be frozen if this is {@code null}, which * should only happen in tests. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. */ void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken, @Nullable ConfigurationContainer requestingContainer) { if (mOrientation == orientation) { return; } mOrientation = orientation; // 更新ActivityRecord实例的方向值 final WindowContainer parent = getParent(); if (parent != null) { if (getConfiguration().orientation != getRequestedConfigurationOrientation()) { // Resolve the requested orientation. // 如果请求的方向与当前ActivityRecord方向不一致,以父节点的config更新当前Activity的config // 存在一种可能, Activity与当前的系统方向不一致,例如size compact mode,系统竖屏情况下,会强制 // 横屏unsizable activity缩放显示,但是此Activity仍是横屏方向,此时此Activity设置竖屏,就不会引起系统方向更新,只是更新Activity的config。 onConfigurationChanged(parent.getConfiguration()); } onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); } }
就像view是以树形结构管理的,其实系统端的窗口也是以树形结构管理。画下10.0和11.0的窗口结构(11.0改动稍微大些)如下图所示:
Step 5. WindowContainer.onDescendantOrientationChanged
/** * Called when this container or one of its descendants changed its requested orientation, and * wants this container to handle it or pass it to its parent. * * @param freezeDisplayToken freeze this app window token if display needs to freeze * @param requestingContainer the container which orientation request has changed * @return {@code true} if handled; {@code false} otherwise. */ boolean onDescendantOrientationChanged(@Nullable IBinder freezeDisplayToken, @Nullable ConfigurationContainer requestingContainer) { final WindowContainer parent = getParent(); if (parent == null) { return false; } return parent.onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); }
沿着树结构往上,该函数仅有Task和DisplayContent复写。针对全屏情况而言,这里仅看DisplayContent的相关逻辑。
Step 6. DisplayContent.onDescendantOrientationChanged
boolean onDescendantOrientationChanged(IBinder freezeDisplayToken, ConfigurationContainer requestingContainer) { // 1. 综合当前信息判断是否更新方向,执行冻屏,更新config,displayInfo等信息 关键函数,代码量较大: Step 7 -- Step 22 final Configuration config = updateOrientation( getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */); // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. final boolean handled = getDisplayRotation().respectAppRequestedOrientation(); if (config == null) { return handled; } if (handled && requestingContainer instanceof ActivityRecord) { final ActivityRecord activityRecord = (ActivityRecord) requestingContainer; final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord, false /* deferResume */, null /* result */); activityRecord.frozenBeforeDestroy = true; if (!kept) { mRootWindowContainer.resumeFocusedStacksTopActivities(); } } else { // We have a new configuration to push so we need to update ATMS for now. // TODO: Clean up display configuration push between ATMS and WMS after unification. updateDisplayOverrideConfigurationLocked(config, null /* starting */, false /* deferResume */, null); } return handled; }
Step 7. DisplayContent.updateOrientation参数分析
final Configuration config = updateOrientation( getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */); 第2参数就是指向请求方向更新的activity,第3个参数判断是否强制更新,后面会讲到,这里主要看下第一个参数。窗口管理架构结构都是继承WindowContainer,而WindowContainer继承了ConfigurationContainer。因此这里直接看到ConfigurationContainer. getRequestedOverrideConfiguration
/** Returns requested override configuration applied to this configuration container. */ public Configuration getRequestedOverrideConfiguration() { return mRequestedOverrideConfiguration; } /** * Contains requested override configuration settings applied to this configuration container. */ private Configuration mRequestedOverrideConfiguration = new Configuration();
RequestedOverrideConfiguration仅有一处赋值:ConfigurationContainer.onRequestedOverrideConfigurationChanged,稍后会介绍到
Step 8. DisplayContent.updateOrientation
/** * Update orientation of the display, returning a non-null new Configuration if it has * changed from the current orientation. If a non-null configuration is returned, someone must * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration, * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically * be done by calling {@link #sendNewConfiguration}.注释同样写得很详细了: 更新display的方向,如果方向更改则返回一个非null的新的config对象. 如果返回了非null的config,则必须调用WindowManagerService. setNewDisplayOverrideConfiguration告诉wm,告诉他可以解冻了。 * * @param currentConfig The current requested override configuration (it is usually set from * the last {@link #sendNewConfiguration}) of the display. It is used to * check if the configuration container has the latest state. * @param freezeDisplayToken Freeze the app window token if the orientation is changed. * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)} */ Configuration updateOrientation(Configuration currentConfig, IBinder freezeDisplayToken, boolean forceUpdate) { if (!mDisplayReady) { return null; } Configuration config = null; // 判断是否更新方向,执行冻屏等操作,如果方向 change,则return true。否则,return false if (updateOrientation(forceUpdate)) { // 关键函数: Step 9 -- Step 17 // If we changed the orientation but mOrientationChangeComplete is already true, // we used seamless rotation, and we don't need to freeze the screen. if (freezeDisplayToken != null && !mWmService.mRoot.mOrientationChangeComplete) { // 本文这种情况下并不满足 final ActivityRecord activity = getActivityRecord(freezeDisplayToken); if (activity != null) { activity.startFreezingScreen(); } } config = new Configuration(); // Compute display configuration based on display properties and policy settings. 如注释所示,就是基于display properties和policy setting计算display config和displayInfo Step 18 -- Step 22 computeScreenConfiguration(config); } else if (currentConfig != null) { // No obvious action we need to take, but if our current state mismatches the // activity manager's, update it, disregarding font scale, which should remain set // to the value of the previous configuration. // Here we're calling Configuration#unset() instead of setToDefaults() because we // need to keep override configs clear of non-empty values (e.g. fontSize). mTmpConfiguration.unset(); mTmpConfiguration.updateFrom(currentConfig); computeScreenConfiguration(mTmpConfiguration); // 先计算config if (currentConfig.diff(mTmpConfiguration) != 0) { // 方向未更改的情况下,config change, 同样冻屏 mWaitingForConfig = true; setLayoutNeeded(); mDisplayRotation.prepareNormalRotationAnimation(); config = new Configuration(mTmpConfiguration); } } return config; }
Step 9. DisplayContent.updateOrientation
private boolean updateOrientation(boolean forceUpdate) { // 综合各种情况,返回最合适的方向值。 这个要综合layer排序,窗口的方向值做判断。 假设设置方向的activity是top resumed activity,则一般会返回刚才设置的mOrientation Step 10 final int orientation = getOrientation(); // The last orientation source is valid only after getOrientation. final WindowContainer orientationSource = getLastOrientationSource(); final ActivityRecord r = orientationSource != null ? orientationSource.asActivityRecord() : null; if (r != null) { final Task task = r.getTask(); if (task != null && orientation != task.mLastReportedRequestedOrientation) { task.mLastReportedRequestedOrientation = orientation; mAtmService.getTaskChangeNotificationController() .notifyTaskRequestedOrientationChanged(task.mTaskId, orientation); } // Currently there is no use case from non-activity. 本文所说的这种情况下,返回false,暂不考虑,后面梳理其他情况下再介绍 if (handleTopActivityLaunchingInDifferentOrientation(r, true /* checkOpening */)) { // Display orientation should be deferred until the top fixed rotation is finished. return false; } } // DisplayRotation: 构造函数中创建,与DisplayContent一对一 Step 11 return mDisplayRotation.updateOrientation(orientation, forceUpdate); }Step 10. DisplayContent.getOrientation
11.0的树形结构还没梳理,稍后补充上。 但是应该和10.0差不了多少Step 11. DisplayRotation.updateOrientation
/**-
Defines the mapping between orientation and rotation of a display.
-
Non-public methods are assumed to run inside WM lock.
*/ public class DisplayRotation {}boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {
// 如果新返回的方向值与之前一致,且未强制更新的情况下,则return if (newOrientation == mLastOrientation && !forceUpdate) { return false; } mLastOrientation = newOrientation; // 仅在此处赋值 指向AcitivityInfo.ScreenOrientation类型 if (newOrientation != mCurrentAppOrientation) { mCurrentAppOrientation = newOrientation; if (isDefaultDisplay) { updateOrientationListenerLw(); // 更新方向监听器, 判断enable/disable 方向监听器 } } return updateRotationUnchecked(forceUpdate); // Step 12 继续判断是否更新方向 }
Step 12. DisplayRotation.updateRotationUnchecked
/** * Update rotation with an option to force the update. This updates the container’s perception * of rotation and, depending on the top activities, will freeze the screen or start seamless * rotation. The display itself gets rotated in {@link DisplayContent#applyRotationLocked} * during {@link DisplayContent#sendNewConfiguration}. * * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating * orientation because we’re waiting for some rotation to finish or display * to unfreeze, which results in configuration of the previously visible * activity being applied to a newly visible one. Forcing the rotation * update allows to workaround this issue. * @return {@code true} if the rotation has been changed. In this case YOU MUST CALL * {@link DisplayContent#sendNewConfiguration} TO COMPLETE THE ROTATION AND UNFREEZE * THE SCREEN. */ boolean updateRotationUnchecked(boolean forceUpdate) { final int displayId = mDisplayContent.getDisplayId(); if (!forceUpdate) { // 在非强制更新的情况下,有可能因为正在执行屏幕旋转动画,冻屏等因素,直接返回 if (mDeferredRotationPauseCount > 0) { // Rotation updates have been paused temporarily. Defer the update until updates // have been resumed. ProtoLog.v(WM_DEBUG_ORIENTATION, “Deferring rotation, rotation is paused.”); return false; }final ScreenRotationAnimation screenRotationAnimation = mDisplayContent.getRotationAnimation(); 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. ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress."); return false; } if (mService.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. ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, still finishing previous rotation"); return false; } if (mDisplayContent.mFixedRotationTransitionListener .isTopFixedOrientationRecentsAnimating()) { // During the recents animation, the closing app might still be considered on top. // In order to ignore its requested orientation to avoid a sensor led rotation (e.g // user rotating the device while the recents animation is running), we ignore // rotation update while the animation is running. return false; } } if (!mService.mDisplayEnabled) { // No point choosing a rotation if the display is not enabled. ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, display is not enabled."); return false; } final int oldRotation = mRotation; // mRotation:Current rotation of the display. display真正的方向 Surface.Rotation类型 final int lastOrientation = mLastOrientation; // 保存新计算出来的mLastOrientation // 综合sensor,rotation loack和docking mode等因素计算出合适的方向 final int rotation = rotationForOrientation(lastOrientation, oldRotation); ProtoLog.v(WM_DEBUG_ORIENTATION, "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and " + "oldRotation=%s (%d)", Surface.rotationToString(rotation), rotation, displayId, ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation, Surface.rotationToString(oldRotation), oldRotation); ProtoLog.v(WM_DEBUG_ORIENTATION, "Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId, ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation, Surface.rotationToString(rotation), rotation); if (oldRotation == rotation) { // 如果新计算出的方向与之前的方向一致,则return // No change. return false; } ProtoLog.v(WM_DEBUG_ORIENTATION, "Display id=%d rotation changed to %d from %d, lastOrientation=%d", displayId, rotation, oldRotation, lastOrientation); // int delta = newRotation - oldRotation; // if (delta < 0) delta += 4; 0:0 90:1 180:2 270:3 if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) { // 不是180度更改 // 表明系统config change,直到sent complete config才设置为false mDisplayContent.mWaitingForConfig = true; } mRotation = rotation; // 更新系统方向 mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; // send freeze timeout msg mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT, mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION); mDisplayContent.setLayoutNeeded(); // 允许窗口执行performLayout, 即计算新的窗口位置 // 判断旋转方式,后面一篇文章,我会梳理另一种更新方向的情况,设置fixedApp,就是samless rotate, 即不执行屏幕旋转动画和冻屏等操作 if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) { // 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. prepareSeamlessRotation(); } else { // 本文这种情况,执行旋转动画,冻屏等操作 Step 13-Step 17 这一步执行冻屏,截屏并显示 prepareNormalRotationAnimation(); } // Give a remote handler (system ui) some time to reposition things. startRemoteRotation(oldRotation, mRotation); return true;}
Step 13. DisplayRotation.prepareNormalRotationAnimation
void prepareNormalRotationAnimation() { cancelSeamlessRotation(); // 挑选合适的旋转动画,但是一般都是null,最后由ScreenRotationAnimation根据detla决定 final RotationAnimationPair anim = selectRotationAnimation(); mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent); // 开始冻屏 }Step 14. WindowManagerService.startFreezingDisplay
void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) { startFreezingDisplay(exitAnim, enterAnim, displayContent, ROTATION_UNDEFINED /* overrideOriginalRotation */); }void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent, int overrideOriginalRotation) { // 如果此时已经处于冻屏状态或者是seamless rotation,则return if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) { return; } if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) { // No need to freeze the screen before the display is ready, if the screen is off, // or we can't currently animate. 判断是否满足冻屏的条件 return; } ProtoLog.d(WM_DEBUG_ORIENTATION, "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s", exitAnim, enterAnim, Debug.getCallers(8)); mScreenFrozenLock.acquire(); // 避免灭屏 mDisplayFrozen = true; // 标记系统已处于冻屏状态 mDisplayFreezeTime = SystemClock.elapsedRealtime(); mLastFinishedFreezeSource = null; // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time. // As a result, we only track the display that has initially froze the screen. mFrozenDisplayId = displayContent.getDisplayId(); // 冻结input,其实原理很简单,就是设置值到InputDispatcher中,让其不再分发而已 mInputManagerCallback.freezeInputDispatchingLw(); if (displayContent.mAppTransition.isTransitionSet()) { displayContent.mAppTransition.freeze(); // app transition—activity切换时执行的动画 } if (PROFILE_ORIENTATION) { File file = new File("/data/system/frozen"); Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); mExitAnimId = exitAnim; mEnterAnimId = enterAnim; displayContent.updateDisplayInfo(); // 如果并没有指定方向,则从DisplayInfo中获取方向,注意此时系统config以及displayInfo尚未更新,因此返回的是之前的方向值,这里值异常的话,会导致旋转动画异常,等会儿详细分析 final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED ? overrideOriginalRotation : displayContent.getDisplayInfo().rotation; // new ScreenRotationAnimation,并将其保存到DisplayContent中 Step 15-Step17 displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent, originalRotation));}
Step 15. ScreenRotationAnimation.ScreenRotationAnimation
// This class handles the rotation animation when the device is rotated. 这个类用于处理设备旋转时的rotation animation. class ScreenRotationAnimation {} ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) { mService = displayContent.mWmService; mContext = mService.mContext; mDisplayContent = displayContent; displayContent.getBounds(mOriginalDisplayRect);// Screenshot does NOT include rotation! final DisplayInfo displayInfo = displayContent.getDisplayInfo(); // 又重新从DisplayInfo中获取原方向值,因为之前传进来的参数可能由overrideOriginalRotation指定, 这个值是真正决定解冻前,截图的显示位置 final int realOriginalRotation = displayInfo.rotation; final int originalWidth; final int originalHeight; if (displayContent.getDisplayRotation().isFixedToUserRotation()) { // Emulated orientation. mForceDefaultOrientation = true; originalWidth = displayContent.mBaseDisplayWidth; originalHeight = displayContent.mBaseDisplayHeight; } else { // Normal situation originalWidth = displayInfo.logicalWidth; originalHeight = displayInfo.logicalHeight; } // 获取宽度和高度,这个也比较好理解,以90->0度为例,此时DisplayInfo还是横屏下的数据, // 因此width为2400,height为1080(只是以分辨率为1080*2400举例), 那么此时计算出来的mWidth为1080,mHeight为2400 if (realOriginalRotation == Surface.ROTATION_90 || realOriginalRotation == Surface.ROTATION_270) { mWidth = originalHeight; mHeight = originalWidth; } else { mWidth = originalWidth; mHeight = originalHeight; } mOriginalRotation = originalRotation; // 保存前面传进来的参数,这个用于决定旋转动画 // If the delta is not zero, the rotation of display may not change, but we still want to // apply rotation animation because there should be a top app shown as rotated. So the // specified original rotation customizes the direction of animation to have better look // when restoring the rotated app to the same rotation as current display. final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation); final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270; mOriginalWidth = flipped ? originalHeight : originalWidth; mOriginalHeight = flipped ? originalWidth : originalHeight; // Utility class that runs a {@link ScreenRotationAnimation} on the {@link SurfaceAnimationRunner}. 10.0的旋转动画还运行在android.anim线程中 mSurfaceRotationAnimationController = new SurfaceRotationAnimationController(); // Check whether the current screen contains any secure content. final boolean isSecure = displayContent.hasSecureWindowOnScreen(); final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { mBackColorSurface = displayContent.makeChildSurface(null) .setName("BackColorSurface") .setColorLayer() .setCallsite("ScreenRotationAnimation") .build(); mScreenshotLayer = displayContent.makeOverlay() .setName("RotationLayer") .setBufferSize(mWidth, mHeight) .setSecure(isSecure) .setCallsite("ScreenRotationAnimation") .build(); // 这个就是ScreenShot suface,创建Surface相关,以后再详细写下吧 mEnterBlackFrameLayer = displayContent.makeOverlay() .setName("EnterBlackFrameLayer") .setContainerLayer() .setCallsite("ScreenRotationAnimation") .build(); // In case display bounds change, screenshot buffer and surface may mismatch so set a // scaling mode. SurfaceControl.Transaction t2 = mService.mTransactionFactory.get(); t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW); t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. // 下面那么多其实就是抓取一个截图绑定到surface上 final int displayId = displayContent.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); surface.copyFrom(mScreenshotLayer); SurfaceControl.ScreenshotGraphicBuffer gb = mService.mDisplayManagerInternal.systemScreenshot(displayId); if (gb != null) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ScreenRotationAnimation#getMedianBorderLuma"); mStartLuma = RotationAnimationUtils.getMedianBorderLuma(gb.getGraphicBuffer(), gb.getColorSpace()); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); try { surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(), gb.getColorSpace()); } catch (RuntimeException e) { Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage()); } // If the screenshot contains secure layers, we have to make sure the // screenshot surface we display it in also has FLAG_SECURE so that // the user can not screenshot secure layers via the screenshot surface. if (gb.containsSecureLayers()) { t.setSecure(mScreenshotLayer, true); } t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); t.setAlpha(mBackColorSurface, 1); t.show(mScreenshotLayer); t.show(mBackColorSurface); } else { Slog.w(TAG, "Unable to take screenshot of display " + displayId); } surface.destroy(); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: CREATE", mScreenshotLayer); setRotation(t, realOriginalRotation); // 设置RotationLayer的初始位置 t.apply(); // 立即显示RotationLayer}
Step 16. ScreenRotationAnimation.setRotation
public void setRotation(SurfaceControl.Transaction t, int rotation) { mCurRotation = rotation; // 暂时没啥用,后面执行config更新时,会再一次设置新的方向值// Compute the transformation matrix that must be applied // to the snapshot to make it stay in the same original position // with the current screen rotation. 计算原方向到0的delta int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); // 根据detla,mWidth,mHeight计算对应的Matrix setRotationTransform(t, mSnapshotInitialMatrix);}
Step 17. RotationAnimationUtils.createRotationMatrix
public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) { switch (rotation) { case Surface.ROTATION_0: outMatrix.reset(); break; case Surface.ROTATION_90: outMatrix.setRotate(90, 0, 0); outMatrix.postTranslate(height, 0); break; case Surface.ROTATION_180: outMatrix.setRotate(180, 0, 0); outMatrix.postTranslate(width, height); break; case Surface.ROTATION_270: outMatrix.setRotate(270, 0, 0); outMatrix.postTranslate(0, width); break; } } 这个我不知道你们有没有搞混,我之前是一直都不怎么理解。 这里简单介绍下: Matrix的主要用于坐标转换映射,我们可以通过 Matrix 矩阵来控制视图的变换。 比如以90->0的方向旋转为例,坐标原点都是左上角,此时穿进来的参数detla为3,即Surface.ROTATION_270,mWidth为1080,mHeight为2400。此时就得到了相应的变换。整个流程如下图,RotationLayer就得到了正确的坐标位置。private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) { if (mScreenshotLayer == null) { return; } matrix.getValues(mTmpFloats); float x = mTmpFloats[Matrix.MTRANS_X]; float y = mTmpFloats[Matrix.MTRANS_Y]; if (mForceDefaultOrientation) { mDisplayContent.getBounds(mCurrentDisplayRect); x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } t.setPosition(mScreenshotLayer, x, y); // 上面的情况计算出来的: x:0 y:1080 t.setMatrix(mScreenshotLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); t.setAlpha(mScreenshotLayer, (float) 1.0); t.show(mScreenshotLayer); // 应用计算出来的Matrix, 以及设置alpha,并显示}
Step 9 – Step 17 主要的逻辑就是: 综合多种信息判断是否更新方向,若方向更新,则判断是否是seamless raotation,若是,则不执行冻屏等操作,直接reruen,否则执行冻屏,冻结input,截屏并显示。如果方向不更新,直接reutrn。
下一步就是基于更新后的方向重新计算DisplayInfo和Configuration。 Step 18 – Step 22
Step 18. DisplayContent.computeScreenConfiguration
/** * Compute display configuration based on display properties and policy settings. * Do not call if mDisplayReady == false. */ void computeScreenConfiguration(Configuration config) { // 第一步就是根据最新信息,重新计算更新DisplayInfo Step 19 – Step 20 final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config); calculateBounds(displayInfo, mTmpBounds); // Step 21 基于新的方向计算bounds config.windowConfiguration.setBounds(mTmpBounds); config.windowConfiguration.setWindowingMode(getWindowingMode()); config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); // 更新WindowConfiguration的相关信息,我们经常看到的config信息的起始设置点就是这里final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode, displayInfo.displayCutout); // Step 22 更新appBounds,方向等值 config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) | ((displayInfo.flags & Display.FLAG_ROUND) != 0 ? Configuration.SCREENLAYOUT_ROUND_YES : Configuration.SCREENLAYOUT_ROUND_NO); // 下面就是更新Config的一些其他数据信息,以后遇到再看吧 config.densityDpi = displayInfo.logicalDensityDpi; config.colorMode = ((displayInfo.isHdr() && mWmService.hasHdrSupport()) ? Configuration.COLOR_MODE_HDR_YES : Configuration.COLOR_MODE_HDR_NO) | (displayInfo.isWideColorGamut() && mWmService.hasWideColorGamutSupport() ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO); // Update the configuration based on available input devices, lid switch, // and platform configuration. config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; config.keyboard = Configuration.KEYBOARD_NOKEYS; config.navigation = Configuration.NAVIGATION_NONAV; int keyboardPresence = 0; int navigationPresence = 0; final InputDevice[] devices = mWmService.mInputManager.getInputDevices(); final int len = devices != null ? devices.length : 0; for (int i = 0; i < len; i++) { InputDevice device = devices[i]; // Ignore virtual input device. if (device.isVirtual()) { continue; } // Check if input device can dispatch events to current display. // If display type is virtual, will follow the default display. if (!mWmService.mInputManager.canDispatchToDisplay(device.getId(), displayInfo.type == Display.TYPE_VIRTUAL ? DEFAULT_DISPLAY : mDisplayId)) { continue; } final int sources = device.getSources(); final int presenceFlag = device.isExternal() ? WindowManagerPolicy.PRESENCE_EXTERNAL : WindowManagerPolicy.PRESENCE_INTERNAL; if (mWmService.mIsTouchDevice) { if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) { config.touchscreen = Configuration.TOUCHSCREEN_FINGER; } } else { config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; } if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { config.navigation = Configuration.NAVIGATION_TRACKBALL; navigationPresence |= presenceFlag; } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD && config.navigation == Configuration.NAVIGATION_NONAV) { config.navigation = Configuration.NAVIGATION_DPAD; navigationPresence |= presenceFlag; } if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { config.keyboard = Configuration.KEYBOARD_QWERTY; keyboardPresence |= presenceFlag; } } if (config.navigation == Configuration.NAVIGATION_NONAV && mWmService.mHasPermanentDpad) { config.navigation = Configuration.NAVIGATION_DPAD; navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; } // Determine whether a hard keyboard is available and enabled. // TODO(multi-display): Should the hardware keyboard be tied to a display or to a device? boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; if (hardKeyboardAvailable != mWmService.mHardKeyboardAvailable) { mWmService.mHardKeyboardAvailable = hardKeyboardAvailable; mWmService.mH.removeMessages(REPORT_HARD_KEYBOARD_STATUS_CHANGE); mWmService.mH.sendEmptyMessage(REPORT_HARD_KEYBOARD_STATUS_CHANGE); } mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); // Let the policy update hidden states. config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);}
Step 19. DisplayContent.updateDisplayAndOrientation
/** * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config * changed. * Do not call if {@link WindowManagerService#mDisplayReady} == false. */ private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) { // Use the effective “visual” dimensions based on current rotation final int rotation = getRotation(); // 获取的是DisplayRotation.mRotation 前面已经设置为新的目标方向 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); // Overridden display size. Initialized with {@link #mInitialDisplayWidth} and {@link #mInitialDisplayHeight}, but can be set via shell command “adb shell wm size”. final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; // 这两个值应该不会因为方向的更改而更改,但是具体我也没深入研究过 final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;// Update application display metrics. // DisplayCutout:Represents the area of the display that is not functional for displaying content. 其实就是非全屏的前置摄像头所在的区域,为了避免内容被前置摄像头遮盖 final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); // appWidth = dw - nav bar width - (cutout safe left + cutout safe right) final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout); // appHeight计算也差不多,就不贴代码了 appHeight = dh - nav bar height - (cutout safe top + cutout safe bottom) final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout); mDisplayInfo.rotation = rotation; // 下面就是更新DisplayInfo的方向,宽度,高度等值 mDisplayInfo.logicalWidth = dw; mDisplayInfo.logicalHeight = dh; mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity; mDisplayInfo.appWidth = appWidth; mDisplayInfo.appHeight = appHeight; if (isDefaultDisplay) { mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); } mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; mDisplayInfo.getAppMetrics(mDisplayMetrics); if (mDisplayScalingDisabled) { mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; } else { mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; } computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh, mDisplayMetrics.density, outConfig); // We usually set the override info in DisplayManager so that we get consistent display // metrics values when displays are changing and don't send out new values until WM is aware // of them. However, we don't do this for displays that serve as containers for ActivityView // because we don't want letter-/pillar-boxing during resize. final DisplayInfo overrideDisplayInfo = mShouldOverrideDisplayConfiguration ? mDisplayInfo : null; mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, overrideDisplayInfo); mBaseDisplayRect.set(0, 0, dw, dh); if (isDefaultDisplay) { mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, mCompatDisplayMetrics); } return mDisplayInfo;}
Step 20. DisplayPolicy.getNonDecorDisplayWidth
/** * Return the display width available after excluding any screen * decorations that could never be removed in Honeycomb. That is, system bar or * button bar. */ public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, DisplayCutout displayCutout) { int width = fullWidth; // mHasNavigationBar: DisplayPolciy构造函数中赋值,DisplayPolicy是在DisplayContent构造函数中创建的,一对一。 所以应用设置显示或隐藏nav bar,并不会对该值造成影响 if (hasNavigationBar()) { final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { width -= getNavigationBarWidth(rotation, uiMode); } }// 而DisplayCutout的区域是由config_mainBuiltInDisplayCutoutRectApproximation提供的数据决定的,不同的机型,数据都会不一样 if (displayCutout != null) { width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight(); } return width;}
DisplayPolicy构造函数中: mHasNavigationBar的赋值逻辑
if (mDisplayContent.isDefaultDisplay) { mHasStatusBar = true; mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);// Allow a system property to override this. Used by the emulator. // See also hasNavigationBar(). String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); if ("1".equals(navBarOverride)) { mHasNavigationBar = false; } else if ("0".equals(navBarOverride)) { mHasNavigationBar = true; } } else { mHasStatusBar = false; mHasNavigationBar = mDisplayContent.supportsSystemDecorations(); }
Step 21. DisplayContent.calculateBounds
// Determines the current display bounds based on the current state private void calculateBounds(DisplayInfo displayInfo, Rect out) { // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. final int rotation = displayInfo.rotation; boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; int width = displayInfo.logicalWidth; int left = (physWidth - width) / 2; int height = displayInfo.logicalHeight; int top = (physHeight - height) / 2; // 基于新的方向计算outBounds,以本文的情况为例,out为(0,0–1080,22400) out.set(left, top, left + width, top + height); }Step 22. DisplayContent.computeScreenAppConfiguration
/** Compute configuration related to application without changing current display. / private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh, int rotation, int uiMode, DisplayCutout displayCutout) { final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout); final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout); // appWidth和appHeight的计算逻辑已经分析过了 mDisplayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); // 这里就是根据nav bar和displaycutout的坐标,计算出inset区域,逻辑就不贴了 final int leftInset = mTmpRect.left; final int topInset = mTmpRect.top; // AppBounds at the root level should mirror the app screen size. outConfig.windowConfiguration.setAppBounds(leftInset / left /, topInset / top /, leftInset + appWidth / right /, topInset + appHeight / bottom */); // 更新appBounds区域 outConfig.windowConfiguration.setRotation(rotation); // 更新方向,density等 outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;final float density = mDisplayMetrics.density; outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, displayCutout) / density); outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, displayCutout) / density); outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale); outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh);}
转载地址:https://cczheng.blog.csdn.net/article/details/115770155 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!