wmsandroid
❶ Android 重学系列 WMS在Activity启动中的职责 计算窗体的大小(四)
通过启动窗口为例子,大致上明白了WMS是如何添加,更新,移除窗口的工作原理。本文将会重点聊一聊窗口的大小计算逻辑。
下面的源码都是来自Android 9.0
计算窗口的大小和Android 4.4相比变化很大。花了一点心思去重新学习了。在Android 4.4中,窗体的计算在onResume中调用了ViewRootImpl调用relayoutWindow对整个Window重新测量窗口大小以及边距。
relayoutWindow这个方法是做什么的呢?当我们在Activity的生命周期到达了onResume的阶段,此时ViewRootImpl的setView,开始走渲染的View的流程,并且调用requestLayout开始测量渲染。其中有一个核心的逻辑就是调用WMS的relayoutWindow,重新测量Window。
在Android 9.0中把这个流程和DisplayContent绑定起来。让我们稍微解剖一下这个方法。
relayout大致上要做了以下的事情:
relayout的方法有点长,本次我们将关注这一部分核心的逻辑。分别是两个方法:
能看到在这里面对performSurfacePlacementLoop做最多为6次的循环,这六次循环做什么呢?
能看到这里面的核心逻辑,首先会检查WMS下mForceRemoves集合中是否还有对象。有则调用removeImmediately清空WindowState的中SurfaceControl和WindowContainer之间的绑定和Surface对象,以及销毁WindowAnimator中的Surface。
做这个得到目的很简单,因为下一个步骤将会申请一个Surface对象,而此时如果Android系统内存过大了(OOM),mForceRemoves就存在对象,就可以销毁不需要的Surface。这一点的设计和Davlik虚拟机申请对象时候的思路倒是一致的。
销毁需要一点时间,因此就需要做一个250毫秒的的等待。接着会调用RootWindowContainer的performSurfacePlacement做真正的执行。最后会通过handler通过ViewServer通知事件给DebugBridge调试类中。
每一次loop的最后,如果发现RootWindowContainer需要重新测量皮空,就会把当前这个方法,放入Handler中,等待下次的调用,也是调用6次。这样就能最大限度的保证在这段时间内Window能够测量每一次的窗体参数。
下面这个方法十分长,我们只看核心;
我在上面划分了9个部分:
这里只给总览,之后有机会再进去里面抓细节。
我们能够看到无论是在哪里,如果窗口发生了变化,都会调用updateFocusedWindowLocked方法。实际上这个方法才是真正的核心测量窗口大小逻辑。
这里注意一下isWindowChange是判断输入法焦点是否一致,而窗体焦点则是通过不同的WindowState来判断。
实际上核心测量的真正动作是DisplayContent.performLayout。我们仔细一想也就知道,在Android 9.0的时候,DisplayContent象征着逻辑屏幕,我们讨论无分屏的情况,实际上就是指我们当前窗体铺满逻辑显示屏各个边距的大小。
在正式开始聊窗体大小的测量之前,实际上,在Android系统中,为了把Window各个边界标记出来,实际上随着时代和慧握颤审美潮流的演进,诞生越来越多的边距类型,我们往往可以通过这些边距来测定窗体的大小。
在DisplayFrame中有了大致的分区,如下:
可以看到,这些窗体的边距实际上是跟着这些年潮流走的。如Android 7.0的自由窗体模式,嵌套窗体模式,刘海屏等等,这些边距的共同作用,才会诞生一个真正的Window大小。有了这些基础知识之后,我们去看看测量大小的逻辑。
我前败们这里把这个方法拆成如下几个部分:
能看到,此时会设置当前显示屏幕的大小,以及获取过扫描区域,还会判断当前手机屏幕是否支持刘海屏。这一切实际上都是由硬件回馈到DisplayService,我们再从中获取的信息。
实际上如果有读者注意到我写的WMS第一篇就会看到实际上WMS初始化的时候,我们能够看到WMS会初始化一个WindowManagerPolicy的策略,而这个策略就是PhoneWindowManager。实际上这也支持了系统开发自定义策略,从而办到自己想要的窗体计算结果。
首先初始化几个参数,父窗体,屏幕,过扫描,可见区域,输入法区域为当前逻辑显示屏的大小,等到后面做裁剪。
能看到所有的事情实际上是关注的是系统UI上的判断,检测NavBar,StatusBar大小。最后再判断当前刘海屏的不允许交叉的区域顶部和显示屏顶部哪个大。如果mDisplayCutoutSafe的top大于mUnrestricted的top,说明mDisplayCutoutSafe在mUnrestricted下面,也就是我上面那个包含一段黑色的区域。此时会拿稳定的应用区域和刘海区域顶部的最大值,作为刘海屏幕的区域。这样就能保证刘海屏的顶部就是状态栏。
提一句如果NavigationBar隐藏,则会创建一个虚假的区域把输入事件都捕捉起来。
里面有四个关键函数:
可以看到所有的所有的间距将会设置为mUnrestricted的初始宽高,也就是不包含OverScan区域。如果是遇到刘海屏,则会根据设置的SafeInset区域来设置mDisplayCutoutSafe的安全区域。也就是我上面那种情况。比如设置了LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT这种情况,显示区域将不会超过刘海屏的底部。
我们关注到mTmpNavigationFrame这个对象的赋值,在正常的情况下的范围是如下:
此时mStable和mStableFullscreen区域的底部都是对应着top,也就是对应着Navigation顶部。System系统元素的底部也是Navigation顶部。
最后经过computeFrameLw重新计算这个区域的值。这个方法稍后会聊到,但是在正常手机开发中,其实是没有变化的。也就说,实际上对于mNavigationBar来说:
同理对于statusBar来说:
注意,此时如果statusBar可见,则做如下计算:
这种情况挺常见的,我们从一个隐藏状态栏的页面跳转到有状态栏的页面,国有有个PopupWindow,你能看到这个popwindow会明显向下移动。
在这个方法中mScreenDecorWindows这个集合实际上是在adjustWindowParamsLw以及prepareAddWindowLw这两个方法中加入。加入的条件是,每当有新的Window加入(WMS的addView)或者Window需要重新调整(WMS的relayoutWindow),当前新增得到Window或者需要重新relayout的Window有StatusBar有权限,且显示则会添加到mScreenDecorWindows集合。
mScreenDecorWindows从上面的描述,能得知实际上这个步骤还没有根据层级作区分。但是没关系,此时仅仅只是初步的测量。
明白了mScreenDecorWindows之后,我们阅读上面这个方法就很简单了。
layoutScreenDecorWindows做的事情就如名字一样,要测量Window上装饰部分,如StatusBar,如输入法。此时经过循环,自尾部往头部调用所有的WindowState的computeFrameLw计算每一个WindowState的对应Window的窗体大小。
当计算出每一个窗体大小之后,将会把事件分成两个情况,当计算出来的当前的Window的left和top都小于等于0,也就是说,当前的Window的顶部边缘并且左边缘超过了当前的屏幕。
说明了有什么东西在右下侧把整个Window定上去了。因此dockFrame的计算就很简单了:
如果计算出来的bottom大于等于屏幕高度且right大于等于屏幕宽度。说明有什么东西在左上方把整个Window顶下去了。
最后再设置这个把displayFrames的可见等区域都设置为dockFrame。联合上下文,实际上这里就是把整个区域的顶部移动到了statusBar之下。
❷ 智能座舱——Android的WMS/AMS/PMS是什么
智能座舱中的Android系统内部,有三个关键组件负责不同的功能:
首先,WindowManagerService (WMS) 是Android系统的核心组件,它负责窗口的创建和显示顺序管理。作为WindowManager的实际执行者,它介于View和底层的SurfaceFlinger之间,确保了界面的流畅和协调。
其次,Activity Manager Service (AMS) 是活动管理器,其核心职责是管理应用程序的生命周期和交互。它掌控着Activity的启动、暂停、停止等操作,同时调度系统服务,确保应用程序权限的正确管理,确保系统的稳定运行。
最后,Package Manager Service (PMS) 是Android系统中的关键组件,它扮演着超级管理员的角色,主要负责应用程序的安装、卸载、更新等管理任务。无论是新应用的引入还是现有应用的维护,PMS都在后台默默进行,确保用户界面的完整性和系统的安全性。
❸ Android WMS动画系统初探(一)
Android WMS动画系统初探(一)
Android WMS动画系统初探(二)
Android WMS动画系统初探(三)
Android中动画的工作过程:在某一个时间点,调用getTransformation(),根据mStartTime和mDuration,计算出当前的进度,在根据mInterpolator计算出转换的进度,然后计算出属性的当前值,保存在matrix中。
再调用Matrix.getValues将属性值取出,运用在动画目标上。
[图片上传失败...(image-8c5ae5-1636101404926)]
Animation
在给定了初始状态、结束状态、启动时间与持续时间后,可以为使用者计算其动画目标在任意时刻的变换(Transformation)
Transformation
描述了一个变换,包含两个分量:透明度和一个二维变换矩阵
无论APP或者系统,都是可以直接向Choreographer注册FrameCallback来实现动画驱动的。
Choreographer 类似 Handler,处理回调的时机为屏幕的垂直同步(VSync)事件到来之时,其处理回调的过程被当作渲染下一帧的工作的一部分
postCallback(int callbackType, Runnable action, Object token)
postCallbackDelayed(int callbackType, Runnable action, Object token, delayMillis)
postFrameCallback(FrameCallback callback)
对于View动画,动画的目标就是View,而对于窗口来说,动画的目标其实都是Surface,对 不同层级的SurfaceControl 进行操纵,会产生不同的动画效果。
[图片上传失败...(image-b25ef9-1636101404926)]
如上图 WMS的结构层次可以简单概括为:
RootWindowContainer -> DisplayContent -> DisplayArea -> Task -> WindowToken -> WindowState
[图片上传失败...(image-2bdd8a-1636101404926)]
根据操纵层级的不同我把动画分类为:窗口动画、过渡动画、Task动画、全屏动画等等
[图片上传失败...(image-d5ed20-1636101404926)]
在窗口布局(relayout)阶段调用到
WindowStateAnimator#commitFinishDrawingLocked ->
WindowState#performShowLocked ->
WindowStateAnimator#applyEnterAnimationLocked
开启窗口动画流程
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java中定义了一个mSurfaceAnimator成员变量
SurfaceAnimator的startAnimation方法中创建Leash,可以通过SurfaceAnimator的类注释了解 Leash
为什么引入Leash可以参考此文: Android P——LockFreeAnimation
mAnimation.startAnimation这一步最终会通过LocalAnimationAdapter找到WMS里的SurfaceAnimationRunner进行执行。
这是 WindowContainer与SurfaceAnimtor、SurfaceAnimationRunner的持有关系 :
[图片上传失败...(image-f741c0-1636101404926)]
往编舞者上抛的runnable是执行startAnimations方法
SurfaceAnimationRunner#startAnimations ->
SurfaceAnimationRunner#startPendingAnimationsLocked
会从mPendingAnimations遍历RunningAnimation并执行startAnimationLocked
这一步构建了一个SfValueAnimator来真正的驱动动画,每一帧的处理是通过WindowAnimationSpec构建真正要执行的动画事务,然后使用mChoreographer.postCallback在下一个vsync信号到来时提交动画事务。
ValueAnimator驱动动画的原理本文就不做深入了。
下一篇文章我将进一步分析Activiy的过渡动画和屏幕旋转动画的相关流程。
Android WMS动画系统初探(二)
Android WMS动画系统初探(三)