4

У меня есть активность с ViewPager, в ViewPager Adapter Я предоставляю фрагмент для каждой позиции.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(); 
       } 
      } 
     } 
    } 
} 

Я называю getUserData моего DebugFragment из onResume фрагмента, BroadcastReceiver, OnActivityResult деятельности.

Когда-то я получаю исключение NullPointerException в getUserData при попытке доступа к FragmentListener i.e mListener. Я хочу знать Почему?

Поскольку я уже проверяю на действие null. Разве этого недостаточно. Должен ли я также проверять нулевое значение mListener? Было бы здорово, если кто-нибудь объяснит мне случай, когда активность не будет нулевой, но мой 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 Я добавил код для создания фрагментов. Я использую статический метод NewInstance() из моего адаптера viewPager. Я получаю сообщение о сбоях с устройства пользователя. Я не могу точно сказать, что пользователь должен вызвать этот крах. То, что вы сказали, может быть так. – shubendrak

+0

Возможно, вы можете поделиться своим кодом адаптера? – Elye

ответ

11

При вызове getActivity().runOnUiThread(new Runnable() {}), это ставит в очередь, что Runnable для запуска в потоке пользовательского интерфейса после все остальное, которое уже установлено в потоке пользовательского интерфейса, которое может включать в себя вызовы onDestroy(), whi ch установил бы mListener null. Это означает, что ваше приложение может закрываться во время трансляции. Хотя это не должно быть проблемой в обычном случае, потому что вы отменили регистрацию своего получателя до очистки mListener, возможно, что Runnable был установлен в очередь после этого, поэтому слушатель имеет значение null, когда он выполняется.

Чтобы избежать NPE, вы должны проверить на mListener == null в Runnable. Однако это все равно означает, что обратный вызов запланирован после уничтожения вашего фрагмента, и из-за этого ваш экземпляр фрагмента просочился. Лучше всего создать 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 вместо шаблона слушателя. Eventbus будет транслировать событие, и если активность не активна, событие не поступит, и никакой NPE не будет выброшен.

0

В соответствии с документацией onAttach вызывается, когда фрагмент вначале прикреплен к его контексту. Теперь, поскольку вы напрямую не создаете экземпляр фрагмента внутри действия, но через адаптер, вы уверены, что метод onAttach (контекстный контекст) DebugFragment вызывается, потому что именно там вы назначили контекст для mListener?

Сообщалось о случаях, когда метод onAttach (контекстный контекст) не вызывается. См. https://code.google.com/p/android/issues/detail?id=183358

Вы пропустили методы жизненного цикла фрагментов вместе и установите mListener прямо в newInstance().

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