首页 > 解决方案 > 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;
}

所以我的问题是:

  1. 这是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 资源代码中,我们可以看到一个可为空的视图被传递给一个方法,该方法在视图上调用而不检查空值。我想知道这是否是谷歌需要做些什么的问题?另一方面,如果代码没问题,并且我们有责任确保不发生异常情况,那怎么办?

标签: androidnullpointerexception

解决方案


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

for (int i = 0; i < layoutManager.getChildCount(); i++) {
    if (layoutManager.getChildAt(i) == null) {
        return NO_POSITION;
    }
}

int scrollPosition = layoutManager.findFirstCompletelyVisibleItemPosition();

推荐阅读