Compose Multiplatform LazyGrid 图片网格性能基准:跨 Android、iOS 与 Desktop 的三端同构实现
2026/9/13 20:33:43 网站建设 项目流程

Compose Multiplatform LazyGrid 图片网格性能基准:跨 Android、iOS 与 Desktop 的三端同构实现

【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform

本篇文章以仓库中 LazyGridImageView 基准示例 为主线,系统拆解如何用同一套 Compose Multiplatform 代码在 Android、iOS、Desktop(以及 JS/Wasm 目标)上渲染 999 张图片的 3 列懒加载网格,并与原生 Android(Jetpack Compose + Coil)和原生 iOS(SwiftUI + AsyncImage)两套纯原生实现进行逐平台对比。读者在读完本文后,将掌握 Compose Multiplatform 资源目录(composeResources)、LazyVerticalGrid懒加载网格、Coil 异步图片加载与自动滚动压力测试脚本的完整落地写法,以及一套可复现的三端基准测量工作流(大小、启动时间、FPS、CPU/GPU 占用等),可直接照搬到自己的性能评测或图片类应用项目中。

项目定位:为什么需要三个平台同一套功能

LazyGridImageView是仓库benchmarks/showcases目录下专门用于横向对比 Compose Multiplatform 与纯原生实现性能的示例工程。它用三份独立实现完成完全相同的用户场景:

  1. Kotlin Multiplatform + Compose:位于composeApp模块,共享一份commonMain代码,同时产出 Android、iOS、Desktop、JS 与 Wasm 目标;
  2. Native Android:位于nativeAndroidApp模块,纯 Android 实现(Jetpack Compose + Coil),图片来自 Android assets;
  3. Native iOS:位于nativeiosApp模块,纯 SwiftUI 实现,图片来自 Bundle 资源。

三个实现都渲染999 张 512×512 的图片,排列成3 列的懒加载网格,并提供一个Auto Scroll 自动滚动开关,用于在基准测试时制造持续的滚动负载。这个"同一功能、三份代码"的设计,使得对比指标(应用体积、启动时间、FPS、CPU/GPU 占用、内存等)具有可对照的公平基础,正如 README 所述:"The project is used to compare Compose Multiplatform performance metrics with native counter-parts such as size, startup time, FPS, CPU/GPU usage, etc."

仓库还额外提供了 compare_sizes.main.kts 与 measure_sizes.main.kts 两个 Kotlin 脚本,分别用于对比三端产物体积与测量各产物大小,支撑"应用体积"这一项基准维度。

项目结构与三端模块划分

整个工程位于benchmarks/showcases/LazyGridImageView/,目录组织如下:

目录 / 文件职责
composeApp/Kotlin Multiplatform 实现,含commonMainandroidMainiosMaindesktopMainwasmJsMainjsMain等源集
nativeAndroidApp/原生 Android(Jetpack Compose),图片资源放在app/src/main/assets/downloaded_images/
nativeiosApp/原生 iOS(SwiftUI),图片放在nativeiosApp/downloaded_images/,通过 Xcode 工程构建
download_images.sh从 Picsum Photos 批量下载 999 张测试图片并分发到三端资源目录
compare_sizes.main.kts/measure_sizes.main.kts产物体积对比 / 测量脚本
gradle/libs.versions.toml版本目录(Version Catalog),统一管理三方库版本
settings.gradle.kts声明:composeApp模块并启用TYPESAFE_PROJECT_ACCESSORS特性

composeApp:一份代码,五个平台目标

从 composeApp/build.gradle.kts 可以看到完整的平台矩阵:

  • androidTarget:Android 应用(JVM 11);
  • iosX64 / iosArm64 / iosSimulatorArm64:三个 iOS 目标,统一产出名为ComposeApp的静态 framework(isStatic = true);
  • jvm("desktop"):桌面 JVM 目标;
  • wasmJsjs:浏览器目标,均配置browser下的commonWebpackConfig输出composeApp.js

