4

我有ViewPager的活动,在ViewPager适配器中,我为每个位置提供片段。片段监听器上的NullPointerException

一个示例片段是DebugFragment。我已经在下面写了源代码。

public class DebugFragment extends android.support.v4.app.Fragment { 

private OnFragmentInteractionListener mListener; 

public interface OnFragmentInteractionListener { 
    void onFragmentInteraction(int someValue); 
} 

public static DebugFragment newInstance() { 
    DebugFragment fragment = new DebugFragment(); 
    Bundle args = new Bundle(); 
    fragment.setArguments(args); 
    return fragment; 
} 

private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { 
    @Override 
    public void onReceive(Context context, Intent intent) { 
     mListener.onFragmentInteraction(0); 
    } 
}; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    LocalBroadcastManager.getInstance(getContext()).registerReceiver(mMessageReceiver, 
      new IntentFilter("com.android.example.INITIAL_REQUEST")); 
} 

@Override 
public void onDestroy() { 
    LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mMessageReceiver); 
    super.onDestroy(); 
} 

@Override 
public void onAttach(Context context) { 
    super.onAttach(context); 
    if (context instanceof OnFragmentInteractionListener) { 
     mListener = (OnFragmentInteractionListener) context; 
    } else { 
     throw new RuntimeException(context.toString() 
       + " must implement OnFragmentInteractionListener"); 
    } 
} 

@Override 
public void onDetach() { 
    super.onDetach(); 
    mListener = null; 
} 

@Override 
public void onResume() { 
    super.onResume(); 
    getUserData(); 
} 

public void getUserData() { 
// Inside Background Thread 
    if (getActivity() == null) { 
     return; 
    } 
    getActivity().runOnUiThread(new Runnable() { 
     @Override 
     public void run() { 
      mListener.onFragmentInteraction(0); // This line throws NPE 
     } 
    }); 
} 

我的活动执行如下。

public class DebugActivity extends AppCompatActivity implements 
    DebugFragment.OnFragmentInteractionListener { 

    // Other Activity Callback 

    @Override 
    public void onActivityResult(int requestCode, int resultCode, Intent data) { 
     super.onActivityResult(requestCode, resultCode, data); 
     if (requestCode == REQUEST_CODE) { 
      if (resultCode == Activity.RESULT_OK) { 
       DebugFragment debugFragment = ((DebugFragment) mViewPagerAdapter.getRegisteredFragment(2)); 
       if (debugFragment != null) { 
        debugFragment.getUserData(); 
       } 
      } 
     } 
    } 
} 

我打电话从片段中的onResume,广播接收器,活动的OnActivityResult我DebugFragment的getUserData。

有时我在getUserData中尝试访问FragmentListener(即mListener)时收到NullPointerException。 我想知道为什么?

因为我已经在检查Activity null。这是不够的。我还需要检查mListener的null吗?如果有人会向我解释活动不会为空的情况,但是我的mListener将为空,那将会很棒。我只将活动保持在肖像模式。

编辑

我的适配器代码

public abstract class TabPagerAdapter extends FragmentPagerAdapter { 

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

    public abstract View getTabView(int position); 
} 

public class SecondaryPagerAdapter extends TabPagerAdapter { 

    private static final int NUM_PAGES = 5; 

    private String tabTitles[] = new String[] { "Today", "New", "Calendar", "In-progress", "Invoices" }; 
    private int[] imageResId = { R.drawable.ic_tab_hired_pro, R.drawable.ic_tab_history, 
      R.drawable.ic_tab_today, R.drawable.ic_tab_inprogress, R.drawable.ic_tab_invoices }; 
    SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>(); 

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

    @Override 
    public Fragment getItem(int position) { 
     switch (position) { 
      case 0: 
      case 1: 
      case 3: 
       return ServiceRequestFragment.newInstance(tabTitles[position]); 
      case 2: 
       return DebugFragment.newInstance(); 
      case 4: 
       return InvoicesFragment.newInstance(); 
      default: 
       throw new RuntimeException("No fragment for this position"); 
     } 
    } 

