likes
comments
collection
share

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

作者站长头像
站长
· 阅读数 26

SVGA 是一种使用简单、性能卓越的跨平台开源动画格式。它的出现,使原本复杂的动画开发流程分工明确,所有人都只需专注于自身领域,大大减少了动画交互的沟通成本。

典型的 SVGA 动画开发流程

  • 动画设计师

    1. 使用 AE、Animate(Flash) 等工具制作动画;
    2. 使用 SVGAConverter 将动画源文件转换为 SVGA 文件;
  • 开发工程师

    1. 根据不同平台的需要集成相应平台的 SVGAPlayer;
    2. 调用 SVGAParser 从本地资源或远程服务器解析 SVGA 文件;
    3. 将解析出的内容交给相应平台的 SVGAPlayer 播放。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

SVGA 与 GIF、Lottie 孰优孰劣?

SVGA 文件的本质就是一个采用了 ProtoBuf + Zlib 方式打包的压缩文件,其最大的特点在于,创新性地将一个动画拆分成了以下两个部分:

  • 动画元素:指的是动画过程中运用到的各种切图素材(包含位图、矢量图等)。
  • 动画帧:代表所有元素在某一时刻的 位置、大小、旋转角度、透明度 等信息。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

与 GIF 相比

由于 GIF 的每一帧都是一张完整的图片,因此无论是文件大小,还是播放时占用的资源,都不太符合生产的要求,且由于 GIF 只支持 8 bit 的色深,实际的动画表现效果也不够理想。

而 SVGA 所包含的动画元素是有限的,且在动画过程中也在不断复用,因而可以有效地降低文件大小和播放时占用的资源, 且由于 PNG 格式的动画元素可以支持 32 bit 的色深,因而在动画表现效果上也更胜 GIF 一筹。

与 Lottie 相比

Lottie 与 SVGA 其实是有点类似的,只不过它导出的内容是「动画元素」与「动画脚本」,它实际上是把完整的动画逻辑在相应平台上重新实现了一次。这也就意味着,如果遇上了较为复杂的动画效果,如需要进行一些高阶的插值运算(二次线性方程、贝塞尔曲线方程等)时,Lottie 同样会面临性能问题的困扰。

而 SVGA 则是在导出动画时,把动画逻辑封装在了每一个动画帧的帧信息中,播放器只需读取帧信息,然后粗暴地把每一个动画元素,丝毫不差地渲染到屏幕上即可,无需进行任何的插值运算,从而提高了性能。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

所以我们才会说,“SVGA 不关心关键帧,因为 SVGA 里面的每一帧都是关键帧”。

如果你还是无法理解二者的区别,我们可以用一个生活化的例子来进行类比。

假设你想拍摄一部低成本的科幻片,有一幕是主角用念力驱使桌子上的一个杯子移动一段距离。

毫无疑问,这一幕里的动画元素就是一个杯子、一张桌子和一个便秘表情的主角。

如果用 Lottie 实现,就相当于你需要构思如何在不接触杯子的情况下让杯子移动。你可能需要在桌子底下安上一块磁铁,或者同时调整桌子角度和拍摄角度,你需要在拍摄脚本中写一遍,然后在实际拍摄中操作一遍。拍摄脚本就相当于 Lottie 的动画脚本,越复杂的场景拍摄方式就越困难。

如果用 SVGA 实现,就相当于你只需要在桌子上每隔1厘米做上标记,这些标记就相当于 SVGA 的动画帧,记录着杯子在某一时刻的位置,然后我们只需要固定镜头角度,然后每移动杯子到下一个标记就拍摄一次,之后将所有镜头串联起来,即可实现相同的效果,非常简单粗暴。

现有 SVGA 使用场景存在的问题

近年来,随着 VAP 等动画特效库的兴起,MP4 逐渐取代 SVGA 成为了直播间礼物的主流格式。只不过,凭借其自身独特的优势,SVGA 仍在小型动画(诸如头像框、声波特效等)领域保有一席之地。

在对现有的 SVGA 使用场景展开了一轮考察之后,我们发现了以下几个颇为显著的问题:

