【问题标题】:Why am I getting a null reference on my RecyclerView为什么我在 RecyclerView 上得到一个空引用
【发布时间】:2014-12-29 10:10:36
【问题描述】:

我正在尝试在我的片段中使用 RecyclerView。它在第一个选项卡上显示得很好,但是当我滑动到第二个选项卡然后回到第一个选项卡时,我收到以下错误:

java.lang.NullPointerException: 尝试调用虚方法 'void android.support.v7.widget.RecyclerView$LayoutManager.stopSmoothScroller()' 在空对象引用上

有谁知道我为什么会收到这个错误?似乎在我碎片切换之前我错过了一个电话,但我无法弄清楚。

片段的寻呼机适配器:

public class PagerAdapter extends FragmentPagerAdapter {

    private final String[] TITLES = {"Header 0", "Header 1" };

    public PagerAdapter(FragmentManager fm){
        super(fm);
    }

    @Override
    public CharSequence getPageTitle(int position){
        return TITLES[position];
    }

    @Override
    public int getCount() {
        return TITLES.length;
    }

    @Override
    public Fragment getItem(int position){
        switch(position){
            case 0:
                return new FragmentOne();
            case 1:
                return new FragmentTwo();
            default:
                return new FragmentOne();
        }
    }
}

FragmentOne 和 FragmentTwo 使用我创建的样板类:

public class FragmentOne extends Base {
    ... code for displaying content in the RecyclerView. Just contains my custom Adapter for the RecyclerView ...
}

基地:

public abstract class Base extends Fragment {

    public View mView;
    public RecyclerView mDataView;
    public ProgressBar mProgressBar;
    public Context mContext;
    private RecyclerView.LayoutManager mLayoutManager;

    public Base() { }

    @Override
    public View onCreateView(LayoutInflater _i, ViewGroup _vg, Bundle savedInstanceBundle){
        mView = _i.inflate(R.layout.f_base, null);
        mDataView = (RecyclerView) mView.findViewById(R.id.data);
        mProgressBar = (ProgressBar)mView.findViewById(R.id.loading);
        mLayoutManager = new LinearLayoutManager(mContext);
        mDataView.setLayoutManager(mLayoutManager);
        mContext = this.getActivity();
        return mView;
    }

    @Override
    public void onStart() {
        super.onStart();
        init();
        doWork();
    }

    public abstract void init();

    public abstract void doWork();

}

编辑

错误堆栈跟踪:

11-02 12:49:44.171    3708-3708/com.nitrox.digitune E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.nitrox.digitune, PID: 3708
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView$LayoutManager.stopSmoothScroller()' on a null object reference
            at android.support.v7.widget.RecyclerView.stopScrollersInternal(RecyclerView.java:1159)
            at android.support.v7.widget.RecyclerView.stopScroll(RecyclerView.java:1151)
            at android.support.v7.widget.RecyclerView.onDetachedFromWindow(RecyclerView.java:1356)
            at android.view.View.dispatchDetachedFromWindow(View.java:13441)
            at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2837)
            at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2834)
            at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2834)
            at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4163)
            at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4136)
            at android.view.ViewGroup.removeView(ViewGroup.java:4068)
            at android.support.v4.view.ViewPager.removeView(ViewPager.java:1326)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1045)
            at android.support.v4.app.FragmentManagerImpl.detachFragment(FragmentManager.java:1280)
            at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:724)
            at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
            at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:486)
            at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:141)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:1073)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:919)
            at android.support.v4.view.ViewPager$3.run(ViewPager.java:249)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
            at android.view.Choreographer.doCallbacks(Choreographer.java:580)
            at android.view.Choreographer.doFrame(Choreographer.java:549)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

【问题讨论】:

  • 可以粘贴错误日志吗?
  • 请检查我的更新。
  • 我不认为您的 RecyclerView 为空。它看起来更像是其他东西inside RecyclerView 为空。可能是 Google 代码的错误。
  • 如果您的数据列表为空,请确保您也调用了 setAdapter 和 setLayoutManager。当您删除片段并且 FragmentManager 尝试与适配器内的某些视图进行交互时,会发生这种情况。如果你没有调用 setAdapter (也有空列表)或 setLayoutManager 你可以有这个异常

标签: java android android-fragments


【解决方案1】:

基本上,LayoutManager 在您的回收站完成之前就被丢弃了。

来自安卓源:

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mItemAnimator != null) {
        mItemAnimator.endAnimations();
    }
    mFirstLayoutComplete = false;
    stopScroll();
    mIsAttached = false;
    if (mLayout != null) {
        mLayout.onDetachedFromWindow(this, mRecycler);
    }
    removeCallbacks(mItemAnimatorRunner);
}

