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,
相关产品推荐
相关产品推荐