commonMain的依赖中既有compose.runtime / foundation / material3 / ui全套 Compose 库,也有compose.components.resources(资源组件,用于访问composeResources)和coil.compose(Coil 3 的 Compose 集成)。桌面端额外引入kotlinx.coroutines.swing,Android 端引入androidx.activity:activity-composecompose.preview。桌面入口在 main.kt,iOS 入口在 MainViewController.kt(ComposeUIViewController { App() }),Android 入口在 MainActivity.kt(setContent { App() })——三个平台的界面代码全部收敛到commonMain的同一个App()

资源放置差异:三套体系,同一批图片

平台资源目录加载方式
Compose MultiplatformcomposeApp/src/commonMain/composeResources/files/Res.getUri("files/xxx.jpg")+ Coil
原生 AndroidnativeAndroidApp/app/src/main/assets/downloaded_images/file:///android_asset/前缀 + Coil
原生 iOSnativeiosApp/nativeiosApp/downloaded_images/(Bundle 内)Bundle.main.url(...)+ SwiftUIAsyncImage

准备数据:一键下载 999 张测试图片

项目要求在任何构建之前先运行下载脚本(README 明确说明"Please run the script before building the project")。脚本位于 download_images.sh:

./download_images.sh

脚本核心逻辑如下:

  1. 预创建三个目标目录:composeApp/src/commonMain/composeResources/filesnativeiosApp/nativeiosApp/downloaded_imagesnativeAndroidApp/app/src/main/assets/downloaded_images
  2. 循环1..999,构造文件名downloaded_image%03d.jpg(即downloaded_image001.jpgdownloaded_image999.jpg);
  3. 每个编号用固定 ID 从https://picsum.photos/id/$i/512/512.jpg下载,失败时自动回退到随机图片https://picsum.photos/512/512.jpg
  4. 下载成功(文件非空)后,cp复制到 iOS 与 Android 两个目录,保证三端使用完全相同的图片数据;
  5. 每张图片之间sleep 0.1防止请求过快被限流。

注意两点:

  • 图片统一为512×512 的 JPG,与后续 UI 中aspectRatio(1f)的正方形卡片一一对应;
  • 由于某些编号在 Picsum 上可能不存在(404),脚本会对失败项使用随机图兜底,因此本地实际成功数可能少于 999,代码运行时也会对缺失资源做防御性处理(见下文Res.getUri的 try/catch)。

仓库中已附带downloaded_image001.jpg等少量示例图片,便于在没有联网环境时先跑通工程;完整数据仍需执行脚本生成。

Compose Multiplatform 实现:commonMain 中的网格与图片加载

资源枚举与防御式解析

App.kt 中首先在remember块里遍历 1..999,用 Compose 资源的类型安全生成器Res.getUri("files/downloaded_imageXXX.jpg")解析每个资源 URI,并用 try/catch 忽略下载失败产生的缺失资源:

