From 80ab32890603296d1dcd2b356c2e47a07be599d3 Mon Sep 17 00:00:00 2001 From: meyou <2636699780@qq.com> Date: Mon, 25 May 2026 22:43:45 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E9=99=90=E5=88=B6=20WebP=20=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E9=87=8D=E5=A4=8D=E6=AC=A1=E6=95=B0=E5=B9=B6=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E5=85=A5=E5=9C=BA=E5=8A=A8=E7=94=BB=EF=BC=8C=E5=87=8F?= =?UTF-8?q?=E5=B0=91=E5=B8=A7=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perfetto trace 分析显示 75.6% 帧延迟,根因为 250x250 WebP 持续以 11-14 FPS 无限循环解码。每次解码周期导致 4-26 帧延迟。 - AnimatedGif: repeatCount(2) 限制动画播放 3 次后停止 - AnimatedGif: 移除两阶段弹跳动画,改为 150ms 淡入 - README: 添加构建命令和产物路径 --- README.md | 26 +++++++++++++ .../kotlin/plus/rua/project/ui/AnimatedGif.kt | 39 ++++++++++--------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 35149a4..56dfaea 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,30 @@ - 双模块:`:shared`(UI + 逻辑) · `:androidApp`(薄壳) - iOS 入口为 `MainViewController.kt`,Xcode 工程位于 `iosApp/` +## 构建 + +```bash +# Debug +./gradlew :app:assembleDebug # 构建 debug APK +./gradlew :app:installDebug # 安装 debug APK 到设备 + +# Release +./gradlew :app:assembleRelease # 构建 release APK +./gradlew :app:installBenchmark # 安装 benchmark(release + 可调试)APK + +# 测试 +./gradlew :core:testDebugUnitTest # 运行全部测试 +./gradlew :core:testDebugUnitTest --tests "plus.rua.project.ui.CalendarUtilsTest" # 运行单个测试 + +# Baseline Profile(需要连接设备) +./gradlew :macrobenchmark:updateBaselineProfile # 一键生成 + 自动复制到 :core +./gradlew :macrobenchmark:connectedBenchmarkAndroidTest # 仅运行基准测试 + +# 性能 Profiling(需要连接设备) +./scripts/profile.sh # 默认 8 秒 +./scripts/profile.sh 15 # 自定义时长 +``` + +构建产物位于 `app/build/outputs/apk//` 目录。 + 线条小狗表情包来自 https://www.douban.com/group/topic/264788645/?_i=9181692phrDzjR,9241256phrDzjR diff --git a/core/src/main/kotlin/plus/rua/project/ui/AnimatedGif.kt b/core/src/main/kotlin/plus/rua/project/ui/AnimatedGif.kt index f24f9c0..7f4fbb1 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/AnimatedGif.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/AnimatedGif.kt @@ -2,8 +2,6 @@ package plus.rua.project.ui import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -11,6 +9,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import com.github.panpf.sketch.AsyncImage +import com.github.panpf.sketch.rememberAsyncImageState +import com.github.panpf.sketch.request.ImageOptions +import com.github.panpf.sketch.request.repeatCount import plus.rua.project.getWebpUri /** @@ -18,12 +19,16 @@ import plus.rua.project.getWebpUri */ private val WEBP_FILES = (1..152).map { "${it.toString().padStart(3, '0')}.webp" } +private const val REPEAT_COUNT = 2 + /** - * 显示动画 GIF 图片,切换日期时随机选择一个。 + * 显示动画 WebP 图片,切换日期时随机选择一个。 + * + * 动画播放 3 次(1 + [REPEAT_COUNT])后停止,避免持续解码导致的帧丢失。 * * @param modifier 应用于图片的 Modifier * @param contentDescription 无障碍描述 - * @param seed 用于控制重新随机时机的 key,变化时重新选择 GIF + * @param seed 用于控制重新随机时机的 key,变化时重新选择 WebP */ @Composable fun AnimatedGif( @@ -33,28 +38,26 @@ fun AnimatedGif( ) { val webpFile = remember(seed) { WEBP_FILES.random() } val uri = remember(webpFile) { getWebpUri(webpFile) } - val scale = remember { Animatable(0f) } + val alpha = remember { Animatable(0f) } LaunchedEffect(seed) { - scale.snapTo(0f) - scale.animateTo( - targetValue = 1.1f, - animationSpec = tween(250, easing = FastOutSlowInEasing), - ) - scale.animateTo( + alpha.snapTo(0f) + alpha.animateTo( targetValue = 1f, - animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy), + animationSpec = tween(150, easing = FastOutSlowInEasing), ) } + val state = rememberAsyncImageState( + options = remember { ImageOptions { repeatCount(REPEAT_COUNT) } } + ) + AsyncImage( uri = uri, contentDescription = contentDescription, - modifier = modifier - .graphicsLayer { - scaleX = scale.value - scaleY = scale.value - alpha = scale.value.coerceIn(0f, 1f) - }, + state = state, + modifier = modifier.graphicsLayer { + this.alpha = alpha.value + }, ) }