首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Android LinearLayoutManager创建NullPointerException (NullPointerException代码中的bug?)

Android LinearLayoutManager创建NullPointerException (NullPointerException代码中的bug?)
EN

Stack Overflow用户
提问于 2020-01-19 17:28:25
回答 2查看 320关注 0票数 0

我的LinearLayoutManager设置为我的RecyclerView。在我的代码中,我调用:

代码语言:javascript
运行
复制
int firstFullyVisibleIndex = linearLayoutManager.findFirstCompletelyVisibleItemPosition();

并从一些(一小部分)用户那里获得这种崩溃:

代码语言:javascript
运行
复制
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中我看到:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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中实现的,它说:

代码语言:javascript
运行
复制
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方法,该方法返回可空的:

代码语言:javascript
运行
复制
@Nullable
public View getChildAt(int index) {
    return this.mChildHelper != null ? this.mChildHelper.getChildAt(index) : null;
}

所以我的问题是:

  1. 是安卓代码的一个bug吗?
    1. 1.1如果是,我应该如何在我的代码中处理它?
    2. 1.2,如果不是,为什么,我应该如何在我的
    3. 中处理它

回应意见:

,您可以在创建布局管理器实例的地方发布代码吗?

你有可能把整个代码都贴出来吗?包括如何设置布局管理器,以及如何在RecyclerView中设置子视图?

这里的LayoutManager是一个非常简单的LinearLayoutManager子类,它有一个额外的字段。整个守则如下:

代码语言:javascript
运行
复制
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的构造函数中创建和设置的,如下所示:

(在回收视图的构造函数中:)

代码语言:javascript
运行
复制
setLayoutManager(new MyLayoutManager(context));

然后,RecyclerView通过Adapter添加子节点。视图和适配器的代码太长,不太可能导致问题,所以我认为我们可以跳过它们,除非我们有理由相信它们(有助于问题),那么我将粘贴代码或我们感兴趣的部分。

我感到困惑,因为我所做的只是调用一个本地方法:linearLayoutManager.findFirstCompletelyVisibleItemPosition();,然后崩溃发生了(我们仍在研究如何复制它,但我怀疑这是一种竞赛条件,因为它只发生在少数用户身上)。也许是一些不寻常的情况触发了它,但这个方法不应该处理得更好吗?在上面粘贴的Android资源代码中,我们可以看到一个可为空的视图被传递给一个方法,该方法在不进行空检查的情况下调用该视图。我想知道这是否是谷歌需要做些什么的问题?另一方面,如果代码很好,我们有责任确保不发生不寻常的情况,那么怎么做呢?

EN

回答 2

Stack Overflow用户

发布于 2020-07-09 16:36:57

使用以下方法自己检查空值:

代码语言:javascript
运行
复制
for (int i = 0; i < layoutManager.getChildCount(); i++) {
    if (layoutManager.getChildAt(i) == null) {
        return NO_POSITION;
    }
}

int scrollPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
票数 0
EN

Stack Overflow用户

发布于 2020-07-09 16:44:51

这似乎是个bug,您可以尝试捕捉异常以避免崩溃。

代码语言:javascript
运行
复制
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();
        }
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59812576

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档