图片加载性能问题的根源

应用开发中,图片资源数量或大小超过特定阈值时,由于图片格式需要通过CPU解压缩为纹理格式才能直接被GPU读取,增加CPU处理时间,导致图片加载延迟。CPU解压缩生成的图片资源会占用大量内存空间,增加内存压力,可能导致卡顿和掉帧。

图片优化分两大类:预置图片(打包在应用安装包内的本地图片)和非预置图片(来自网络或本地文件系统的图片)。预置图片主要通过纹理压缩技术优化;非预置图片包括使用图像编辑工具压缩、降低GIF图片分辨率、使用CDN优化网络图片、优先使用.webp图片、利用autoResize对图片资源进行降采样。

预置图片:纹理压缩的威力

预置图片存放在resource/base/media目录下,纹理压缩用于减小纹理图片文件大小,在构建过程中对预置图片进行转码和压缩,减少CPU处理时间,降低内存占用,提升应用性能。

纹理压缩的实现原理

image.png
预置图片不使用纹理压缩时,需要先经CPU解码生成PixelMap,再上传给GPU生成纹理,此过程耗时较长。纹理压缩在编译构建阶段提前完成CPU解码和纹理生成,减少CPU处理图片的时间。

纹理压缩需在编译文件中配置相关属性,构建时根据配置找到预置图片,转换生成纹理码流,并进行超压缩编码生成超压缩码流。编译完成后进入运行态,进行超压缩解码生成纹理码流,GPU读取纹理码流后进行渲染显示。

纹理压缩在编译构建中对预置图片进行处理,首先在编辑器的编译文件中配置纹理压缩参数,hvigor读取待压缩的文件资源,构造restool命令解析并生成资源文件列表,遍历文件列表将待转换文件转码为纹理格式,已转换的资源文件不再打包到构建产物中,最后将纹理文件和未转换的文件一起构建生成资源产物。
image.png

工程配置方法

使用纹理压缩前,需进行基础配置,选择要超压缩的预置图片。可在工程级或模块级的build-profile.json5配置文件中,于compression对象内添加media和filters属性。

media中,enable属性用于控制是否启用纹理压缩,默认值为false,需要启用时将enable属性值设为true。

filters属性中可配置method、files和exclude三个属性对象。method包含type和blocks两个属性,type可以设置为转换类型"sut"或"astc",blocks用于设置转换类型的扩展参数,当前仅支持"4x4"。files中的path、size和resolution分别指定按路径、大小和分辨率匹配的过滤条件。exclude中列出的属性与files中相同,从files中移除不需要压缩的文件。

"buildOption": {
  "resOptions": {
    "compression": {
      "media": {
        "enable": true
      },
      "filters": [
        {
          "method": {
            "type": "sut",
            "blocks": "4x4"
          },
          "files": {
            "path": ["./**/*"],
            "size": [[0, '1000k']],
            "resolution": [
              [
                { "width": 0, "height": 0 },
                { "width": 3000, "height": 3000 }
              ]
            ]
          },
          "exclude": {
            "path": ["./**/*.webp"],
            "size": [[0, '1k']],
            "resolution": [
              [
                { "width": 0, "height": 0 },
                { "width": 1024, "height": 1024 }
              ]
            ]
          }
        }
      ]
    }
  }
}

配置项关键点

文件过滤配置参数filters:当工程级和模块级同时配置时,优先按模块级的过滤条件匹配。若模块级匹配成功,忽略工程级的过滤条件;若模块级未匹配成功,继续按工程级的条件匹配。

转换类型type:

  • astc(Adaptive Scalable Texture Compression):自适应可变纹理压缩,一种对GPU友好的纹理格式,可在设备侧更快地显示,有更少的内存占用
  • sut(Super compression for Texture):纹理超压缩,一种对GPU友好的纹理格式,可在设备侧更快地显示,有更少的内存占用,相比astc具备更大压缩率和更少ROM占用

按大小匹配是一维数组,按大小匹配[0-1k,1k-2k]与按大小匹配[0-2k]的取值范围相同。