问题1:内存占用高且长期驻留

虽然 SVGA 支持动态绘制矢量图,然而在实际的应用中,动画设计师们更多地会使用提前做好的位图来制作动画,且往往会出于对动画效果的极致追求,在动画中使用了大量的位图素材。

诸如此类的原因,最终致使 SVGA 成为了仅次于 MP4 礼物的内存消耗大户,而且相较于仅播放 1 次的 MP4 礼物,某些 SVGA 是需要重复播放的(如头像框),这就导致只要页面不销毁,SVGA 就会长期驻留在内存当中。

问题2:不同尺寸适配成本较大

同一个 SVGA 可能会以不同的尺寸展示于多个地方(如特效商品的预览与正常展示),如果要求生成不同的尺寸,一来会增加动效制作人员的工作量,二来也会增大包体大小或磁盘缓存大小;而如果仅产出单一的尺寸,在小尺寸的控件上使用大尺寸的 SVGA 显然性价比不高。

问题3:前期规范缺失难以修正

某些项目的前期没有为 SVGA 制定产出规范,导致某些 SVGA 的素材资源不符合要求,全部进行修改的话成本过高,并且某些 SVGA 资源出于成本与效率的考量,采用了外部购买的方式,规范也难以覆盖。

SVGA 总内存占用估算方式

SVGA 内存占用的主要来源还是其动画过程中使用到的各种切图素材,因此,可以通过单独计算并汇总其中每个切图素材的内存占用,来估算 SVGA 的总内存占用。

Android/iOS/Flutter 默认都是以 32 bit 的色深解码图片,1 byte = 8 bit,因此每个切图素材的内存占用的计算公式为:图片分辨率 * 4 bytes。

按照这个计算方法得出的 SVGA 总内存占用,与其在 SVGA 官网预览时所得到的内存占用数值是一致的:

内存占用:2.7479171752929688 MB 
Dian---{"width":30, "height":30, memoryUsage: 0.0034332275390625 MB}
GuangHuan---{"width":169, "height":95, memoryUsage: 0.061244964599609375 MB}
Ren01---{"width":174, "height":293, memoryUsage: 0.19448089599609375 MB}
ShanGuang---{"width":154, "height":154, memoryUsage: 0.0904693603515625 MB}
Shou01---{"width":125, "height":78, memoryUsage: 0.03719329833984375 MB}
tou---{"width":126, "height":135, memoryUsage: 0.06488800048828125 MB}
...

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

有了 SVGA 动画实现原理作为支撑,也梳理出了现有 SVGA 使用场景问题,同时获知了 SVGA 内存计算方式,接下来我们便以 Flutter 平台为例,给出相应的内存优化方案,方案是通用的,其他平台均可借鉴其思路。

Flutter 是如何揪出尺寸过大的图片的?

在 Flutter inspector 工具里有一个「高亮尺寸过大的图片」的选项,启用该选项之后,Flutter会将尺寸过大的图片进行「垂直翻转」与「色调反转」:


SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各


SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各


同时,在 控制台中打印图片的「显示尺寸」、「解码尺寸」以及「额外内存消耗」

dash.png has a display size of 213×392 but a decode size of 2130×392, which uses an additional 2542KB.

dash.png 的显示尺寸为 213×392,但解码尺寸为 2130×392,这额外使用了 2542KB 。

这部分逻辑的相关代码实现位于 decoration_image.dart 源文件当中:

