为什么我的RecyclerView出现空引用错误

19
我正在尝试在我的片段中使用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)

你能粘贴一下错误日志吗? - Benjamin RD
请检查我的更新。 - Andrew Butler
1
我不认为你的RecyclerView是null。看起来更像是RecyclerView内部的某些东西是null。可能是Google代码的一个bug。 - Karakuri
2
请确保您已经调用了setAdapter和setLayoutManager,即使您的数据列表为空。当您移除片段并且FragmentManager尝试与适配器中的某个视图进行交互时,就会发生这种情况。如果您没有调用setAdapter(也包括空列表)或setLayoutManager,则可能会出现此异常。 - frusso
5个回答

20
基本上,LayoutManager 在您的回收器完成处理之前被处理掉了。
来自 Android 源码的解释:
@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是否为空。

我为我正在开发的应用程序提供了一个非常糟糕的临时解决方案,但我不建议长期使用这个解决方案,因为它是一种曲线救国的方式。如果像我一样你有紧急截止日期,最好只是捕获空指针异常并忽略它。

我的临时解决方案只是创建了一个自定义的View,继承自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修补此问题后将其删除,因为这有点像黑客。

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

1
你明白他们会修复它并且什么时候吗?https://code.google.com/p/android/issues/detail?id=79244 - ichaki5748
1
顺便说一下,你的热修补并没有解决我的问题。 - Cocorico
@Cocorico 我也以为它对我不起作用,但后来我意识到我有两个布局显示了RecyclerView,而我只在其中一个上尝试了HotFixRecyclerView。 - Raphael Royer-Rivard
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Cocorico
非常感谢。最初我在引起此问题的片段中将RecyclerView更改为HotFixRecyclerView。直到我在我的片段中替换了所有RecyclerView实例到HotFixRecyclerView(一个调用另一个,因此它们以某种方式相互连接),才发现问题所在。我想知道是否没有正确地使用基本的RecyclerView对象进行回收利用。无论如何,非常感谢。 - vasanth
显示剩余4条评论

4
看起来,如果您在布局中有一个RecyclerView并在Activity中加载它,则必须为其设置一个LayoutManager,否则当尝试销毁它时,它将抛出此异常。我刚刚遇到了这个错误,以下是我解决它的方法:
我的活动显示一对TextView或RecyclerView中的元素,具体取决于项目数量:如果是项目集合,我使用RecyclerView;如果只有一个项目,则显示在TextView上,并将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。 - Juan José Melero Gómez
如何在onCreate()中将layoutManager分配给RecyclerView,当RecyclerView在onCreateView()中实例化时。 - Adnan Ali

3

因为已经膨胀了pref.xml文件。您应该删除它:

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

0
在我的情况下,我错误地将notifyDataSetChanged()应用于错误的适配器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接