理解Canvas和Surface概念

125

我很难理解如何绘制到SurfaceView以及整个在Android中使用的Surface/Canvas/Bitmap系统的过程。

我已经阅读了android-developers网站上能找到的所有文章和API文档页面,几篇Android图形教程,LunarLander源代码和这个问题

请告诉我,这些陈述中哪些是真实的,哪些不是,为什么。

  1. Canvas有自己附加的BitmapSurface有自己附加的Canvas
  2. 窗口中的所有View共享同一个Surface,因此共享同一个Canvas
  3. SurfaceViewView的子类,与其他View子类和View本身不同,它有自己的Surface可以进行绘制。

还有一个额外的问题:

  • 为什么需要一个Surface类,如果已经有Canvas用于高层次操作位图?请举一个Canvas无法胜任但Surface可以胜任工作的情况。

4
图形架构文档:https://source.android.com/devices/graphics/architecture.html - fadden
3个回答

244

以下是一些定义:

  • Surface是一个包含像素的对象,用于组合到屏幕上。每个显示在屏幕上的窗口(如对话框、全屏活动、状态栏)都有自己的Surface,用于绘制。Surface Flinger会按照正确的Z顺序将它们呈现到最终的显示器上。通常,一个Surface有多个缓冲区(通常是两个)用于双缓冲渲染:应用程序可以在Surface flinger使用上一帧缓冲区进行屏幕合成时,绘制其下一帧UI状态,而不需要等待应用程序完成绘制。

  • Window基本上就像你在桌面上看到的窗口一样。它有一个单独的Surface,其中窗口的内容被渲染。应用程序与Window Manager交互以创建窗口;Window Manager为每个窗口创建一个Surface并将其提供给应用程序进行绘制。应用程序可以在Surface中绘制任何东西。对于Window Manager来说,Surface只是一个不透明的矩形。

  • View是窗口内部的交互式UI元素。一个窗口附加了一个单独的视图层次结构,提供窗口的所有行为。每当窗口需要重新绘制(例如因为一个View使自己无效了),就会在窗口的Surface上进行。Surface被锁定,这会返回一个Canvas,用于在其中绘制。从层次结构中向下进行绘制遍历,将Canvas传递给每个View以绘制其UI部分。完成后,Surface被解锁并发布,以便刚刚绘制的缓冲区被交换到前景,然后由Surface Flinger合成到屏幕上。

  • SurfaceView 是 View 的一种特殊实现,它会创建自己专用的 Surface,以便应用程序可以直接绘制到其中(这在正常视图层次结构之外,否则必须共享窗口的单个 Surface)。这样做的方式比您想象的要简单-- SurfaceView 所做的只是请求窗口管理器创建一个新窗口,并告诉它将该窗口的 Z-order 置于 SurfaceView 窗口的前面或后面,并将其定位到与 SurfaceView 在包含窗口中的位置相匹配。如果正在将表面放置在主窗口后面(按 Z 顺序),SurfaceView 还会使用透明度填充其在主窗口中的部分,以便可以看到表面。

  • Bitmap 只是对一些像素数据的接口。像素可以由 Bitmap 自己分配,当你直接创建一个 Bitmap 时,或者它可能指向它不拥有的像素,例如内部发生的挂钩 Canvas 到 Surface 上进行绘制。 (一个 Bitmap 被创建并指向 Surface 的当前绘图缓冲区。)

  • 还请记住,由于这意味着,SurfaceView 是一个相当重量级的对象。 如果您在特定 UI 中有多个 SurfaceView,请停下来考虑是否真的需要这样做。 如果您有两个以上,几乎可以肯定您有太多。


    1
    非常感谢!您的回答让事情更加清晰了。关于将Canvas连接到Surface的部分还不太清楚。无法想象在哪里需要这样的操作。下面可以举一个例子来说明这个操作:使用lockCanvas()方法从SurfaceHolder获取的Canvas上绘制位图? - Fyodor
    1
    这就是绘图的过程。Canvas 是 2D 绘图 API。如果你要在一个表面上绘制 o,你需要创建一个指向其缓冲区的 Canvas,以便使用 Canvas 2D 绘图 API 在其上进行绘制。 - hackbod
    7
    除了#hackbod的回答之外,SurfaceView还可以从次要线程中进行渲染,而对于View对象来说这是不可能的。 - Mohan
    1
    一个表面还具有lockHardwareCanvas功能,它绑定到由GPU提供的而不是软件画布。这个GPU绑定的画布具有有限数量的2D绘图功能,但比基于软件的画布具有更高的性能。 - Johann
    1
    如果在特定的用户界面中有多个SurfaceView,请停下来思考一下是否真的需要这么多。如果您有两个以上,几乎可以确定您有太多了。 - 这并不完全正确。几乎所有视频会议应用程序,包括Zoom,都使用SurfaceView来显示单个视频。在同一屏幕上同时显示许多这些视图以查看视频会议中的多个参与者是完全正常的。 - Johann

    57

    窗口、表面、画布和位图的概念概述

    以下是关于窗口、表面、画布和位图之间交互的非常基本和简单的概念概述。
    有时,视觉表示在理解扭曲的概念方面非常有帮助。
    我希望这个图形能够帮助到某些人。


    6
    视觉上,图像比文字更好理解 :D - Maveňツ

    21

    位图(Bitmap)只是像素集合的封装。可以将其视为一些带有其他方便函数的像素数组。

    画布(Canvas)是包含所有绘图方法的类。如果您熟悉AWT/Swing中的Graphics类,则它类似于该类。画圆、画方等绘制逻辑都包含在Canvas内。画布可以在位图或OpenGL容器上绘制,但未来也可以扩展为绘制到其他类型的光栅上。

    SurfaceView是一个包含Surface的视图(View)。表面类似于位图(它具有像素存储)。我不知道它如何实现,但我想它是某种具有额外方法的位图封装,用于与屏幕显示直接相关的事项(这是Surface的原因,位图太通用了)。您可以从Surface中获取Canvas,这实际上是获取与底层位图关联的Canvas。

    你的问题。

    1. Canvas自带位图,而Surface自带画布。

    是的,Canvas在位图(或OpenGL面板)上进行操作。Surface会给您提供使用其位图式像素存储的Canvas。

    2.窗口的所有视图共享同一个Surface,因此共享同一个Canvas。

    不是的。您可以拥有任意数量的SurfaceView。

    3. SurfaceView是View的子类,与其他View的子类和View本身不同,它具有自己的Surface进行绘制。

    是的。就像ListView是View的子类,具有自己的列表数据结构一样。每个View的子类都做了不同的事情。


    1
    那么,BitmapSurface只是不同类型的像素存储,而Canvas可以包装它们中的任何一个? - Fyodor
    2
    基本上是的。除了Canvas不能写入表面,它操作的是Surface正在使用的像素存储(没有查看Android源代码,我无法确定它是什么)。它可能是某种位图扩展,因为Canvas仅提供Bitmap和GL的构造函数。 - sksamuel
    非常感谢您的帮助!关于第二个答案,我的问题是指标准视图,而不是SurfaceView。假设我有一个RelativeLayout,其中包含大量字段和按钮。在这种情况下,Surface是否附加到整个窗口并由视图层次结构中的所有视图共享? - Fyodor
    1
    请记住,Surface仅是像素的集合。因此,每个Surface视图都有自己的表面,并且每个表面都可以呈现在屏幕的不同部分。它们不一定填满整个屏幕(尽管这是常见用法,在全屏游戏中呈现图形)。 - sksamuel
    但是正如我之前提到的RelativeLayout,它并不包含SurfaceView,只有按钮、字段和背景。那么你是在告诉我每个视图都有自己的Surface吗? - Fyodor
    1
    我真的不认为位图和Surface是等价的。Surface是一个对象,在窗口合成器surface flinger中知道它的存在。也就是说,它是直接出现在屏幕上的东西,在屏幕上有Z顺序等等。 - hackbod

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接