按分辨率匹配时,匹配分辨率的宽高值是二维数组。分辨率小于2048×2048的所有图片,和分辨率小于1024×1024的图片加分辨率大于1024×1024且小于2048×2048的图片,虽然两种写法看似相同,但取值范围并不一致。
image.png

性能收益实测

在Tab栏切换示例中,预置图片分别以原图(.png)、纹理超压缩(.sut)和自适应可变纹理压缩(.astc)三种方式测试,图片读取耗时对比:

文件 耗时 收益
原图(.png) 62.103ms -
纹理超压缩(.sut) 15.309ms 4.13倍
自适应可变纹理压缩(.astc) 38.239ms 1.63倍

image.png
使用原图(.png)格式的图片加载时间大约是纹理格式加载时间的4倍,纹理超压缩和自适应可变纹理压缩的加载时间大约是纹理格式的2倍。开启纹理超压缩或自适应可变纹理压缩可以显著提升应用中预置图片的加载速度。

内存占用对比:

是否开启纹理压缩 内存占用大小
开启(.sut) 165015KB
开启(.astc) 167723KB
关闭 598965KB

开启纹理压缩后,内存占用从598965KB下降到165015KB至167723KB,图片加载占用的内存显著减少。

开销分析

使用纹理压缩时,编译过程中会预置图片转换,增加编译时间。预置图片转换为纹理格式后,根据图片格式的不同,转换后的大小也会有所不同,可能导致包体膨胀或收缩。

编译时长对比:

测试用例 纹理压缩关 纹理压缩开 增加耗时说明
全量编译 19s 74ms 1min 16s 遍历资源文件+纹理压缩+搬移资源文件
修改按分辨率过滤参数 - 16s 487ms 遍历资源文件+搬移资源文件
增加1~100张图片 10s 177ms~10s 283ms 16s 491ms~16s 673ms 遍历资源文件

开启纹理压缩后,全量编译耗时较长。但按分辨率过滤预置图片后再次进行纹理压缩,能够显著减少编译时长。

包体积膨胀率对比:

图片格式 纹理压缩相比原图膨胀率
.jpg 3.05
.png 0.92
.webp 2.50

.png格式的图片纹理压缩后,包体积没有增加,而.jpg格式和.webp格式的图片包体积显著增加。综合编译时长和打包体积考虑,为了使用纹理超压缩获得更好的性能,在对包体积敏感的场景下,可以采用将所有.png格式图片进行纹理压缩;对.jpg和.webp格式的图片,挑选高频使用或对关键帧有重要影响的部分进行转换的策略。

纹理压缩在编译构建时会提前处理预置图片,可能会增加编译时间并导致包体增大。如果资源图片占用较多空间,对包体积影响显著,建议筛选图片资源,以减少纹理压缩的开销。

非预置图片:多维优化策略

非预置图片不是应用内的资源,而是通常来自网络或本地文件系统,例如用户头像、动态内容中的图片、从服务器获取的商品图片。这类图片一般不是固定不变的。

使用图像编辑工具预压缩

在应用构建前,建议使用图片编辑工具或脚本,将图片分辨率调整为UI中的实际显示大小,并进行适当的压缩编码,以减小资源体积、提升加载性能。

场景案例:网页或App中有一个头像显示区域,大小为80×80px,有一张4180×4180的大图,若直接通过代码缩放到80×80显示,会出现内存占用高、解码慢、滚动卡顿的问题;正确的做法是,提前将图片压缩并缩放为80×80的小图,然后再进行加载显示。

耗时对比:

是否预压缩到实际UI尺寸 图片解码耗时
93ms
740us

预压缩到实际UI尺寸后,图片加载耗时从183us下降到95us,加载速度显著提升。

内存占用对比:

是否预压缩到实际UI尺寸 内存占用大小
90224KB
21230KB

预压缩到实际UI尺寸后,内存占用从90224KB下降到21230KB,图片加载占用的内存显著减少。
777.gif

GIF图片降低分辨率

通过使用FFmpeg三方库的能力降低分辨率,有两种方式实现GIF图片的压缩(也可将两种方式进行结合)。

使用-s设置图片的分辨率,将GIF图片的分辨率降低,宽高设置为90×90像素:

ffmpeg -i input.gif -s 90x90 -y output.gif

