Android LinearLayoutManager создает исключение NullPointerException (ошибка в Android коде?) - PullRequest
0 голосов
/ 19 января 2020

У меня LinearLayoutManager установлено на RecyclerView. В моем коде я вызываю:

int firstFullyVisibleIndex = linearLayoutManager.findFirstCompletelyVisibleItemPosition();

и получаю этот cra sh от некоторых (небольшого процента) пользователей:

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;
    }

Обратите внимание на первые две строки для l oop , В случае cra sh, getChildAt() возвращает ноль и передает его 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, который возвращает nullable:

        @Nullable
        public View getChildAt(int index) {
            return this.mChildHelper != null ? this.mChildHelper.getChildAt(index) : null;
        }

Так что мой вопрос:

  1. Это ошибка Android код?
    • 1.1 Если да, что я должен сделать, чтобы обработать это в моем коде?
    • 1.2 Если нет, почему и что я должен сделать, чтобы обработать это в моем коде?

Адресные комментарии:

Не могли бы вы опубликовать код, где вы создаете экземпляр layoutmanager.

Возможно ли это для вам разместить весь код? В том числе, как вы настраиваете менеджер компоновки, и как вы устанавливаете дочерний вид внутри 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 добавляет детей через адаптер. Код представления и Adapter слишком длинный и вряд ли может способствовать возникновению проблемы, поэтому я думаю, что мы можем их пропустить, если у нас нет оснований полагать, что они это делают (внести свой вклад в проблему), тогда я вставлю код или часть, которая нас интересует.

Я чувствую растерянность, потому что все, что я сделал, это вызвал нативный метод: linearLayoutManager.findFirstCompletelyVisibleItemPosition();, затем произошло cra sh (мы все еще работаем над тем, как воспроизвести его, но я подозреваю, что это условие гонки, дающее это, только случилось на нескольких пользователях). Может быть, некоторые необычные случаи вызывают его, но не должен ли метод справиться с этим лучше? В коде ресурса Android, который я вставил выше, мы можем видеть, что обнуляемое представление передается методу, который вызывает представление без проверки нуля. Интересно, это проблема, что Google должен что-то сделать? С другой стороны, если код в порядке и наша обязанность - обеспечить, чтобы необычные случаи никогда не происходили, то как?

...