У меня 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;
}
Так что мой вопрос:
- Это ошибка 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 должен что-то сделать? С другой стороны, если код в порядке и наша обязанность - обеспечить, чтобы необычные случаи никогда не происходили, то как?