或者使用-vf参数配合scale过滤器,设置宽为90像素,高度自动等比例缩放:

ffmpeg -i input.gif -vf "scale=90:-1" -y output.gif

参数意义:

  • -s 90x90:设置图片分辨率为90×90像素
  • -y:覆盖已有文件
  • -vf “scale=90:-1”:设置图片滤镜,参数是单个滤镜或多个逗号分隔的滤镜链

使用CDN裁剪网络图片

优化网络图片资源加载有利于提升用户体验、减少流量消耗、降低内存占用以及加快页面渲染速度。CDN(Content Delivery Network)是一种用于加快内容分发的网络技术,通过CDN提供的动态处理能力,用户可按需调整图片的尺寸、质量、格式等参数,减少带宽消耗,提高页面加载速度。

大多数CDN服务提供者(如华为云)支持通过在图片URL后附加查询参数来动态调整图片大小、格式转换等。这些参数可以控制图片的宽度、高度、裁剪方式等属性,常见参数示例:

  • w:图片宽度
  • h:图片高度
  • fit:裁剪方式
  • q:图片质量
  • format:输出格式(如webp、jpeg)

有一个URL为"https://your-cdn-url.com/path/to/image.jpg"的网络图片,需要返回一个宽度为200,高度为150,采用cover裁剪方式,质量为85%,并且转换为.webp格式的图片:

private imgUrl = 'https://******.com/path/to/image.jpg?w=200&h=150&fit=cover&q=85&format=webp';

build() {
  NavDestination() {
    Column() {
      Image(this.imgUrl)
        .width(200)
        .height(150)
        .objectFit(ImageFit.Cover)
    }
    .width('100%')
    .height('100%')
  }
  .backgroundColor('#F1F3F5')
}

并非所有CDN服务均支持相同参数集,请查阅所用CDN服务商提供的文档了解详细参数信息。

优先使用.webp图片

.webp格式支持有损和无损压缩,其优势在于显著减少文件大小,同时保持高质量图像传输,是一种功能全面、适用于多种场景的图像格式。

.webp相较于.png和.jpg的优势:

优势 说明 适用场景
更小的文件体积、更快加载、节省宽带 同质量下比.jpg小25%~35%,比.png小25% 网站图片
支持透明通道 .png也支持,但.webp体积更小 UI图标、按钮、透明图
支持动画 可替代.gif,质量更高、体积更小 动画表情包

autoResize动态降采样

autoResize适用于需要组件尺寸动态适配的场景,例如响应页面内容变化或设备形态差异(如不同屏幕尺寸、折叠屏展开/收起)时,图片需要根据父容器尺寸自动缩放,使用autoResize可避免图片溢出或留白,提升界面自适应能力。

通过给Image组件设置autoResize为true时,组件会根据显示区域的尺寸决定用于绘制的图源尺寸,有利于减少内存占用。

Image(this.imageUrl)
  .width(300)
  .height(200)
  .autoResize(true)

autoResize在运行时根据组件实际显示尺寸决定图源尺寸,避免加载过大图片浪费内存,提升图片加载性能。

优化策略组合应用

根据图片资源是否预置(即打包在应用内),优化策略有所不同。

对于预置图片,推荐使用纹理压缩技术,在编译构建阶段提前完成CPU解码和纹理生成,减少CPU处理图片时间,降低内存占用,提升应用性能。需要注意的是,纹理压缩可能增加编译时间并导致包体增大,建议筛选图片资源,对.png格式图片进行纹理压缩,对.jpg和.webp格式的图片挑选高频使用或对关键帧有重要影响的部分进行转换。

对于非预置图片,可采用预压缩至实际UI尺寸、网络图片资源优化、GIF图片压缩、优先使用.webp图片等方式对使用前的图片进行优化;通过autoResize对使用中的图片进行优化。开发者需根据实际情况,选择合适方案或方案组合对图片资源进行性能优化。

图片加载优化不是单一方案,而是多维策略组合应用。预置图片优先纹理压缩,非预置图片从使用前和使用中两个层面优化,通过合理配置和优化策略,可以有效提升图片加载性能,减少内存占用,提升用户体验。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