Android Framework--转屏原理
来源:互联网 发布:周哥k101淘宝店 编辑:程序博客网 时间:2024/06/01 15:58
本文带你从framework的视角了解转屏从产生到结束这一过程,应用开发中转屏相关的知识点已经有很多现成的资料,不在这里的讨论范围。
转屏的流程非常简单,如下:
下面将分为三个阶段进行讨论,本文贴出的代码来源于Android N。
转屏的产生
框架利用一定的策略来确定当前的屏幕方向,依据主要是窗口的screenOrientation,以及其它的一些状态,比如系统是否开启了屏幕旋转、系统是否固定了屏幕方向、是否处于dock模式等。
在讲如何确定屏幕方向前,先介绍窗口的screenOrientation。对于一个窗口,根据是否Activity窗口,以不同的方式来声明screenOrientation:
- Activity窗口:
1、(静态)AndroidManifest.xml中配置screenOrientation
2、(动态)通过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. * * @param requestedOrientation An orientation constant as used in * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. */public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { if (mParent == null) { try { ActivityManagerNative.getDefault().setRequestedOrientation( mToken, requestedOrientation); } catch (RemoteException e) { // Empty } } else { mParent.setRequestedOrientation(requestedOrientation); }}
- 非Activity窗口:指定WindowManager.LayoutParams.screenOrientation
/** * Specific orientation value for a window. * May be any of the same values allowed * for {@link android.content.pm.ActivityInfo#screenOrientation}. * If not set, a default value of * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} * will be used. */public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
窗口的screenOrientation默认为ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED(不指定方向),另外还有个特殊的值为ActivityInfo.SCREEN_ORIENTATION_BEHIND(由下方的窗口指定方向)。
现在可以开始讲屏幕方向的确定过程,主要是两个步骤:
从上到下遍历窗口堆栈,在可见的窗口中确定一个基准screenOrientation
1)非Activity窗口的screenOrientation如果不是上述的两个值,则结束遍历
2)一旦遍历到一个Activity窗口,接下来仅在Activity窗口中遍历,不再关心非Activity窗口根据第一步算出的基准screenOrientation,按照一定策略计算出最终屏幕方向,有兴趣的可以参考源码
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.javapublic int rotationForOrientationLw(int orientation, int lastRotation);
根据第一点可以知道,一个比较高层级的非Activity窗口,是可以通过配置screenOrientation来让自己成为基准screenOrientation的确定者,进而影响最终的屏幕方向。
那么转屏的原因很简单,即系统在某一时刻计算出的屏幕方向发生了变化,常见的场景如下:
- 横竖屏界面的切换,比如在Launcher上启动了个横屏应用,或者从横屏应用返回Launcher
- 当前应用未指定方向,然后手动旋转设备方向
关键点在于基准screenOrientation的确定,越顶层的可见窗口越有机会决定基准screenOrientation。
转屏的准备
好,现在系统检测到要改变屏幕方向,接下来屏幕上能看到的所有窗口都要以新的尺寸来重新绘制,大家想一下会遇到什么问题。由于各个窗口的重绘不可能在同一时刻完成,也不可能同时刷新,如果我们什么都不做,在屏幕上将会看到各种闪烁。
怎么办?
以竖屏切换到横屏为例,涉及到两个状态的切换,如果我们把竖屏的界面保存下来,再把横屏时应该显示的界面通通准备好,然后加入动画在两个状态间平滑的过渡,问题就可以迎刃而解。这个从概念上有点类似于framebuffer中的双缓冲区,待back buffer渲染完成后,再推到前台显示。
我们来看一下这个方案会遇到什么问题:
- 竖屏的状态以什么方式保存?
- 横屏界面在绘制时怎么不被用户察觉?
- 怎么确定横屏状态所需的界面?
- 过渡的动画怎么设计?
看下Android是如何解决的:
- 截屏来保存竖屏状态
- 用第一步的截屏生成一个非常高层级的,完全不透明的Layer,让用户在视觉上一直停留在竖屏状态,由于是完全不透明,所以在它下方的所有内容完全不可见,那么就尽情地绘制吧
- 方向改变前哪些窗口可见,就必须等待那些窗口绘制完成,这个很好理解
- 截屏Layer为整体,施加旋转并淡出的动画,重绘的所有横屏界面为另一个整体,也施加一个旋转动画,这样当动画结束时看到的就是重绘后的横屏界面。还有一点需要注意,转屏过程中系统会禁用所有其它动画,避免动画的叠加。
截屏的代码,关键位置加了注释:
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.javapublic ScreenRotationAnimation(Context context, DisplayContent displayContent, SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation, boolean isSecure) { ... try { try { int flags = SurfaceControl.HIDDEN; if (isSecure) { flags |= SurfaceControl.SECURE; } if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface", mWidth, mHeight, PixelFormat.OPAQUE, flags); Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset=" + mOriginalDisplayRect.toShortString()); } else { mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface", mWidth, mHeight, PixelFormat.OPAQUE, flags); // 新建一个OPAQUE的Layer,即完全不透明 } // capture a screenshot into the surface we just created Surface sur = new Surface(); sur.copyFrom(mSurfaceControl); // FIXME: we should use the proper display SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur); // 调用screenshot截屏,截屏内容将作为这个新建Layer的内容 mSurfaceControl.setLayerStack(display.getLayerStack()); mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT); // 设置层级为2010001,基本没有比这更高的了,这样可以保证盖住所有界面 mSurfaceControl.setAlpha(0); // 先设置Alpha为0 mSurfaceControl.show(); sur.destroy(); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); } if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM, " FREEZE " + mSurfaceControl + ": CREATE"); setRotationInTransaction(originalRotation); } finally { if (!inTransaction) { SurfaceControl.closeTransaction(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, "<<< CLOSE TRANSACTION ScreenRotationAnimation"); } }}
注意,截完屏后,系统的配置就已经由竖屏变更为横屏,这样才能使得原来竖屏的那些窗口重绘。
再看下需要等待哪些窗口重绘完成:
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.javapublic boolean updateRotationUncheckedLocked(boolean inTransaction) { ... final WindowList windows = displayContent.getWindowList(); for (int i = windows.size() - 1; i >= 0; i--) { WindowState w = windows.get(i); // Discard surface after orientation change, these can't be reused. if (w.mAppToken != null) { w.mAppToken.destroySavedSurfaces(); } if (w.mHasSurface) { // 标记有Surface的窗口 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w); w.mOrientationChanging = true; // mOrientationChanging作为重绘是否完成的标记 mWindowPlacerLocked.mOrientationChangeComplete = false; // mOrientationChangeComplete作为是否全部重绘完成的标记 } w.mLastFreezeDuration = 0; } ...}
注意这里的mWindowPlacerLocked.mOrientationChangeComplete,为true时表示已经全部准备完成,前提是所有可见窗口的mOrientationChanging都为false。大家看代码时可以以此为切入点。
最后再看下转屏动画的生成:
private boolean startAnimation(SurfaceSession session, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, boolean dismissing, int exitAnim, int enterAnim) { ... final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { customAnim = true; mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim); } else { customAnim = false; switch (delta) { case Surface.ROTATION_0: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_enter); if (USE_CUSTOM_BLACK_FRAME) { mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_frame); } break; case Surface.ROTATION_90: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_enter); if (USE_CUSTOM_BLACK_FRAME) { mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_frame); } break; case Surface.ROTATION_180: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_enter); if (USE_CUSTOM_BLACK_FRAME) { mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_frame); } break; case Surface.ROTATION_270: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_enter); if (USE_CUSTOM_BLACK_FRAME) { mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_frame); } break; } } ...}
mRotateExitAnimation即施加给截屏Layer的动画,而mRotateEnterAnimation则施加到参与转屏的其它窗口上,大家可以看下代码中相关的动画资源以确认动画的最终效果。从这里也可以看到,动画可以自行定制。
转屏动画的释放
准备过程完成后,即mWindowPlacerLocked.mOrientationChangeComplete为true,就意味着转屏动画的释放,代码如下:
frameworks\base\services\core\java\com\android\server\wm\WindowSurfacePlacer.javaprivate void performSurfacePlacementInner(boolean recoveringMemory) { ... if (mOrientationChangeComplete) { if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) { mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; mService.mLastFinishedFreezeSource = mLastWindowFreezeSource; mService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT); } // 开启转屏动画 mService.stopFreezingDisplayLocked(); } ...}
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.javavoid stopFreezingDisplayLocked() { ... if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, getTransitionAnimationScaleLocked(), displayInfo.logicalWidth, displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) { // 调用screenRotationAnimation.dismiss()来最终释放转屏动画 scheduleAnimationLocked(); } else { screenRotationAnimation.kill(); mAnimator.setScreenRotationAnimationLocked(displayId, null); updateRotation = true; } ...}
到此为止就会看到界面开始旋转,还有最后一个问题,上面说的两个动画是在哪里施加的?
说到动画的步进,第一反应就要想到下面的方法,先看截屏Layer的动画:
frameworks\base\services\core\java\com\android\server\wm\WindowAnimator.javaprivate void animateLocked(long frameTimeNs) { if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) { // 调用该方法步进截屏Layer的转屏动画,即mRotateExitAnimation setAnimating(true); } else { mBulkUpdateParams |= SET_UPDATE_ROTATION; screenRotationAnimation.kill(); displayAnimator.mScreenRotationAnimation = null; //TODO (multidisplay): Accessibility supported only for the default display. if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) { // We just finished rotation animation which means we did not // anounce the rotation and waited for it to end, announce now. mService.mAccessibilityController.onRotationChangedLocked( mService.getDefaultDisplayContentLocked(), mService.mRotation); } } }}
再看mRotateEnterAnimation,即参与转屏的其它窗口的动画:
frameworks\base\services\core\java\com\android\server\wm\WindowStateAnimator.javavoid computeShownFrameLocked() { ... final int displayId = mWin.getDisplayId(); final ScreenRotationAnimation screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId); final boolean screenAnimation = screenRotationAnimation != null && screenRotationAnimation.isAnimating(); ... if (selfTransformation || attachedTransformation != null || appTransformation != null || screenAnimation) { ... if (screenAnimation) { // 在这里 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } ... } ...}
最后,附上动画过程演示图:
- Android Framework--转屏原理
- android framework broadcast 原理
- Android framework学习笔记 -- 原生分屏原理
- Android framework工作方式及原理
- Android的framework层音量控制原理
- 4. Android Framework - View的工作原理
- Android Framework学习笔记 -- Binder原理
- Android原理揭秘系列之framework本地方法注册
- Android原理揭秘系列之framework本地方法注册
- Android原理揭秘系列之framework本地方法注册
- Android的framework层音量控制原理分析
- Android的framework层音量控制原理分析
- Android的framework层音量控制原理分析
- Android Framework的启动方法及原理详解
- Android Framework架构原理学习之AndroidApp启动
- .net framework 工作原理
- Sensor Framework原理
- Sensor Framework原理
- 微信小程序 Jgank 开发(一)
- 并行流与顺序流性能对比试验
- css3
- < 笔记 > DOM
- zookeeper 学习总结
- Android Framework--转屏原理
- 在Eclipse中开发Maven-web项目时报错:maven-archetype-webapp
- 计算两个时间戳之间相差的日时分秒
- 架构师之路-一张图认识jvm内存
- 目录处理命令mkdir
- Mark一个网址
- 如何合并CSS两个字体文件
- C++指针和引用的区别与联系
- [BZOJ]1565: [NOI2009]植物大战僵尸 Tarjan+最小割(最大权闭合子图)