val uris = remember { val availableResources: MutableList<String> = mutableListOf() for (index in 1..999) { try { val resUri = Res.getUri("files/downloaded_image${index.toString().padStart(3, '0')}.jpg") availableResources.add(resUri) } catch (e: Exception) { //ignore } } List(999) { index -> availableResources[index % availableResources.size] } }

index % availableResources.size的取模写法保证了即使实际可用图片少于 999 张,列表仍能凑满 999 项,从而保证基准负载的一致性。这段代码依赖compose.components.resources提供的Res生成类(对应lazygridimage.composeapp.generated.resources.Res导入),并使用@OptIn(ExperimentalResourceApi::class)声明实验 API。

3 列 LazyVerticalGrid 与自动滚动

网格本体使用LazyVerticalGrid,列数由常量numOfColumns = 3决定:

LazyVerticalGrid( columns = GridCells.Fixed(numOfColumns), contentPadding = PaddingValues(4.dp), modifier = Modifier.fillMaxSize(), state = gridState ) { items(uris) { uri -> ImageCard(uri, modifier = Modifier.padding(4.dp)) } }

LazyVerticalGrid只在视口内组合可见条目,这正是"999 张图片仍能流畅滚动"的关键。配合rememberLazyGridState(),代码实现了一个自动往返滚动逻辑:勾选顶部 "Auto Scroll" 复选框后,LaunchedEffect(autoScroll)中每delay(100)毫秒调用一次gridState.animateScrollToItem(index = currentIndex),每次按numOfColumns(3 行)步进;到达底部(currentIndex >= uris.size - numOfColumns - 1)后翻转方向向上滚动,到达顶部再翻转。这样在基准测试时无需人工操作即可持续制造滚动压力。

图片卡片:Coil AsyncImage + 正方形裁剪

每个网格条目封装在ImageCard中:

@Composable fun ImageCard(uri: String, modifier: Modifier = Modifier) { Card( modifier = modifier .aspectRatio(1f) // Square aspect ratio .fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { AsyncImage( model = uri, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() ) } }
  • aspectRatio(1f)让每个卡片呈正方形,与 512×512 的源图比例一致;
  • ContentScale.Crop做居中裁剪填充;
  • 图片加载使用 Coil 3 的coil3.compose.AsyncImage,模型直接传Res.getUri解析出的资源 URI——这正是 README 所述"images are loaded using Coil library from the Compose Multiplatform resources"的实现位置。Coil 负责异步解码、缓存与生命周期管理,因此App()中没有任何手写协程加载逻辑。

平台薄壳

三个平台的入口都只是把App()塞进各自宿主:

  • Android:MainActivity.kt ——setContent { App() }
  • iOS:MainViewController.kt ——ComposeUIViewController { App() }
  • Desktop:main.kt ——application { Window(title = "LazyGridImage") { App() } }

原生 Android 实现:Jetpack Compose + Coil + assets

nativeAndroidApp用纯 Android 技术栈重写同一功能,入口在 nativeAndroidApp/app/src/main/java/org/jetbrains/lazygridimage/MainActivity.kt。

与 Compose Multiplatform 版本最大的差异在资源来源:这里通过context.assets.list("downloaded_images/")枚举 assets 目录下的全部图片文件名,然后拼出file:///android_asset/downloaded_images/xxx.jpg形式的 URI:

val uris = remember { val imagesFolder = "downloaded_images/" val availableResources = context.assets.list(imagesFolder) List(999) { index -> "file:///android_asset/" + imagesFolder + availableResources!![index % availableResources.size] } }

其余骨架与共享版本高度一致:LazyVerticalGrid(columns = GridCells.Fixed(3))rememberLazyGridState()、Auto Scroll 复选框 +LaunchedEffect+animateScrollToItem自动滚动、ImageCardAsyncImage(model = uri, contentScale = ContentScale.Crop)。也就是说,只要把"资源 URI 的获取方式"这一层换掉,网格与滚动逻辑在 Jetpack Compose 与 Compose Multiplatform 之间可以近乎原样复用——这正是两套实现便于直接对比的前提。

原生 iOS 实现:SwiftUI LazyVGrid + AsyncImage

nativeiosApp是纯 SwiftUI 工程,主要文件:

  • ContentView.swift:网格 + 自动滚动 + 图片名加载;
  • GridItemView.swift:单个网格条目的AsyncImage渲染;
  • App.swift:应用入口。

ContentView 中同样固定 3 列:GridItem(.flexible())× 3,网格本体是LazyVGrid(columns:columns, spacing: 8)。自动滚动用Timer.publish(every: 0.1, on: .main, in: .common)每 100ms 触发一次updateScrollPosition(),配合ScrollViewReaderproxy.scrollTo(newPosition, anchor: .top)完成往返滚动,步长同样是列数 3——与 Compose 版delay(100)+animateScrollToItem的频率与步进完全对齐,保证两边滚动负载等价。

图片名通过loadImageNames()构造downloaded_image001downloaded_image999,并保留testingMode开关:测试模式下先用UIImage(named:)过滤掉未成功下载的图片(existingImages),再取模补满 999 项,与 Compose 版的防御式处理思路一致。

GridItemView.swift 展示了资源定位与加载的差异点:

let imageUrl = Bundle.main.url(forResource: imageName, withExtension: "jpg") AsyncImage(url: imageUrl) { phase in switch phase { case .success(let image): image .resizable() .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) default: Color.gray } }
  • 资源来自Bundledownloaded_images目录被打包进 app),对应 README 所述"uses iOS asset catalog to load images"
  • AsyncImage是 SwiftUI 自带的异步图片加载器,加载中/失败时渲染Color.gray占位;
  • 条目外层.aspectRatio(1, contentMode: .fill).id(index)(供ScrollViewReader定位),背景白色 +cornerRadius(8)圆角卡片,视觉上与 Compose 版的Card保持一致。

三端构建与运行指南

按 README 的说明,三套实现的启动方式如下:

实现操作
Compose Multiplatform(Android)用 Android Studio 打开工程,运行composeApp配置
Compose Multiplatform(iOS)打开iosApp目录中的 Xcode 工程运行(注意composeApp产物是名为ComposeApp的静态 framework)
Compose Multiplatform(Desktop)在 IntelliJ IDEA 或 Android Studio 中运行desktopApp配置
原生 Android用 Android Studio 打开nativeAndroidApp目录直接运行
原生 iOS用 Xcode 打开nativeiosApp/nativeiosApp.xcodeproj运行

前置条件:先执行./download_images.sh下载 999 张图片,否则资源目录为空、网格将无图可显(iOS 端testingMode即为此场景的容错)。

关于 Web 目标需要说明:composeApp的构建脚本虽然声明了wasmJsjs两个浏览器目标,但commonMain中的Res.getUri解析与 Coil 加载在当前资源配置下主要面向桌面/移动端验证,浏览器端运行请以实际构建产物为准,本文档仅记录移动与桌面三端的官方运行路径。

基准测量的配套工具

工程目录下还提供了两个.main.kts脚本,用于自动化"体积"维度的测量:

  • measure_sizes.main.kts:测量各平台产物体积;
  • compare_sizes.main.kts:对三端产物进行体积对比输出。

结合 README 提到的对比维度(应用大小、启动时间、FPS、CPU/GPU 占用等),建议的完整基准流程是:

  1. 运行./download_images.sh准备 999 张 512×512 图片;
  2. 分别构建 Compose Multiplatform、原生 Android、原生 iOS 三个产物;
  3. measure_sizes.main.kts/compare_sizes.main.kts量化体积差异;
  4. 启动 App 后勾选 "Auto Scroll",让三端以相同的 100ms 步进做往返滚动,再用系统级 Profiler(Android Profiler、Xcode Instruments、桌面性能分析工具)采集 FPS、CPU/GPU 占用与内存;
  5. 汇总数据并对照三份实现得出结论。

小结:一套 UI、三种落地、可比可测

LazyGridImageView示例的核心价值可以概括为三点:

  1. 代码复用极限App()一份 commonMain 代码同时支撑 Android、iOS、Desktop 三个平台,平台差异被压缩到三个各不超 20 行的入口文件;
  2. 等价负载设计:三端均固定 999 张图、3 列网格、100ms 步进自动往返滚动,保证基准测试输入一致;
  3. 可复制的测量链路download_images.sh统一数据源,measure/compare_sizes.main.kts量化体积,外部 Profiler 采集运行时指标。

无论是想评估 Compose Multiplatform 在真实图片密集型场景下的表现,还是想为自己的项目搭建一套跨平台网格 + Coil 异步图片的参考实现,都可以直接以本示例为起点:复制composeApp目录,替换composeResources/files中的图片数据,即可在最短时间内得到一套可运行的跨平台 LazyGrid 图片浏览器。

【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询