/// 仅在 Debug 模式或 Profile 模式下执行
if (!kReleaseMode) {
  // 我们假设所有图像都针对具有最高设备像素比的视图进行解码,并将其用作图像显示尺寸的上限。
  final double maxDevicePixelRatio = PaintingBinding.instance.platformDispatcher.views.fold(
    0.0,
    (double previousValue, ui.FlutterView view) => math.max(previousValue, view.devicePixelRatio),
  );
  final ImageSizeInfo sizeInfo = ImageSizeInfo(
    source: debugImageLabel ?? '<Unknown Image(${image.width}×${image.height})>',
    imageSize: Size(image.width.toDouble(), image.height.toDouble()),
    displaySize: outputSize * maxDevicePixelRatio,
  );
  assert(() {
    /// 启用了「高亮尺寸过大的图片」选项 & 解码尺寸大于显示尺寸
    if (debugInvertOversizedImages &&
          sizeInfo.decodedSizeInBytes > sizeInfo.displaySizeInBytes + debugImageOverheadAllowance) {
      final int overheadInKilobytes = (sizeInfo.decodedSizeInBytes - sizeInfo.displaySizeInBytes) ~/ 1024;
      final int outputWidth = sizeInfo.displaySize.width.toInt();
      final int outputHeight = sizeInfo.displaySize.height.toInt();
      /// 打印显示尺寸 & 解码尺寸 & 额外内存消耗
      FlutterError.reportError(FlutterErrorDetails(
        exception: 'Image $debugImageLabel has a display size of '
        '$outputWidth×$outputHeight but a decode size of '
        '${image.width}×${image.height}, which uses an additional '
        '${overheadInKilobytes}KB (assuming a device pixel ratio of '
        '$maxDevicePixelRatio).\n\n'
        'Consider resizing the asset ahead of time, supplying a cacheWidth '
        'parameter of $outputWidth, a cacheHeight parameter of '
        '$outputHeight, or using a ResizeImage.',
        library: 'painting library',
        context: ErrorDescription('while painting an image'),
      ));
      // 色调反转
      canvas.saveLayer(
        destinationRect,
        Paint()..colorFilter = const ColorFilter.matrix(<double>[
          -1,  0,  0, 0, 255,
          0, -1,  0, 0, 255,
          0,  0, -1, 0, 255,
          0,  0,  0, 1,   0,
        ]),
      );
      // 垂直翻转
      final double dy = -(rect.top + rect.height / 2.0);
      canvas.translate(0.0, -dy);
      canvas.scale(1.0, -1.0);
      canvas.translate(0.0, dy);
      invertedCanvas = true;
    }
    return true;
  }());
}

这一类尺寸过大的图片会导致应用性能低下,在低端设备上这一情况尤为显著。而当列表中存在大量此类图片时,性能下降的影响还会叠加。

  • 可能的情况下,最好的办法还是直接调整图片资源的大小,让它变得更小;
  • 如果调整困难,还有一种办法,那就是在构造 Image Widget 时传递参数「cacheHeight」、「cacheWidth」;
  • 这两个参数可以让 Flutter 引擎按照指定的大小来解析图片,从而减少内存使用量;
  • 只不过相较于直接缩小图像资源本身,解码和存储图像的开销仍然是相对比较昂贵的。

我们从源码的角度来看一下「cacheHeight」与「cacheWidth」参数让 Image Widget 做了什么事情。

  1. 当任一参数不为空时,Image 就会包装成 ResizeImage:
  static ImageProvider<Object> resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider<Object> provider) {
    if (cacheWidth != null || cacheHeight != null) {
      return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
    }
    return provider;
  }

2. ResizeImage 在 loadImage 方法内会根据图像缩放策略来调整目标宽高,进行精确调整或等比缩放。

