Gaia 支持的可视性监视器

随着超低价设备需求的激增,我们必须更仔细地计算设备的每项资源,例如 CPU、RAM 和 Flash。在这里我想介绍一下 可视性监视器,它在 Gaia 中已经存在很长时间了。

起源

可视性监视器源于 Gaia 的 Gallery 应用程序,首次出现在 Bug 809782(如果 sdcard 上有太多图像,图库会崩溃)中。它解决了由于在 Gallery 应用程序中存储太多图像而导致的内存不足问题。一段时间后,Tag 可视性监视器(可视性监视器的“兄弟”)诞生了。它们的两个功能几乎相同,只是 Tag 可视性监视器 遵循预先分配的标签名称来过滤需要监控的元素。因此,在接下来的部分中,我们将使用 Tag 可视性监视器作为示例。当然,可视性监视器也是适用的。

供您参考,可视性监视器是由 JavaScript 大师 David Flanagan 完成的。他也是 JavaScript: The Definitive Guide 的作者,并在 Mozilla 工作。

工作原理

基本上,可视性监视器将屏幕外显示的图像从 DOM 树中删除,因此 Gecko 有机会释放由图像加载器/解码器临时使用的图像内存。

您可能会问:“这项操作可以在 Gecko 上完成。为什么要在 Gaia 上这样做?”事实上,Gecko 默认情况下启用了可视性监视器;但是,可视性监视器只删除图像缓冲区(图像解码器未压缩的图像)。然而,原始图像仍然临时存储在内存中。这些图像是由图像加载器从互联网或本地文件系统捕获的。但是,Gaia 支持的可视性监视器将完全从 DOM 树中删除图像,即使是临时存储在图像加载器中的原始图像也是如此。此功能对于 Tarako(Firefox OS 低端设备项目的代号)极其重要,它仅配备了 128MB 内存。


以上面的图形为例,我们可以将整个图像分为:

  • 显示端口
  • 预渲染区域
  • 边距
  • 所有其他区域

当显示端口上下移动时,可视性监视器应该动态加载预渲染区域。同时,预渲染区域之外的图像将不会被加载或解压缩。可视性监视器将把边距区域作为动态可调参数。

  • 边距值越高,Gecko 必须预渲染的图像部分越大,这将导致更多的内存使用,并使滚动更加流畅(FPS 将更高)。
  • 反之亦然:边距越低,Gecko 必须预渲染的图像部分越小,这将导致更少的内存使用,并使滚动不那么流畅(FPS 将更低)。

由于这种工作原理,我们可以调整参数和图像质量以满足我们的需求。

先决条件

不可能“既要又要”。就像不可能“使用可视性监视器并且不受其影响”一样。使用可视性监视器的 先决条件 列在下面

被监控的 HTML DOM 元素从上到下排列

Web 的原始布局是从上到下的,但我们可能会使用一些 CSS 选项(例如 flex-flow)将布局从下到上更改。应用它们后,可视性监视器可能会变得更加复杂,并降低 FPS(我们不喜欢结果),这种布局对于可视性监视器是不可接受的。当有人使用这种布局时,可视性监视器在应该显示图像的区域没有任何显示,而是发送错误。

被监控的 HTML DOM 元素不能被绝对定位

可视性监视器计算每个 HTML DOM 元素的高度以决定是否显示该元素。因此,当元素固定在某个位置时,计算将变得更加复杂,这是不可接受的。当有人使用这种排列时,可视性监视器在应该显示图像的区域没有任何显示,并发送错误消息。

被监控的 HTML DOM 元素不应该通过 JavaScript 动态改变其位置

与绝对定位类似,动态更改 HTML DOM 元素的位置会使计算更加复杂,两者都是不可接受的。当有人使用这种排列时,可视性监视器在该区域没有任何显示。

被监控的 HTML DOM 元素不能被调整大小或隐藏,但它们可以具有不同的尺寸

可视性监视器使用 MutationObserver 监控 HTML DOM 元素的添加和删除操作,但不监控 HTML DOM 元素的出现、消失或调整大小。当有人使用这种排列时,可视性监视器再次没有任何显示。

运行监控的容器不能使用 position: static

由于可视性监视器使用 offsetTop 计算显示端口的位置,因此它不能使用 position: static。我们建议使用 position: relative 代替。

运行监控的容器只能通过调整窗口大小来调整大小

可视性监视器使用 window.onresize 事件来决定是否重新计算预渲染区域。因此,大小的每次更改都应该发送一个 resize 事件。

Tag 可视性监视器 API

可视性监视器 API 非常简单,只有一个函数

function monitorTagVisibility(
    container,
    tag,
    scrollMargin,
    scrollDelta,
    onscreenCallback,
    offscreenCallback
)

它接受的参数定义如下

  1. container:用户滚动时的一个真实的 HTML DOM 元素。它不一定是被监控元素的直接父元素,但必须是它们的祖先之一
  2. tag:一个字符串,表示要监控的元素名称
  3. scrollMargin:一个数字,定义显示端口之外的边距大小
  4. scrollDelta:一个数字,定义“应该进行计算以生成新的预渲染区域的滚动像素数”。
  5. onscreenCallback:当 HTML DOM 元素移动到预渲染区域后调用的回调函数
  6. offscreenCallback:当 HTML DOM 元素从预渲染区域移出后调用的回调函数

注意:“移动到”和“移动出”上面提到的意思是:只要有一个像素在预渲染区域内,我们就说它移动到或保持在屏幕上;只要没有像素在预渲染区域内,我们就说它从屏幕上移动出或不存在于屏幕上。