    @Override 
    public int getCount() { 
     return NUM_PAGES; 
    } 

    @Override 
    public View getTabView(int position) { 
     CustomTab customTab = new CustomTab(DashBoardActivity.this); 
     customTab.bindWith(imageResId[position], tabTitles[position]); 
     return customTab; 
    } 

    @Override 
    public Object instantiateItem(ViewGroup container, int position) { 
     Fragment fragment = (Fragment) super.instantiateItem(container, position); 
     registeredFragments.put(position, fragment); 
     return fragment; 
    } 

    @Override 
    public void destroyItem(ViewGroup container, int position, Object object) { 
     registeredFragments.remove(position); 
     super.destroyItem(container, position, object); 
    } 

    public Fragment getRegisteredFragment(int position) { 
     return registeredFragments.get(position); 
    } 
} 

从我的活动我这样称呼它

mSecondaryPagerAdapter = new SecondaryPagerAdapter(getSupportFragmentManager()); 
mSecondaryPager = (ViewPager) findViewById(R.id.dashboard_pager); 
mSecondaryPager.setOffscreenPageLimit(4); 
mSecondaryPager.setAdapter(mSecondaryPagerAdapter); 
+0

您的片段是如何创建的?你能发布代码吗?当您的活动被系统杀死后,我怀疑您有重新启动您的应用程序时有NPE。是这样吗? – Elye

+0

@Elye我添加了片段创建的代码。我从viewPager适配器使用静态方法NewInstance()。我收到来自用户设备的崩溃报告。我无法确切地说出用户必须导致这次崩溃。你所说的可能就是这样。 – shubendrak

+0

也许你可以分享你的适配器的代码? – Elye

回答

11

当你调用getActivity().runOnUiThread(new Runnable() {}),这排入该Runnable到之后的UI线程上运行已经在UI线程中排队的所有其他内容,其中可能包括对onDestroy(),whi ch会将mListener设置为null。这意味着您的应用有可能在广播进入时关闭。虽然这在正常情况下应该不成问题,因为您在清除mListener之前注销了接收器,但可能Runnable已在此之后入列,这就是侦听器执行时为空的原因。

为了避免NPE,您应该在Runnable中检查mListener == null。然而,这仍然意味着回调会在你的Fragment被销毁之后被调度,并且正因为如此,你的Fragment实例才会被泄漏。最好的办法是创建一个Handler并将Runnable发布给它,而不是调用runOnUiThread()。然后,在onDestroy()中,调用mHandler.removeCallbacksAndMessages(null),它基本上清除队列,以便根本不会调用Runnable。

1
mMessageReceiver = new BroadcastReceiver() { 
    @Override 
    public void onReceive(Context context, Intent intent) { 
     if(mListener != null){ 
     mListener.onFragmentInteraction(0); 
     } 
    } 
}; 

当应用程序接收广播,可能是这个活动已经结束,当时

1

@Jschools有最好的答案。但作为替代,您也可以使用Eventbus而不是侦听器模式。事件总线将只广播事件,如果活动未激活,则不会有事件到达,并且不会投掷NPE。

0

根据文档onAttach在段落为时首先将附加到其上下文中。现在既然你没有直接实例化活动内部的片段,而是通过Adapter,你确定DebugFragment的onAttach(Context context)方法被调用,因为那是你将上下文分配给mListener的地方吗?

已经有报道没有调用onAttach(Context context)方法的情况。请参阅https://code.google.com/p/android/issues/detail?id=183358

您可以跳过片段生命周期方法,并在newInstance()中将mListener设置为正确。

public static DebugFragment newInstance(Context context) { 
    DebugFragment fragment = new DebugFragment(); 
    fragment.setListener(context) 
    Bundle args = new Bundle(); 
    fragment.setArguments(args); 
    return fragment; 
} 

private void setListener(Context context){ 
if (context.instanceof(OnFragmentInteractionListener) { 
     mListener = (OnFragmentInteractionListener) context; 
    } 
    else { 
     throw new RuntimeException(context.toString() 
       + " must implement OnFragmentInteractionListener"); 
    } 
}