图1就是 Android 屏幕显示的抽象示意图,这里我来解释一下:
Android 的两种常用绘图机制:
android 的绘制机制中存在着一系列生产者和消费者,这一节我将介绍一下这个机制中相关的概念。
这一章我们来从源码上分析 View 是如何绘制到屏幕上面的,前面的 measure、layout、draw 等等 framework 层的东西我不会着重分析,我主要分析 cpp 层的东西。
其实源码的主要流程都在图3中,我下面讲的东西算是对图3的补充和说明。另外强烈建议结合 Android 源码阅读本章节。
1.首先我们的入口是 ViewRootImpl.scheduleTraversals。看过源码的同学应该知道,类似 invalidate、requestLayout 等等需要改变 View 的显示的操作,最终都会层层向上调用最终调用到这个方法上。
1.在这个方法里主要调用到的方法就是 Choreographer.postCallback 这个方法传入了 mTraversalRunnable,表示在某个时间点会调用这个 Runnable 中的 run()。我们在第一章中讲过 VSync 的相关知识,而 Choreographer 就是 VSync 在代码层面的实现。
1.postCallback 根据 scheduleFrameLocked——>scheduleVsyncLocked——>scheduleVsyncLocked 的调用链,最终会调用到 DisplayEventReceiver.nativeScheduleVsync 向native 层申请下一个 VSync 信号。
2.16ms 后 VSync 信号会从 native 层向上触发,这里是想 ui 的 loop 中添加了一个 msg。这样的话调用链就是:DisplayEventReceiver.dispatchVsync——>DisplayEventReceiver.onVsync——>Choreographer.doFrame。
1.到了 doFrame 这里就会调用前面 postCallback 中传入的 mTraversalRunnable 的 run()。我们知道 run() 中会调用 ViewRootImpl.doTraversal 方法。
1.这样就会调用到 ViewRootImpl.performTraversals 中去。我想大家应该对这个方法很熟悉,这个方法就是调用 measure、layout、draw 的方法。已经分析烂了的东西这里我就不说了。
2.我们直接看 ViewRootImpl.draw 方法,这里会有两种绘制方式。就像我们在第一章中说的那样。如果没有开启硬件加速那么就使用 Skia 库以 CPU 来绘制图像,这里的入口方法是 drawSoftware。如果开启了硬件加速那么就是用 GL ES 来以 GPU 绘制图像,这里入口方法是 ThreadedRenderer.draw。
1.drawSoftware:这个方法比较简单就是创建一个 Surface,然后 lockCanvas 得到一个 Canvas,然后在各个层级的 View 中调用 Canvas 最终调用 Skia 库来在 Surface 上提供的图像内存中绘制图像,画完之后调用 unlockCanvasAndPost 来提交图像内存。这里更详细的流程我会在第三章中分析 Surface 的时候分析。
2.ThreadedRenderer.draw:这个方法是硬件加速下的图像绘制入口,里面最终都是调用到 GL 的 api。在深入之前我们先了解一下硬件加速绘制的几个阶段:
1.硬件加速绘制的五个阶段:
1.APP在UI线程使用 Canvas 递归构建 GL 渲染需要的命令及数据
2.CPU 将准备好的数据共享给 GPU
3.CPU 通知GPU渲染,这里一般不会阻塞等待GPU渲染结束,因为效率很低。CPU 通知结束后就返回继续执行其他任务。当然使用 glFinish 可以阻塞执行。
4.swapBuffers,并通知 SF 图层合成
5.SF 开始合成图层,如果之前提交的GPU渲染任务没结束,则等待GPU渲染完成,再合成(Fence机制),合成依然是依赖GPU
2.硬件加速绘制代码分析:
1.我们先看 draw() 里面调用的 updateRootDisplayList:
1.这个方法的第一个调用链是这样的 updateViewTreeDisplayList——>View.updateDisplayListIfDirty——>View.draw。如图4,这里聪明的同学一看就知道是一个递归操作。View 的 draw 会递归到子 View 中。然后各个 View 会调用 Canvas 的 api 将绘制操作储存在 Canvas 中。那么这里的 Canvas 是怎么来的呢?其实在每个 View 创建的时候内部会创建一个 RenderNode 。这个对象可以创建一个 DisplayListCanvas 来作为 Canvas 给各个 View 在绘制的时候使用。而每个子 View 调用 draw(Canvas, ViewGroup, long) 的时候都会得到 parentView 传递下来的 DisplayListCanvas,然后在本 View.draw(Canvas) 调用结束之后,将DisplayListCanvas 的操作储存到本 View 的 RenderNode 中。最后调用 parentView 的 DisplayListCanvas.drawRenderNode 将本 View 的 RenderNode 存入 parentView 的 RenderNode 中。如此递归,最终将所有绘制操作存入 RootView 的 RenderNode 中。至此 RootView 中就包含了一个 DrawOp 树。
2.我们回到 updateRootDisplayList 这里后续就是将 RootView 的 DrawOp 树 交给 ViewRootImpl 的 RenderNode 方便后面进行操作。
2.再回到 draw() 中,这里下一个调用的重要的方法是 nSyncAndDrawFrame:
1.这个方法最终会调用到 c++ 层的 RenderProxy::syncAndDrawFrame 方法。在了解这里的调用链之前。我先介绍一下 Android 5.0 之后出现的渲染线程的概念,再来讲解调用链。
1.首先渲染线程的实现类是 RenderThread.cpp 它是和 ui 线程类似,是一个事件驱动的 Loop。它的也有队列,队列中储存着 DrawFrameTask.cpp 对象。RenderProxy.cpp 是 RenderThread.cpp 给 java 层的代理。ThreadRender 所有关于渲染线程的请求都会交给 RenderProxy.cpp 然后由它向 RenderThread.cpp 提交 task。
2.了解了渲染线程的概念,我们再来讲讲调用链:syncAndDrawFrame——> DrawFrameTask::drawFrame——>DrawFrameTask::postAndWait。这里做的事情很简单,向 RenderTheas.cpp 的队列中插入一个 DrawFrameTask.cpp,然后阻塞当前的 ui 线程。
3.ui 线程被阻塞之后,渲染线程会调用到上一次插入到队列里的 DrawFrameTask.run 方法。
1.run() 这里会先调用 syncFrameState,这个方法主要是用于同步 java 层的各种数据。
1.第一步是先用 mLayers::apply 来同步数据,这个 mLayers 在 java 层的体现是 TV。这里的分析我们会在第三章中着重分析,这里先略过。
2.第二步是调用 CanvasContext::prepareTree 来将前面在 java 层构建的 DrawOp 树同步到 c++ 层,以便后续运行 OpengGL 的命令。这里关键的调用链是:CanvasContext::prepareTree——>RenderNode::prepareTree——>RenderNode::prepareTreeImpl。由前面我们可以知道 RenderNode.java 已经构建了一个 DrawOp 树。但是之前只是调用 RenderNode::setStagingDisplayList 暂存在 RenderNode::mStagingDisplayListData 中的。因为 java 层在运行过程中还会出现多次meausre、layout的,还有数据还可能发生改变。所以当走到这里的时候数据已经确定了,所以可以开始同步数据。prepareTreeImpl 同步数据主要有三个步骤:
1.调用 pushStagingDisplayListChanges 同步当前 RenderNode.cpp 的属性,也就是把 mStagingDisplayListData 赋值给 mDisplayListData
2.调用 prepareSubTree 递归处理子 RenderNode.cpp。
3.这里会有一个同步成功和同步失败的问题,一般来说这里的数据都会同步成功的。但是在 RenderNode::prepareSubTree 中会有一个步骤是把 RenderNode 用到的 Bitmap 封装成纹理,一旦这里 Bitmap 太大或者数量太多那么同步就会失败。注意这里同步失败只是会与 Bitmap 有关,其他的 DrawOp 数据无论如何都会同步成功的。
3.如果这里同步成功了的话,那么 ui thread 就会被唤醒,反之则暂时不唤醒。
2.run() 中将数据同步完成之后,就会调用 CanvasContext.draw,这个方法主要有三个操作:
1.mEglManager::beginFrame,其实是标记当前上下文,并且申请绘制内存,因为一个进程中可能存在多个window,也就是多个 EglSurface,那么我们首先需要标记处理哪个,然后向 SF 申请内存,这里 EglSurface 是生产者,SF 是消费者。
2.根据前面的 RenderNode 的 DrawOp 树,递归调用 OpenGLRender 中的 GL API,进行绘制。
3.通过 swapBuffers 将绘制好的数据提交给 SF 去合成,值得注意的是此时可能 GPU 并没有完成当前的渲染任务,但是为了提高效率,这里可以不用阻塞渲染线程。
3.当所有的绘制操作都通过 GL 提交给了 GPU 的时候,如果前面数据同步失败了,那么这个时候需要唤醒 ui thread。
上一章我们讲了 Android 的整个绘制机制,但是其中涉及到 Surface 的部分都是简单带过。所以这一章我就来解析一下 Surface 家族中各个成员的源码。
**这里我们以 View 的创建流程为例,讲述一下 Surface 在这个过程中的创建流程,Surface 的创建流程如图5所示。 **
我们在第二章里面说到 View 的绘制根据是否硬件加速分为,软件绘制与硬件绘制两种。当时我们分析了硬件绘制,软件绘制略过了。其实软件绘制与硬件绘制的区别就在于是使用 CPU 进行绘制计算还是使用 GPU 进行绘制计算。这一小节的 Surface 绘制其实就是软件绘制,也就是 ViewRootImpl.drawSoftware 中的内容。
我们都知道 Surface 可以通过 lockCanvas 和 unlockCanvasAndPost 这两个 api 来再通过 Canvas 来绘制图像,这一节我就通过这两个 api 来讲讲 Surface 的绘制流程,整个流程如图6所示。
其实只要了解了 Surface 的创建与使用,那么 SV 就很简单了,SV.updateSurface 中会创建或者更新 Surface。在 SV 上绘制也是调用 Surface 的两个 api。这里我就简单将 View 与 SV 比较一下。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2020字节跳动Android面试历年真题解析》
如果需要PDF版本可以自行领取!
44427)]
《2017-2020字节跳动Android面试历年真题解析》
[外链图片转存中…(img-MqIaFLwj-1711079044427)]
如果需要PDF版本可以自行领取!
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 69lv.com 版权所有 湘ICP备2023021910号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务