我的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()
。回调实现是在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()
只在LayoutManager
中调用相同的name方法,该方法返回可空的:
@Nullable
public View getChildAt(int index) {
return this.mChildHelper != null ? this.mChildHelper.getChildAt(index) : null;
}
所以我的问题是:
回应意见:
,您可以在创建布局管理器实例的地方发布代码吗?
你有可能把整个代码都贴出来吗?包括如何设置布局管理器,以及如何在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
的构造函数中创建和设置的,如下所示:
(在回收视图的构造函数中:)
setLayoutManager(new MyLayoutManager(context));
然后,RecyclerView
通过Adapter添加子节点。视图和适配器的代码太长,不太可能导致问题,所以我认为我们可以跳过它们,除非我们有理由相信它们(有助于问题),那么我将粘贴代码或我们感兴趣的部分。
我感到困惑,因为我所做的只是调用一个本地方法:linearLayoutManager.findFirstCompletelyVisibleItemPosition();
,然后崩溃发生了(我们仍在研究如何复制它,但我怀疑这是一种竞赛条件,因为它只发生在少数用户身上)。也许是一些不寻常的情况触发了它,但这个方法不应该处理得更好吗?在上面粘贴的Android资源代码中,我们可以看到一个可为空的视图被传递给一个方法,该方法在不进行空检查的情况下调用该视图。我想知道这是否是谷歌需要做些什么的问题?另一方面,如果代码很好,我们有责任确保不发生不寻常的情况,那么怎么做呢?
发布于 2020-07-09 16:36:57
使用以下方法自己检查空值:
for (int i = 0; i < layoutManager.getChildCount(); i++) {
if (layoutManager.getChildAt(i) == null) {
return NO_POSITION;
}
}
int scrollPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
发布于 2020-07-09 16:44:51
这似乎是个bug,您可以尝试捕捉异常以避免崩溃。
static class MyLayoutManager extends LinearLayoutManager {
private boolean isScrollEnabled = true;
public MyLayoutManager(Context context) {
super(context);
}
@Override
public boolean canScrollVertically() {
return isScrollEnabled;
}
void setScrollEnabled(boolean isScrollEnabled) {
this.isScrollEnabled = isScrollEnabled;
}
public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public MyLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
//... Your code
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
}
}
https://stackoverflow.com/questions/59812576
复制相似问题