android - Android LinearLayoutManager 创建 NullPointerException(Android 代码中的错误?)
问题描述
我LinearLayoutManager
有一套我的RecyclerView
. 在我的代码中,我调用:
int firstFullyVisibleIndex = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
并从一些(一小部分)用户那里得到这个崩溃:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParams android.view.View.getLayoutParams()' on a null object reference
at androidx.recyclerview.widget.RecyclerView$LayoutManager$2.getChildStart(RecyclerView.java:7381)
at androidx.recyclerview.widget.ViewBoundsCheck.findOneViewWithinBoundFlags(ViewBoundsCheck.java:223)
at androidx.recyclerview.widget.LinearLayoutManager.findOneVisibleChild(LinearLayoutManager.java:1941)
at androidx.recyclerview.widget.LinearLayoutManager.findFirstCompletelyVisibleItemPosition(LinearLayoutManager.java:1874)
...
所以我查看了Android源代码。在LinearLayoutManager
我看到:
public int findFirstCompletelyVisibleItemPosition() {
View child = this.findOneVisibleChild(0, this.getChildCount(), true, false);
return child == null ? -1 : this.getPosition(child);
}
View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) {
this.ensureLayoutState();
int preferredBoundsFlag = false;
int acceptableBoundsFlag = 0;
short preferredBoundsFlag;
if (completelyVisible) {
preferredBoundsFlag = 24579;
} else {
preferredBoundsFlag = 320;
}
if (acceptPartiallyVisible) {
acceptableBoundsFlag = 320;
}
return this.mOrientation == 0 ? this.mHorizontalBoundCheck.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, acceptableBoundsFlag) : this.mVerticalBoundCheck.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, acceptableBoundsFlag);
}
最后一行this.mHorizontalBoundCheck.findOneViewWithinBoundFlags()
引导我ViewBoundsCheck
:
View findOneViewWithinBoundFlags(int fromIndex, int toIndex, int preferredBoundFlags, int acceptableBoundFlags) {
int start = this.mCallback.getParentStart();
int end = this.mCallback.getParentEnd();
int next = toIndex > fromIndex ? 1 : -1;
View acceptableMatch = null;
for (int i = fromIndex; i != toIndex; i += next) {
View child = this.mCallback.getChildAt(i);
int childStart = this.mCallback.getChildStart(child);
int childEnd = this.mCallback.getChildEnd(child);
this.mBoundFlags.setBounds(start, end, childStart, childEnd);
if (preferredBoundFlags != 0) {
this.mBoundFlags.resetFlags();
this.mBoundFlags.addFlags(preferredBoundFlags);
if (this.mBoundFlags.boundsMatch()) {
return child;
}
}
if (acceptableBoundFlags != 0) {
this.mBoundFlags.resetFlags();
this.mBoundFlags.addFlags(acceptableBoundFlags);
if (this.mBoundFlags.boundsMatch()) {
acceptableMatch = child;
}
}
}
return acceptableMatch;
}
注意 for 循环中的前两行。在崩溃情况下,getChildAt()
返回 null 并将其传递给getChildStart()
. 回调实现是 in RecyclerView.LayoutManager
,它说:
public View getChildAt(int index) {
return LayoutManager.this.getChildAt(index);
}
public int getChildStart(View view) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
return LayoutManager.this.getDecoratedLeft(view) - params.leftMargin;
}
可以看到,getChildStart()
需要一个非空参数view
,否则会产生NullPointerException。并且getChildAt()
简单地调用 in 中的同名方法LayoutManager
,该方法返回可为空的:
@Nullable
public View getChildAt(int index) {
return this.mChildHelper != null ? this.mChildHelper.getChildAt(index) : null;
}
所以我的问题是:
- 这是Android代码的错误吗?
- 1.1 如果是,我应该怎么做才能在我的代码中处理它?
- 1.2 如果不是,为什么,我应该怎么做才能在我的代码中处理它?
地址评论:
您能否在创建布局管理器实例的位置发布代码。
您可以发布整个代码吗?包括如何设置布局管理器,以及在 RecyclerView 中设置子视图的方式?
这LayoutManager
是一个非常简单的子类,LinearLayoutManager
带有一个额外的字段。整个代码在这里:
static class MyLayoutManager extends LinearLayoutManager {
private boolean isScrollEnabled = true;
MyLayoutManager(Context context) {
super(context);
}
@Override
public boolean canScrollVertically() {
return isScrollEnabled;
}
void setScrollEnabled(boolean isScrollEnabled) {
this.isScrollEnabled = isScrollEnabled;
}
}
它的实例是在 的构造函数中创建和设置的RecyclerView
,如下所示:
(在 RecyclerView 的构造函数内部:)
setLayoutManager(new MyLayoutManager(context));
然后RecyclerView
通过适配器添加孩子。视图和适配器的代码太长,不太可能导致问题,所以我认为我们可以跳过它们,除非我们有理由相信它们会(导致问题),然后我会粘贴代码或我们感兴趣的部分。
我感到困惑,因为我所做的只是调用本地方法:linearLayoutManager.findFirstCompletelyVisibleItemPosition();
,然后发生崩溃(我们仍在研究如何重现它,但我怀疑这是一个竞争条件,它只发生在少数用户身上)。也许一些不寻常的情况会触发它,但该方法不应该更好地处理它吗?在我上面粘贴的 Android 资源代码中,我们可以看到一个可为空的视图被传递给一个方法,该方法在视图上调用而不检查空值。我想知道这是否是谷歌需要做些什么的问题?另一方面,如果代码没问题,并且我们有责任确保不发生异常情况,那怎么办?
解决方案
使用以下方法自己检查空值:
for (int i = 0; i < layoutManager.getChildCount(); i++) {
if (layoutManager.getChildAt(i) == null) {
return NO_POSITION;
}
}
int scrollPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
推荐阅读
- arrays - Vue js 为每个不间断的收到消息集在最后一个元素放置一个图标
- selenium - 有没有办法使用 Soft Assert 来验证文本是否包含某个字符串
- php - ERR TOO MANY REDIRECTS - 根据用户选择的城市重定向用户时
- c# - 嵌套 DataGrid 的 SelectedValue 绑定不起作用,而 ItemsSource 起作用
- sql - 从 SQL 查询向 BigQuery 表添加多个分区列
- amazon-redshift - 将动态帧写入 Redshift 时,AWS Glue 中出现“文件准备就绪”错误
- angular - 角度材料表错误:找不到具有 id 的列
- flutter - 除了底部菜单之外,是否有内置的 BottomNavigationBar 替代方案?
- c++ - 我可以使用条件运算符来初始化 C 风格的字符串文字吗?
- json - 在 ASP.NET MVC 中执行创建、读取、更新、删除操作的最佳方法?