Skip to content

feat: optimize Flutter codec loading and native Surface lifecycle - #59

Open
q760440238 wants to merge 13 commits into
alibaba:mainfrom
q760440238:feature/android-animated-webp
Open

q760440238 wants to merge 13 commits into
alibaba:mainfrom
q760440238:feature/android-animated-webp

Conversation

@q760440238

@q760440238 q760440238 commented Jul 31, 2026

Copy link
Copy Markdown

Closes #61.

Summary

This updates PowerImage's ordinary-image path and native Surface lifecycle while preserving explicit native/custom decoder support.

  • use a single Flutter ImageProvider + Flutter codec for ordinary HTTP PNG/GIF/WebP by default; native remains opt-in for custom Drawable/private decoders;
  • coalesce encoded-byte network misses with subscriber-aware cancellation;
  • read disk hits with ImmutableBuffer.fromFilePath;
  • visibility-prioritize bounded network work and the first real Codec.getNextFrame() only; later animated frames remain normal asynchronous Flutter codec calls;
  • defer cache writes and LRU maintenance until after first paint, remove forced flushes, coarsen persistent touches, and batch writes with a measured 16-lane cap;
  • route the common PowerImage.network widget through Flutter's standard Image path while keeping the previous FilterQuality.low semantics;
  • replace per-frame native waitForPresent with a teardown fence;
  • scope release coordination to engine/Surface generations instead of a process-wide latch;
  • release native resources when the completer loses its last listener, without requiring global PowerImageBinding / ImageCacheExt behavior;
  • add deterministic 100-image static WebP / animated WebP / GIF fixtures and AndroidX Macrobenchmarks for Flutter-codec comparison, cold/cache states, and 100-Surface rebuild.

Physical-device results

Huawei VOG-AL10, Android 10 / API 29, Flutter 3.38.7, full AOT, identical in-process loopback fixture, 160x160 target. ExtendedImage is pinned to 10.1.0, CachedNetworkImage to 3.4.1. P95 is nearest-rank.

Cold first displayed frame (10x) P50 P95 peak RSS median
PowerImage 41.967 ms 46.382 ms 251,406 KB
ExtendedImage 10.1.0 42.390 ms 63.427 ms 258,870 KB
CachedNetworkImage 3.4.1 75.667 ms 78.328 ms 259,114 KB
Kept change Before P50/P95 After P50/P95
ImmutableBuffer.fromFilePath 32.834 / 38.803 ms 27.509 / 36.203 ms
deferred/bounded cache maintenance 241.172 / 249.526 ms 229.213 / 232.482 ms
schedule first real getNextFrame() 45.145 / 77.059 ms 41.967 / 46.382 ms
standard Flutter Image widget 44.361 / 60.090 ms 42.646 / 59.193 ms
animated queueBuffer average 0.560 / 0.582 ms 0.543 / 0.559 ms

The animated frame-count median increased from 819 to 826; private RSS remained flat. Rejected concurrency/quality experiments are not part of the final result set.

Final cache/lifecycle scenarios:

  • cold first frame, 10x: 41.967 / 46.382 ms P50/P95;
  • encoded-byte disk hit, 20x: 27.936 / 48.598 ms (three disclosed 45-49 ms outliers);
  • Flutter ImageCache hit, 10x: 15.878 / 22.868 ms;
  • release/rebuild 100 native Surfaces, 5x: 962.568 / 2,337.305 ms; all 5 runs completed 100 real frames.

Full methodology and raw AndroidX JSON:
https://github.com/q760440238/power_image/tree/feature/android-animated-webp/example/android/macrobenchmark/results/2026-08-03-VOG-AL10