return decode(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
        switch (policy) {
          case ResizeImagePolicy.exact:
            int? targetWidth = width;
            int? targetHeight = height;

            /// 指定了目标宽高,但目标宽高大于图像本身宽高,
            /// 且不允许缩放时,仍会调整为图像本身宽高
            if (!allowUpscaling) {
              if (targetWidth != null && targetWidth > intrinsicWidth) {
                targetWidth = intrinsicWidth;
              }
              if (targetHeight != null && targetHeight > intrinsicHeight) {
                targetHeight = intrinsicHeight;
              }
            }

            return ui.TargetImageSize(width: targetWidth, height: targetHeight);
          case ResizeImagePolicy.fit:
            /// 图像本身宽高缩放比
            final double aspectRatio = intrinsicWidth / intrinsicHeight;
            /// 最大宽高,根据外部传值限定
            final int maxWidth = width ?? intrinsicWidth;
            final int maxHeight = height ?? intrinsicHeight;
            /// 目标宽高,默认为图像本身宽高
            int targetWidth = intrinsicWidth;
            int targetHeight = intrinsicHeight;

            /// 图像本身宽度超过限制时,以宽度为基准进行缩放
            if (targetWidth > maxWidth) {
              targetWidth = maxWidth;
              targetHeight = targetWidth ~/ aspectRatio;
            }

            /// 图像本身宽度没超过限制而本身高度超过限制,
            /// 或者调整后的图像高度仍超过限制时,以高度为基准再进行缩放
            if (targetHeight > maxHeight) {
              targetHeight = maxHeight;
              targetWidth = (targetHeight * aspectRatio).floor();
            }

            /// 允许缩放
            if (allowUpscaling) {
              if (width == null) {
                /// 只指定了高度,高度直接调整为指定高度,宽度等比例缩放
                assert(height != null);
                targetHeight = height!;
                targetWidth = (targetHeight * aspectRatio).floor();
              } else if (height == null) {
                /// 只指定了宽度,宽度直接调整为指定宽度,高度等比例缩放
                targetWidth = width!;
                targetHeight = targetWidth ~/ aspectRatio;
              } else {
                /// 宽高都指定了,取不超过最大限制的较长边,另一边等比例缩放
                final int derivedMaxWidth = (maxHeight * aspectRatio).floor();
                final int derivedMaxHeight = maxWidth ~/ aspectRatio;
                targetWidth = math.min(maxWidth, derivedMaxWidth);
                targetHeight = math.min(maxHeight, derivedMaxHeight);
              }
            }

            return ui.TargetImageSize(width: targetWidth, height: targetHeight);
        }
      });

3. 两种图像缩放策略的差异:

exact
将图像大小调整为 ResizeImage.width 和 ResizeImage.height 指定的精确宽度和高度。
(即无视容器,指示多大就显示多大)

fit
根据需要缩放图像,以确保其适合 ResizeImage.width 和 ResizeImage.height 指定的边界框,同时保持其纵横比。
(即无论如何都保持其纵横比,宽度优先,allowUpscaling=false时最大显示原图大小,allowUpscaling=true时才会缩放到指定宽高)

https://api.flutter.dev/flutter/painting/ResizeImagePolicy.html

为什么解码成更小尺寸可以节省内存?

图片解码的原理,简单来讲,就是将压缩后的图片数据还原成未压缩的像素矩阵的过程。

图片在存储和传输时,为了节省空间,通常会进行压缩。压缩的方式有很多种,例如JPEG、PNG、WebP等。不同的压缩算法,其原理和压缩效率也不尽相同。

在解码时,解码器会根据压缩算法的规则,将压缩后的数据还原成原始的像素矩阵。

当我们要求解码成更小的尺寸时,解码器并不是简单地丢弃像素来缩小图像尺寸,而是会通过调整采样比例来生成不同尺寸的图像,例如,可以将每2x2的像素块合并成一个像素,从而将图像缩小一半。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

调整采样比例是缩小图片尺寸的关键步骤,也是在信息保留和尺寸缩减之间取得平衡的核心操作。

由于相邻像素通常颜色值比较接近,将它们合并成一个像素,虽然损失了一些细节信息,但人眼对这种变化的感知并不敏感。 在很多情况下,这种信息损失是可以接受的,并不会显著影响对图像内容的整体理解。

而当我们成功解码成更小的尺寸后,根据我们前面的提到的图片内存占用计算公式,由于图像的分辨率显著减小了,其内存占用自然而然也就随之降低了。

那么,在了解完了前面这些内容之后,现在我们面临的一个最关键的问题就是——

如何指定 SVGA 切图素材的解码大小?

SVGA 官方专门为 Flutter 平台提供了一个 SVGAPlayer-Flutter 库,这个库是通过 Flutter CustomPainter 以原生 API 的方式来渲染动画的。

其内部实际使用了 Flutter 的 PaintingBinding.instance.instantiateImageCodecWithSize 方法来解码图片,该方法有一个「getTargetSize」参数,该参数包含一个指定宽度与和一个指定高度,用以结合图像本身大小来进一步判断,最终确定解码图像的大小