问题源于 stopScroll 并尝试调用 mLayout.stopSmoothScroller();不检查 mLayout 是否为空。

我为我一直在开发的应用程序提供了一个非常老套的热修复程序,但我不建议将其用于长期解决方案,因为它非常麻烦。如果像我一样,你的期限很紧,最好只捕获空指针异常并忽略它。

我的热修复只是创建一个扩展 RecyclerView 的自定义视图:

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;

public class HotFixRecyclerView extends RecyclerView
{
    public HotFixRecyclerView( Context context )
    {
        super( context );
    }

    public HotFixRecyclerView( Context context, AttributeSet attrs )
    {
        super( context, attrs );
    }

    public HotFixRecyclerView( Context context, AttributeSet attrs, int defStyle )
    {
        super( context, attrs, defStyle );
    }

    @Override
    public void stopScroll()
    {
        try
        {
            super.stopScroll();
        }
        catch( NullPointerException exception )
        {
            /**
             *  The mLayout has been disposed of before the 
             *  RecyclerView and this stops the application 
             *  from crashing.
             */
        }
    }
}

然后将所有引用从 RecyclerView 更改为 HotFixRecyclerView。如果您确实使用它,请在 Android 修复此问题后将其删除,因为它有点像 hack。

  • 如果您使用 recylerview inn XML,请不要忘记相应地更改您的 XML 文件以使用 com.your.package.HotFixRecyclerView 而不是 android.support.v7.widget.RecyclerView

【讨论】:

  • 感谢代码,但我最终还是回到了使用我之前使用的 Listview 和 viewholder 模式。我只是想用 Recyclerview 弄脏我的手,因为它是一个很好的小部件。不过,我会接受作为答案。
  • 你知道他们会修复它吗?什么时候修复? code.google.com/p/android/issues/detail?id=79244
  • 据我所知,它仍然标记为“状态:FutureRelease”
  • 我也有这个问题,但只有我的一个应用程序。为什么 ?他们什么时候会解决这个问题?
  • 顺便说一句,你的修补程序没有解决我的问题。
【解决方案2】:

似乎如果你的布局中有一个 RecyclerView 并且你在 Activity 中加载它,则必须为其设置一个 LayoutManager,否则它会在尝试销毁它时抛出此异常。我刚刚遇到了这个错误,这就是我解决它的方法:

我的活动根据项目的数量在一对 TextView 或 RecyclerView 中显示元素:如果是项目集合,我使用 RecyclerView;如果只有一项,它会显示在 TextViews 上,我将 RecyclerView 的可见性设置为 GONE

然后,在第一种情况下,我打电话给myRecyclerView.setLayoutManager(myLayoutManager),但在另一种情况下,我没有,因为我不打算使用它。在这两种情况下,包含 RecyclerView 的 Activity 都完美显示。但是在关闭它时,在第二种情况下抛出了异常,因为 RecyclerView 虽然没有使用,但存在并且在尝试处理它时,它似乎不能。所以我只是在这两种情况下都分配了 LayoutManager,虽然我没有使用它,这解决了这个问题。

这是我的 Activity 中的代码:

ItemsAdapter adapter;
RecyclerView myRecyclerView = findViewById(R.id.my_recycler_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);

if (items.size() > 1){
    adapter = new ItemsAdapter(this, R.layout.item_layout, items);
    myRecyclerView.setLayoutManager(layoutManager);
    myRecyclerView.setAdapter(adapter);
} else {
    tv_field1.setText(items.get(0).getField1());
    tv_field2.setText(items,get(0).getField2());
    myRecyclerView.setVisibility(View.GONE);

    //This is what I had to add
    myRecyclerView.setLayoutManager(layoutManager);
}

【讨论】:

    【解决方案3】:

    阅读 Isaak 发布的错误,最好的解决方案似乎是在视图膨胀后立即分配布局管理器。

    就我而言,我只需在 onCreate 上分配它即可解决问题:

    mLayoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(mLayoutManager);
    

    【讨论】:

    • 你说得对,如果 RecyclerView 膨胀,你必须分配 LayoutManager。
    • 当 RecyvlerView 在 onCreateView() 中实例化时,如何在 onCreate() 中将 layoutManager 分配给 RecyclerView
    【解决方案4】:

    因为它已经膨胀了 pref.xml 。你应该删除这个:

        @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mRootView = inflater.inflate(R.layout.xxx, container, false);
        return mRootView;
    }
    

    【讨论】:

      【解决方案5】:

      在我的情况下,我在错误的适配器上使用了 notifyDataSetChanged()

      【讨论】: