Jetpack Compose实现无限轮播时触发NullPointerException异常
问题描述
我正尝试基于循环列表实现方案的修改版本,开发可无限滚动的卡片轮播组件,实现代码如下:
@Composable fun CircularList( items: List<String>, modifier: Modifier = Modifier, onItemClick: (String) -> Unit ) { val listState = rememberLazyListState(Int.MAX_VALUE / 2) LazyColumn( state = listState, modifier = modifier ) { items( count = Int.MAX_VALUE, itemContent = { val index = it % items.size // line X Text(text = items[index]) } ) } }
代码运行时直接崩溃,抛出空指针异常,异常栈信息如下:
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter it at androidx.compose.foundation.lazy.LazyListItemProviderImplKt$generateKeyToIndexMap$1$1.invoke(Unknown Source:2)
测试时发现一个规律:将初始索引设置为500时会触发崩溃,但设置为50时可以正常运行:
val listState = rememberLazyListState(500) // 崩溃 val listState = rememberLazyListState(50) // 正常运行
已知调试信息:
- 崩溃发生在标注为line X的代码行执行之前,传入的items列表非空,测试时列表长度通常为2-6个元素
- 尝试改用
scrollToItem()方法设置初始索引,仍然会抛出相同异常
崩溃原因
这个问题和你写的列表项逻辑、传入的列表数据没有关系,是Jetpack Compose Lazy组件的边界bug:
- 调用
items(count = Int.MAX_VALUE)时如果不手动指定key参数,Lazy列表会默认把每个item的位置索引作为唯一key使用 - Compose内部维护key与索引映射表的逻辑存在整型溢出缺陷:当初始滚动位置的数值较大时,计算映射表数组下标的逻辑会算出非法的负数下标,访问到数组中未初始化的null值,null被传给声明为非空类型的参数时,就会触发你看到的非空校验空指针异常
- 测试时初始值设为50能正常运行、设为500就崩溃,本质是50这个数值还没触发内部计算的溢出阈值,500已经踩中了bug触发点。不管是通过
rememberLazyListState传初始索引,还是后续调用scrollToItem()跳到大索引位置,只要没手动指定key,都可能触发这个问题。
修复方案
不要依赖默认的索引key,手动给items传入自定义key即可,直接用计算后的真实数据索引作为key就行:
items( count = Int.MAX_VALUE, key = { position -> position % items.size }, // 手动指定key,避开大索引触发的内部bug itemContent = { val index = it % items.size Text(text = items[index]) } )
额外说明:实现无限轮播没必要把初始位置设为Int.MAX_VALUE / 2这么极端的值,一般设10000左右的初始位置,普通用户连续滑几分钟都碰不到边界,完全能满足循环滚动的使用需求,过大的索引值反而容易触发框架内部各类边界逻辑问题。
内容的提问来源于stack exchange,提问作者Siim Puniste
相关产品推荐
相关产品推荐