/// 实例化图像编解码器。
/// 
/// buffer 参数是二进制图像数据(例如 PNG 或 GIF 二进制数据)。
/// 
/// 如果指定 getTargetSize 参数,则将调用该参数并传递图像的固有大小以确定图像解码后的大小。
/// 当仅指定宽度和高度之一时,省略的尺寸将被缩放以保持原始尺寸的纵横比。
/// 当两者都为空或省略时,图像将以其原始分辨率进行解码。
Future<Codec> instantiateImageCodecWithSize(
  ImmutableBuffer buffer, {
    TargetImageSizeCallback? getTargetSize,
  }) async {
  getTargetSize ??= _getDefaultImageSize;
  final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer);
  try {
    final TargetImageSize targetSize = getTargetSize(descriptor.width, descriptor.height);
    assert(targetSize.width == null || targetSize.width! > 0);
    assert(targetSize.height == null || targetSize.height! > 0);
    return descriptor.instantiateCodec(
      targetWidth: targetSize.width,
      targetHeight: targetSize.height,
    );
  } finally {
    buffer.dispose();
  }
}

那这个「getTargetSize」参数又应怎么传值呢?

首先,第一步我们需要确立一个「目标解码尺寸」,一般我们都会要求至少要贴合 SVGA 容器的尺寸,也即 SVGA 的显示尺寸最大不能超过 SVGA 容器的尺寸。

但是,如果我们真的直接以 SVGA 容器的尺寸作为目标解码尺寸,就会发现 SVGA 动画变得很模糊:

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

这是因为,我们还有一个维度没有考虑进来,那就是「设备像素密度」。

设备像素密度指的是设备屏幕单位面积内的像素数,称为dpi(dots per inch,每英寸点数)。当两个设备的尺寸相同而像素密度不同时,图像的效果呈现如下:

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

为了优化不同屏幕配置下的用户体验,确保图像能在所有屏幕上显示最佳效果,Android 和 iOS 都建议应针对常见的不同的屏幕尺寸和屏幕像素密度,提供对应的图片资源。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各 SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

那么同样,为了确保 SVGA 也能在所有屏幕上显示最佳效果,在传值时我们需要在 SVGA 容器尺寸的基础上乘以设备像素比,以比实际传值更大的解码尺寸去解码切图素材。

这里我们采用与常见的 .dp、.sp 等类似的扩展方法实现:

extension DimenExtension on double {
  /// 用来乘以设备像素比得出适当的图片解码大小,否则在高设备像素密度的设备上可能会显示模糊
  double get dpr => this * Get.window.devicePixelRatio;
}
SVGAWidget(
  animationController,
  fit: BoxFit.contain,
  clearsAfterStop: true,
  allowDrawingOverflow: true,
  filterQuality: FilterQuality.low,
  decodeWidth: containerWidth.dpr,
  decodeHeight: containerHeight.dpr
),

但是话说回来,如果一味地追求在所有屏幕上显示最佳效果,又与我们减少 SVGA 内存占用的初衷相违背,所以更合理的做法应该是在效果展示和内存占用之间取得平衡。

为此,我们可以参考 Image.asset 方法的 scale 参数,增加一个缩放因子,当此值不为空时,则忽略设备像素比直接取该值。

SVGAWidget(
  animationController,
  fit: BoxFit.contain,
  clearsAfterStop: true,
  allowDrawingOverflow: true,
  filterQuality: FilterQuality.low,
  decodeWidth: containerWidth,
  decodeHeight: containerHeight,
  scale: 2
),

那这个缩放因子取什么值合适呢?这个没有一个统一的定论,在实际操作中,当发现一个问题资源后,我们可以拿一个设备像素密度最高的设备,从较低值开始逐步调整缩放因子的值,在保证 SVGA 显示效果可接受的情况下,尽可能提升 SVGA的内存压缩比例

