You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose LazyColumn滚动崩溃问题求助(含Coursera示例)

LazyColumn滚动崩溃问题解决(大尺寸图片导致)

作为Android开发新手,在Coursera的Meta Android开发课程中实现包含自定义Composable的LazyColumn,每次在模拟器中滚动列表时应用都会崩溃,课程提供的示例解决方案也存在同样问题。

代码实现

1. 定义Dish数据类及DishRepository

object DishRepository {
    val dishes = listOf(
        Dish(
            1,
            "希腊沙拉",
            "著名的希腊沙拉,包含脆生菜、辣椒、橄榄,芝加哥风味。",
            12.99,
            R.drawable.greeksalad
        ),
        Dish(
            2, // 其他菜品数据
            // ...
        )
    )
}

2. 自定义MenuDish Composable

@Composable
fun MenuDish(id: Int, dish: Dish) {
    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Image(painter = painterResource(id = dish.imageResource),
            contentDescription = "菜品图片",
            modifier = Modifier.fillMaxWidth(),
            contentScale = ContentScale.FillWidth)
        Column (verticalArrangement = Arrangement.spacedBy(10.dp),
            modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
            Text(
                text = dish.name,
                style = MaterialTheme.typography.bodyMedium,
                modifier = Modifier.padding(8.dp)
            )
            Text(
                text = dish.description,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

3. LazyColumn中使用自定义Composable

LazyColumn {
    itemsIndexed(dishes) { _, dish ->
        MenuDish(dish) // 注:原代码存在参数不匹配,MenuDish定义需要id和dish两个参数,此处仅传入dish
    }
}

排查过程

  • 仅加载单个Dish对象时,LazyColumn可正常运行;
  • 手动添加多个Item加载不同Dish时,滚动仍会崩溃;
  • 新建空白项目重新实现代码,问题依旧。

问题定位

经分析堆栈信息,确定崩溃由两张超大尺寸图片导致:

  • 3.7MB - 6589x4395
  • 12MB - 8256x5504
    即使调整Image的Modifier(如设置size(50.dp)、移除fillMaxWidth等),仍会抛出相同异常。

崩溃堆栈信息(中文翻译版)

E  FATAL EXCEPTION: main
Process: com.jan.testapp, PID: 7144
java.lang.RuntimeException: Canvas: 尝试绘制过大的位图(181764096字节)。
    at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:266)
    at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
    at androidx.compose.ui.graphics.AndroidCanvas.drawImageRect-HPBpro0(AndroidCanvas.android.kt:271)
    at androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage-AZ2fEMs(CanvasDrawScope.kt:263)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawImage-AZ2fEMs(Unknown Source:40)
    at androidx.compose.ui.graphics.drawscope.DrawScope.drawImage-AZ2fEMs$default(DrawScope.kt:510)
    at androidx.compose.ui.graphics.painter.BitmapPainter.onDraw(BitmapPainter.kt:93)
    at androidx.compose.ui.graphics.painter.Painter.draw-x_KDEd0(Painter.kt:212)
    at androidx.compose.ui.draw.PainterNode.draw(PainterModifier.kt:342)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:365)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:354)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:182)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:362)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:54)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:384)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:383)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2299)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:467)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:383)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:54)
    at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:209)
    at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:305)
    at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:246)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:349)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:182)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:362)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:354)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:182)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:362)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:354)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:922)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:174)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:362)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:54)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:384)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:383)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2299)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:467)
2024-01-25 10:43:54.276  7144-7144  AndroidRuntime          com.jan.testapp                      E      at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:383)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:54)
    at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:209)
    at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:305)
    at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:246)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:349)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:922)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:174)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:66)
    at androidx.compose.foundation.DrawOverscrollModifier.draw(AndroidOverscroll.kt:79)
    at androidx.compose.ui.node.BackwardsCompatNode.draw(BackwardsCompatNode.kt:349)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:365)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:354)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:182)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:362)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:54)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:384)
    at androidx.compose.ui.node.NodeCoordinator$invoke$1.invoke(NodeCoordinator.kt:383)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2299)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:467)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:383)
    at androidx.compose.ui.node.NodeCoordinator.invoke(NodeCoordinator.kt:54)
    at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:209)
    at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:305)
    at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:1138)
    at android.view.View.draw(View.java:23892)
    at android.view.View.updateDisplayListIfDirty(View.java:22756)
    at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
    at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
    at android.view.View.updateDisplayListIfDirty(View.java:22712)
    at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
    at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
    at android.view.View.updateDisplayListIfDirty(View.java:22712)
    at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
    at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
    at android.view.View.updateDisplayListIfDirty(View.java:22712)
    at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
    at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
    at android.view.View.updateDisplayListIfDirty(View.java:22712)
    at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:694)
    at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:700)
    at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:798)
2024-01-25 10:43:54.277  7144-7144  AndroidRuntime          com.jan.testapp                      E      at android.view.ViewRootImpl.draw(ViewRootImpl.java:4939)
    at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4643)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3822)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2465)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9305)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
    at android.view.Choreographer.doCallbacks(Choreographer.java:952)
    at android.view.Choreographer.doFrame(Choreographer.java:882)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
    at android.os.Handler.handleCallback(Handler.java:958)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8177)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

解决方案

1. 预压缩图片资源

将大尺寸图片压缩到合适分辨率(比如屏幕宽度的2倍以内),替换drawable目录中的原图。可使用Android Studio自带的图片压缩工具、Photoshop或在线压缩工具处理,确保图片尺寸适配应用界面需求。

2. 使用Coil加载图片(推荐)

Coil是Jetpack官方推荐的Compose图片加载库,能自动根据View尺寸压缩图片,避免内存溢出。

  • 添加依赖到模块级build.gradle.kts:
implementation("io.coil-kt:coil-compose:2.5.0")
  • 修改MenuDish中的Image实现:
@Composable
fun MenuDish(id: Int, dish: Dish) {
    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Image(
            painter = rememberAsyncImagePainter(
                model = ImageRequest.Builder(LocalContext.current)
                    .data(dish.imageResource)
                    .build()
            ),
            contentDescription = "菜品图片",
            modifier = Modifier.fillMaxWidth(),
            contentScale = ContentScale.FillWidth
        )
        // 文本部分保持不变
        Column (verticalArrangement = Arrangement.spacedBy(10.dp),
            modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
            Text(
                text = dish.name,
                style = MaterialTheme.typography.bodyMedium,
                modifier = Modifier.padding(8.dp)
            )
            Text(
                text = dish.description,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

3. 手动解码压缩图片

若不想使用第三方库,可通过BitmapFactory手动设置采样率压缩图片:

  • 创建图片压缩工具函数:
fun decodeSampledBitmapFromResource(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
    val options = BitmapFactory.Options().apply {
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, this)
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
        inJustDecodeBounds = false
    }
    return BitmapFactory.decodeResource(res, resId, options)
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {
        val halfHeight = height / 2
        val halfWidth = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize
}
  • 在Composable中使用:
@Composable
fun MenuDish(id: Int, dish: Dish) {
    val context = LocalContext.current
    val bitmap = remember(dish.imageResource) {
        decodeSampledBitmapFromResource(
            context.resources,
            dish.imageResource,
            1080,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:55:07