Verification

  • flutter analyze — no errors (repository's existing warnings/info remain)
  • root flutter test — 83 passed
  • example/flutter test — 3 passed
  • ./gradlew :power_image:testDebugUnitTest :app:testDebugUnitTest — passed
  • AndroidX physical-device macrobenchmarks — cold/cache/widget/animated/100-Surface suites passed

- decode animated WebP resources with GlideWebpDecoder
- adapt reusable WebP frame buffers to PowerImage texture rendering
- support network, file, Flutter asset, and native asset loaders
- modernize the example for current Flutter and Android toolchains
@CLAassistant

CLAassistant commented Jul 31, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ q760440238
❌ YuQian


YuQian seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

YuQian added 2 commits July 31, 2026 16:14
Draw animated drawables directly to Flutter surfaces, coalesce stale frame invalidations, use hardware canvases when available, migrate textures to SurfaceProducer lifecycle handling, and decode Glide resources at their target size.
@q760440238 q760440238 changed the title feat(android): support animated WebP and current Flutter feat(android): support animated WebP, SurfaceProducer and current Flutter Jul 31, 2026
@q760440238

Copy link
Copy Markdown
Author

补充本轮 Android 生命周期与调度优化(commit 49917c3):

  • Glide 请求现在返回可取消句柄,在 request release 和 Engine detach 时清理 Target/资源。
  • 动画会跟随 Flutter TickerMode、应用前后台和 Surface 生命周期暂停/恢复;共享纹理使用 owner 计数。
  • 全局 Choreographer 将多个动图 invalidation 按 VSync 合并,并限制为单个在途渲染批次,旧帧直接覆盖。
  • 增加完整不透明帧快速路径扩展点;Glide GIF/WebP 不依赖不可靠的 Drawable.getOpacity(),避免局部帧残影。
  • 新增离线 Android Macrobenchmark:animated WebP 列表滚动、FrameTimingMetric、合帧批次 Count/Average Perfetto 指标。

最终验证:flutter test 51/51;Debug APK 构建通过;API 36 模拟器 Macrobenchmark 5/5 轮通过。模拟器只验证测试链路,性能结论需以物理设备结果为准。详细说明也已同步到 #60

@q760440238

Copy link
Copy Markdown
Author

最终修复已推送到 commit 48fbf6a

本轮先通过 Glide/PowerImage 线程日志确认“多图像排队”不是单线程串行,而是每图 SurfaceProducer 多缓冲与 unlockCanvasAndPost() 下游背压造成。最终让网络 animated WebP 使用压缩缓存文件→Flutter codec,GIF/自定义 Drawable 仍走 SurfaceProducer 直绘和 VSync 合帧。

Huawei VOG-AL10 同场景实测:Total PSS 中位数从 340.2 MB 降到 214.4 MB(-125.8 MB,-37.0%),Native Heap PSS 从 199.8 MB 降到 52.5 MB(-73.7%)。Android 16 模拟器与 cached_network_imageextended_image 同批测试结果已更新到 PR 正文和 #60;其中 PowerImage 总 PSS 略高 2.3%–2.6%,没有把它描述为整体内存领先。

验证:Flutter 54/54、fixture 2/2、Gradle 118 tasks;模拟器与 Huawei 真机均完成 20 个不同 animated WebP 的滚动、并发动画和生命周期测试。本次仅改 Android,iOS 未改。

@q760440238

Copy link
Copy Markdown
Author

根因与证据复核(commit 097a354

针对“20 张 WebP 看起来相同、动画像排队”的反馈,本轮没有继续盲目调整线程数,而是逐层核对 fixture、MethodChannel、Glide 线程和 Perfetto:

  • 旧的 PowerImageBenchmarkLoader 内嵌并返回同一段 Base64 WebP;旧安装包的 20 次请求日志也全部是相同的 380850 bytes / 300×225 / 100 frames。该死代码和注册入口现已删除,避免再次误用。
  • 当前 fixture 每轮日志为 20 次 ready、20 个不同 encoded byte length;Glide 使用 glide-source-thread-0..3 四个 source worker,并非单线程逐张解码。
  • 真正的重入问题在 Dart 缓存生命周期:Flutter-codec WebP 在最后一个 listener 移除时也被当成原生 Surface 动画立即 evict,重新进入视口会重复 MethodChannel 请求和 Glide 磁盘查找,视觉上形成错峰恢复。
  • 修复后,只有持有 SurfaceProducer 的原生多帧纹理在离开树时立即 evict;Flutter-codec WebP 元数据保留在 Flutter 有容量/字节上限的 ImageCache 中,LRU 淘汰仍会调用 provider dispose。GIF/自定义 Drawable 的原生释放策略不变。
  • 模拟器曾显示“Process crashed”,但系统日志没有 FATAL、OOM、LMKD 或 native signal;测试 PID 实际继续运行并最终输出 1 tests, 0 failed。原因是并发 Gradle/直跑控制端和包替换产生的连接误判,而非图片代码崩溃。

Macrobenchmark 也已修正:三库使用相同的 Flutter UI、raster queueBuffer 和峰值内存指标;启动、fixture、导航和首次解码不计入稳态滚动;六次手势上下交替。Android 10 不含 CALLBACK_ANIMATION section,因此只统计 raster 和内存,避免 AndroidX Benchmark 1.4.1 对空 section 计算中位数时崩溃。

Huawei VOG-AL10 / Android 10,同批 20 个不同动物 animated WebP,5 次中位数

Raster avg (ms) Raster max (ms) Raster count Java heap max (KB) RSS anon max (KB) RSS file max (KB) RSS total max (KB)
PowerImage 0.490 1.093 209 5,819 88,088 277,924 366,012
cached_network_image 0.494 1.114 207 2,521 87,612 279,204 366,816
extended_image 0.446 1.428 211 2,329 88,944 275,956 364,900

客观结论:PowerImage 对 cached_network_image 的 raster avg 快约 0.8%、max 低约 1.9%、总 RSS 低约 0.2%;对 extended_image 的 avg 慢约 9.9%,但 max 低约 23.5%,总 RSS 高约 0.3%。三者总 RSS 基本同档;PowerImage 因 Glide 额外持有约 3.2 MiB Java heap,不能宣称所有指标全面领先,当前优势是最小 raster 峰值和更完整的原生 Drawable/Surface 生命周期能力。

验证:根库 Flutter 55/55、example 3/3(20 个文件编码、首帧和动画属性均不同,页面卸载后无 setState 异常)、Gradle 132 tasks BUILD SUCCESSFUL;API 36 模拟器 PowerImage 5/5,Huawei 真机三库 15/15。本次仍只改 Android 路径,iOS 未改。

@q760440238

Copy link
Copy Markdown
Author

最终合并回归修复(commit f327b24

复核远端 merge 0aab066 后确认:它自动合并了两条实现,但把网络 WebP 回退到了 asDrawable(),删除了 encoded-file→Flutter-codec 通道和对应缓存测试,同时恢复了 benchmark 的同一 Base64 loader。结果是多张 WebP 又进入共享 native VSync/render pool,并在列表回看时重复 native request/Glide 查询,视觉上会出现相位错乱和“逐张排队”。

本提交恢复并统一最终方案:

  • 网络 animated WebP:Glide 只下载/缓存原始压缩文件,Flutter 为每张图建立独立 codec 和动画时钟;不创建逐图 SurfaceProducer。
  • Flutter-codec 元数据保留在有界、可淘汰的 ImageCache 中,避免列表回看时重新启动。
  • GIF/自定义 animated Drawable 继续使用 SurfaceProducer、hardware canvas 回退、VSync 合帧、旧帧覆盖和生命周期释放。
  • 恢复 20 个不同动物 WebP、公平三库 Macrobenchmark 及 encoded/cache 回归测试。

最终验证:

  • Flutter package 55/55,example 3/3
  • Android unit/build/benchmark compile 242 tasks
  • Android 16 模拟器 PowerImage Macrobenchmark 5/5
  • Huawei VOG-AL10 / Android 10:PowerImage、cached_network_image、extended_image 各 5 轮,合计 15/15
  • 日志:20 个唯一字节长度,Glide source-thread-0..3 均参与,Flutter codec 20/20,SurfaceProducer 0,load failure 0。

华为真机中位数:PowerImage raster queueBuffer 平均/最大 0.523/1.118 ms,总 RSS(anon+file)334.3 MiB;cached 为 0.537/1.009 ms、358.8 MiB,extended 为 0.541/1.332 ms、355.7 MiB。PowerImage 总 RSS 低约 6.8% / 6.0%,但 Java heap 高约 3.3–3.4 MiB,且相对 cached 的最大 queueBuffer 高约 10.8%,因此不声称所有指标都领先。完整数据已更新到 #60

当前分支无代码冲突;GitHub 仍显示 CLA 未签/工作流等待维护者批准。本次只改 Android,iOS 未改。

@q760440238 q760440238 changed the title feat(android): support animated WebP, SurfaceProducer and current Flutter feat: optimize Flutter codec loading and native Surface lifecycle Aug 3, 2026
Added a benchmark report comparing the performance of 20 different GIPHY animations in WebP format on a Huawei VOG-AL10 device. The report includes test environment details, results, and conclusions regarding CPU and memory usage.
Added a CSV file containing Giphy materials with metadata including dimensions, frame count, duration, and URLs.
@q760440238

Copy link
Copy Markdown
Author

Additional verification: 20-animation steady-state benchmark

The head branch now includes the raw result, report, and material manifest. This is additive to the existing cold first-frame comparison in the PR body (PowerImage 41.967/46.382 ms P50/P95; ExtendedImage 10.1.0 42.390/63.427 ms; CachedNetworkImage 3.4.1 75.667/78.328 ms).

Huawei VOG-AL10, Android 10/API 29, 20 different GIPHY animated WebPs shown simultaneously in a fixed 4x5 grid. Five interleaved rounds per implementation, with 12 s warm-up and 20 s sampling per round:

Implementation CPU P50/P95 PSS P50/P95 KiB Graphics P50/P95 KiB Surface FPS P50
PowerImage (Flutter codec) 133.704% / 136.405% 150,736 / 157,014 47,436 / 48,742 60.116
ExtendedImage 10.1.0 133.857% / 136.022% 148,206 / 160,580 48,548 / 53,586 60.132
Pure Android Glide 4.16.0 138.088% / 154.886% 112,489 / 114,503 43,728 / 45,159 60.176

PowerImage and ExtendedImage are effectively tied on median CPU. PowerImage's median PSS is 1.71% higher, but P95 PSS is 2.22% lower, P95 RSS is 3.36% lower, and P95 Graphics is 9.04% lower. Versus native Glide, PowerImage uses 3.17% less median CPU and 11.93% less P95 CPU, while Flutter Engine/Impeller adds about 38 MiB PSS.

All 300 GPU-frequency samples remained at 139 MHz (720 MHz maximum). GPU utilization/perf counters are not accessible on this production kernel, so frequency is a workload proxy only.

Full report and machine-readable results:
https://github.com/q760440238/power_image/tree/feature/android-animated-webp/example/android/macrobenchmark/results/2026-08-04-VOG-AL10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: Flutter codec loading, cache lifecycle, and Surface teardown bottlenecks

2 participants