接下来,第二步我们需要获取 SVGA 的「画布尺寸」。在解析 SVGA 文件后得到的 MovieEntity 类中,包含了许多 SVGA 动画的关键信息,其中就包含了我们需要的画布尺寸 viewBoxWidth x viewBoxHeight。

  /// 从缓冲区下载动画文件,并对其进行解码。
  Future<MovieEntity> decodeFromBuffer(List<int> bytes,
      {double? decodeWidth, double? decodeHeight, double? scale}) {
    ...
  }  
message MovieEntity {
  string version = 1;                 // SVGA 格式版本号
  MovieParams params = 2;             // 动画参数
  map<string, bytes> images = 3;      // Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。
  repeated SpriteEntity sprites = 4;  // 元素列表
  repeated AudioEntity audios = 5;    // 音频列表
}
message MovieParams {
  float viewBoxWidth = 1;     // 画布宽
  float viewBoxHeight = 2;    // 画布高
  int32 fps = 3;              // 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
  int32 frames = 4;           // 动画总帧数
}

得到所有的这些计算因子后,如何指定 SVGA 切图素材的解码大小就很清晰了——

  1. 首先通过对比目标解码尺寸与 SVGA 画布尺寸,得到应缩放的比例;
  2. 再预读取每一个切图素材的尺寸,乘以这个比例得到实际应解码的尺寸;
  3. 作为 getTargetSize 参数传递给解码器,从而实现以指定的大小解码切图素材。

最终敲定的 SVGA 内存优化方案

步骤1: 检查 SVGA 资源使用

当应用以 Debug 或 Profile 模式运行,并启用了「高亮尺寸过大的图片」选项后,检查应用中使用了 SVGA 动画资源的地方。

步骤2: 高亮显示问题资源

当发现某个 SVGA 的画布尺寸大于其显示尺寸时,对 SVGA 进行色调反转,以方便开发者定位问题资源。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

需要注意的是,这里同样参考 Flutter 的做法,假设所有 SVGA 都针对具有最高设备像素比的视图进行解码,并将其用作 SVGA 显示尺寸的上限。

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

步骤3: 以更小尺寸解码 SVGA

当定位到问题资源后,即可修改代码,增加「decodeWidth 」与「decodeHeight 」参数,用以传递目标解码尺寸、计算缩放比例,从而让解码器以指定的大小解码切图素材,减少内存的消耗。

默认情况下会取设备像素比作为缩放因子,确保 SVGA 能在所有设备的屏幕上显示最佳效果。

步骤4: 调整缩放因子,取得最佳平衡

对比修改前后的内存占用有无明显改善,如果不太理想,则需要增加 scale 参数,手动调整缩放因子。

缩放因子11.5
SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各
内存占用0.3019561767578125 MB0.6835441589355469 MB
压缩比例89%75%
缩放因子23
SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各
内存占用1.2151031494140625 MB2.7479171752929688 MB
压缩比例56%0

可以看到,当缩放因子调整为1.5时,单靠肉眼已经很难觉察到与2、3的区别了,所以在这个例子中,1.5就是在效果展示和内存占用之间取得平衡的最佳数值,其压缩比例可以达到75%。

而在Profile模式下也可以从Memory View上看到,优化后的内存增长曲线高度相对于优化前的更加平缓:

Demo运行初始内存播放瞬间内存增长曲线运行一分钟后内存情况
SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各
SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

测试设备参数:

SVGA 内存高居?适配困难?规范缺失?🔱🐭👉🏻我来助你!SVGA 内存占用的主要来源还是其动画过程中使用到的各

目标解码尺寸=容器尺寸=容器尺寸=容器尺寸=容器尺寸
缩放因子11.523
SVGA 总解码时长180ms202ms197ms167ms174ms176ms185ms183ms184ms196ms192ms192ms228ms229ms240ms
最大尺寸图像解码时长70ms88ms81ms60ms64ms63ms72ms71ms72ms81ms83ms79ms115ms116ms121ms
最小尺寸图像解码时长12ms12ms12ms9ms12ms12ms12ms12ms11ms12ms12ms10ms12ms11ms12ms
转载自:https://juejin.cn/post/7412810199227138074
评论
请登录