示例:音乐应用程序(1.3T 分支)

我的任务之一是在 1.3T 音乐应用程序中添加可视性监视器。由于对音乐应用程序的结构缺乏了解,我向另一位同事寻求帮助,以找出我应该在哪个位置添加它,结果是三个位置:

  • TilesView
  • ListView
  • SearchView

在这里,我们只以 TilesView 作为示例,并演示添加它的方法。首先,我们使用 应用程序管理器 找出 TilesView 中用于滚动的真实 HTML DOM 元素


使用应用程序管理器,我们发现 TilesView 有 views-tileviews-tiles-searchviews-tiles-anchorli.tile(它们三个都在下面)。经过测试,我们可以看到滚动条显示在 views-tile 上;views-tiles-search 然后会自动滚动到不可见的位置。然后每个图块以 li.tile 的方式存在。因此,我们应该将容器设置为 views-tiles 并将标签设置为 li。以下代码用于调用可视性监视器

monitorTagVisibility(
    document.getElementById('views-tile'),
    'li',
    visibilityMargin,    // extra space top and bottom
    minimumScrollDelta,  // min scroll before we do work
    thumbnailOnscreen,   // set background image
    thumbnailOffscreen // remove background image
);

在上面的代码中,visibilityMargin 设置为 360,表示屏幕的 3/4。minimumScrollDelta 设置为 1,表示每个像素都会被重新计算一次。thumbnailOnScreenthumbnailOffscreen 可用于设置缩略图的背景图像或将其清除。

效果

我们在 Tarako 设备上进行了实际测试。我们启动了音乐应用程序,并让它加载了近 200 个带封面图像的 MP3 文件,总计约 900MB。在没有可视性监视器的情况下,音乐应用程序对图像的内存使用量如下

├──23.48 MB (41.04%) -- images

│ ├──23.48 MB (41.04%) -- content

│   │   ├──23.48 MB (41.04%) -- used

│   │   │ ├──17.27 MB (30.18%) ── uncompressed-nonheap

│   │   │ ├───6.10 MB (10.66%) ── raw

│   │   │ └───0.12 MB (00.20%) ── uncompressed-heap

│   │   └───0.00 MB (00.00%) ++ unused

│   └───0.00 MB (00.00%) ++ chrome

使用可视性监视器,我们重新获得了以下内存使用量

├───6.75 MB (16.60%) -- images

│   ├──6.75 MB (16.60%) -- content

│   │  ├──5.77 MB (14.19%) -- used

│   │  │  ├──3.77 MB (09.26%) ── uncompressed-nonheap

│   │  │  ├──1.87 MB (04.59%) ── raw

│   │  │  └──0.14 MB (00.34%) ── uncompressed-heap

│   │  └──0.98 MB (02.41%) ++ unused

│   └──0.00 MB (00.00%) ++ chrome

比较两者

├──-16.73 MB (101.12%) -- images/content

│  ├──-17.71 MB (107.05%) -- used

│  │  ├──-13.50 MB (81.60%) ── uncompressed-nonheap

│  │  ├───-4.23 MB (25.58%) ── raw

│  │  └────0.02 MB (-0.13%) ── uncompressed-heap

│  └────0.98 MB (-5.93%) ── unused/raw

为了确保可视性监视器正常工作,我们添加了更多的 MP3 文件,总计约 400 个。同时,内存使用量保持在 7MB 左右。对于 128MB 设备来说,这是一个巨大的进步。

结论

老实说,如果没有那么多图像,我们不必使用可视性监视器。由于可视性监视器始终影响 FPS,我们可以让 Gecko 处理这种情况。当谈到使用大量图像的应用程序时,我们可以通过可视性监视器控制内存资源。即使我们增加了图像数量,内存使用量仍然保持稳定。

可视性监视器的边距和增量参数将影响 FPS 和内存使用量,结论如下

  • 较高的 marginvalue 值:更多的内存使用,FPS 将更接近 Gecko 本机滚动
  • 较低的边距值:更少的内存使用,更低的 FPS
  • 较高的 delta 值:内存使用量略微增加,更高的 FPS,更容易看到未加载的图像
  • 较低的 delta 值:内存使用量略有下降,FPS 降低,看到未加载图像的可能性降低

关于 John Hu

John Hu 是 Mozilla 台北办事处的 前端工程师,常驻台湾。Hu 是视频应用程序的同行,主要关注 Firefox OS 的 Gaia。目前,他加入了新兴设备团队,该团队试图将 Firefox OS 带入未知世界。John Hu 在软件开发方面拥有约 10 年的经验,包括 Web 服务、窗口应用程序和嵌入式软件。他发布的一款全球性应用程序是 miidio 录音机,这是一款设计精良的 Android MP3 录音机。他对学习新事物感兴趣,例如树莓派、3D 打印机,欢迎联系他。

更多 John Hu 的文章…

关于 Robert Nyman [名誉编辑]

Mozilla Hacks 的技术布道者和编辑。进行有关 HTML5、JavaScript 和开放 Web 的演讲和博客。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直在瑞典和纽约市从事 Web 前端开发工作。他还定期在 http://robertnyman.com 上发博客,喜欢旅行和结识朋友。

更多 Robert Nyman [名誉编辑] 的文章…


一条评论

  1. Fawad Hassan

    从您的帖子中我理解到,可见性监视器会在 DOM 元素超出可见+边距区域时移除它们,并在滚动回到可见区域附近时添加它们。这是对的吗?

    2014 年 10 月 28 日 下午 2:19

本文的评论已关闭。