Window的更新
在 Android 中,窗口的更新是一个非常常见的事情。比如,在使用 App 过程中,需要弹出键盘窗口或者切换横竖屏时,就会发生窗口的更新。
首先,当需要更新窗口时,会调用 WindowManager
的 updateViewLayout()
方法来设置参数,并将参数设置到对应的 View
上。WindowManager 的实现类为 WindowManagerImpl
,但它实际上并没有做太多的工作,而是直接委托给了 WindowManagerGlobal(WMGlobal)
。
接下来,在 updateViewLayout()
方法中,使用锁
来同步更新,然后通过传入的 view 参数找到其在 DecorView
中的索引值,再根据索引值找到对应的 ViewRootImpl
对象。
由于所有的窗口都是由 ViewRootImpl 来管理的,因此我们需要调用 ViewRootImpl 的 setLayoutParams()
方法,来将新的布局参数设置到对应的 ViewRootImpl
上。在这个过程中,旧的布局参数会被移除,新的布局参数会被添加进去。
UI刷新
帧率
在移动设备上,为了确保操作界面的流畅性和用户体验,帧率(FPS)是非常重要的一个因素。帧率表示在一秒钟内刷新的图像帧数,表示界面绘制的速度,而较高的帧率则意味着更加流畅的操作体验。
在 Android 系统中,默认帧率为 60 FPS
,在每 16 毫秒
的时间内绘制一次界面。为了保证界面的流畅性,系统会通过 VSYNC
信号来同步帧率和屏幕刷新,从而保证在一个 VSYNC 周期内最多只绘制一帧,避免了过度绘制和渲染导致 CPU 和 GPU 的过度占用,优化了系统性能和电池寿命。因此,我们可以使用一些调试工具来监测帧率和绘制时间,以便了解系统的性能并进行优化。
请求刷新
在 Android 中,UI 的刷新
并不是由应用程序自行决定的。相反,它需要向同步信号服务
申请同步信号以进行刷新操作。应用程序在进行 UI 绘制之前必须先向系统申请同步信号,在得到系统同意后,才能开始实际的视图重绘。
在请求刷新时,应用程序会向系统注册一个回调函数
,以便在同步信号到达时可以接收通知。当系统接收到请求时,它会分配一个时间片
来准备重绘操作,并在准备完成后向应用程序发送同步信号
。此时,应用程序的回调函数就会被调用,以便进行实际的重绘操作。
通过这种方式,Android 系统可以保证应用程序的刷新操作在正确的时机进行,并且不会占用过多的系统资源。同时,它还能够优化系统性能和电池寿命,提高用户体验。
UI的View刷新流程
View的内容变化会调用invalidate()
方法,该方法会设置 ViewRootImpl
中的 mDirty
参数,表示需要重绘的区域。
紧接着在调用 invalidate() 方法后,ViewRootImpl 会执行 scheduleTraversals()
方法准备进行绘制操作。在此期间,ViewRootImpl 同时请求VSYNC
信号。此请求的目的是为了在下一次刷新屏幕的同时绘制 View 并保证画面的流畅度。
调用 scheduleTraversals() 方法后,会将一个 mTraversalRunnable
对象加入 Choreographer.CALLBACK_TRAVERSAL
的队列中等待 VSYNC 信号到来。
在 VSYNC 信号到来后,Choreographer
会执行 mTraversalRunnable
中的 run()
方法,接着执行 doTraversal()
方法,进而调用 performTraversals()
方法。
在 performTraversals() 方法中,首先对输入的触摸事件进行处理,接着开始调用 View 的 measure()
、layout()
和 draw()
方法,依次完成测量、布局和绘制的过程。
在绘制出来之后,ViewRootImpl 会通过Surface
或者SurfaceView
将界面显示到屏幕上。
UI绘制流程
UI局部刷新
UI局部刷新(Partial UI Refresh)指的是只对某些View进行重绘刷新,而不需要对整个屏幕进行重新绘制。实际上,在 Android 中实现 UI 局部刷新是非常重要且常见的需求,在某些情况下,仅针对一个或几个 View 进行重绘能够提升应用程序的性能和响应速度。
在 Android 中,实现 UI 局部刷新主要有两种方式:
invalidate(Rect dirty)
方法:该方法可以指定需要刷新的矩形区域,从而只会对该区域内的 View 进行重绘刷新。这种方式适用于实现需求简单的 UI 局部刷新,但是需要手动计算出需要刷新的矩形区域。
ViewOverlay
:ViewOverlay 是一种可以叠加在 View 上方显示的视图,它的作用是为某个具体的 View 添加覆盖层。通过使用 ViewOverlay,我们可以在不影响原有 View 布局的情况下,动态添加、删除遮盖层,并且只需对遮盖层进行重绘刷新。这种方式可以实现比较复杂的 UI 局部刷新需求。
需要注意的是,对于使用了硬件加速的 View,使用 invalidate() 方法进行局部刷新时,可能会因为硬件加速的缓存机制导致 invalidate() 方法不会生效。此时,可以使用 setLayerType(View.LAYER_TYPE_SOFTWARE, null)
方法关闭硬件加速,或者使用 View.setClipChildren(false)
方法将 View 的父容器取消裁剪,从而避免这个问题。