Андроид. Фрагмент getActivity() иногда возвращает null
в отчетах об ошибках консоли разработчика иногда я вижу отчеты с проблемой NPE. Я не понимаю, что не так с моим кодом. На эмуляторе и моем устройстве приложение работает хорошо без forcecloses, однако некоторые пользователи получают NullPointerException в классе фрагмента при вызове метода getActivity ().
активность
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
pager = (ViewPager) findViewById(R.id.pager);
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) adapter.getItem(0));
firstTask.execute();
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
@Override
public void onPageScrolled(int i, float v, int i1) {}
@Override
public void onPageScrollStateChanged(int i) {}
});
}
класс AsyncTask
public class FirstTask extends AsyncTask{
private TaskListener taskListener;
...
@Override
protected void onPostExecute(T result) {
...
taskListener.onTaskComplete(result);
}
}
фрагмент класс
public class FirstFragment extends Fragment immplements Taskable, TaskListener{
public FirstFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_view, container, false);
}
@Override
public void executeTask() {
FirstTask firstTask = new FirstTask(MyActivity.this);
firstTask.setTaskListener(this);
firstTask.execute();
}
@Override
public void onTaskComplete(T result) {
// NPE is here
Resources res = getActivity().getResources();
...
}
}
возможно, эта ошибка происходит, когда приложения возобновлены от фона. В этом случае, как я должен справиться с этой ситуацией должным образом?
7 ответов:
кажется, что я нашел решение своей проблемы. Очень хорошие объяснения даются здесь и здесь. Вот мой пример:
pulic class MyActivity extends FragmentActivity{ private ViewPager pager; private TitlePageIndicator indicator; private TabsAdapter adapter; private Bundle savedInstanceState; @Override public void onCreate(Bundle savedInstanceState) { .... this.savedInstanceState = savedInstanceState; pager = (ViewPager) findViewById(R.id.pager);; indicator = (TitlePageIndicator) findViewById(R.id.indicator); adapter = new TabsAdapter(getSupportFragmentManager(), false); if (savedInstanceState == null){ adapter.addFragment(new FirstFragment()); adapter.addFragment(new SecondFragment()); }else{ Integer count = savedInstanceState.getInt("tabsCount"); String[] titles = savedInstanceState.getStringArray("titles"); for (int i = 0; i < count; i++){ adapter.addFragment(getFragment(i), titles[i]); } } indicator.notifyDataSetChanged(); adapter.notifyDataSetChanged(); // push first task FirstTask firstTask = new FirstTask(MyActivity.this); // set first fragment as listener firstTask.setTaskListener((TaskListener) getFragment(0)); firstTask.execute(); } private Fragment getFragment(int position){ return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position)); } private String getFragmentTag(int position) { return "android:switcher:" + R.id.pager + ":" + position; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("tabsCount", adapter.getCount()); outState.putStringArray("titles", adapter.getTitles().toArray(new String[0])); } indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Fragment currentFragment = adapter.getItem(position); ((Taskable) currentFragment).executeTask(); } @Override public void onPageScrolled(int i, float v, int i1) {} @Override public void onPageScrollStateChanged(int i) {} });
основная идея в этом коде заключается в том, что при обычном запуске приложения вы создаете новые фрагменты и передаете их адаптеру. Когда вы возобновляете приложение fragment manager уже имеет экземпляр этого фрагмента, и вам нужно получить его из fragment manager и передать его в адаптер.
обновление
кроме того, это хорошая практика при использовании фрагментов для проверки isAdded перед вызовом getActivity (). Это помогает избежать исключения нулевого указателя при отсоединении фрагмента от действия. Например, действие может содержать фрагмент, который толкает асинхронную задачу. Когда задача завершена, onTaskComplete слушателя.
@Override public void onTaskComplete(List<Feed> result) { progress.setVisibility(View.GONE); progress.setIndeterminate(false); list.setVisibility(View.VISIBLE); if (isAdded()) { adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result); list.setAdapter(adapter); adapter.notifyDataSetChanged(); } }
если мы откроем фрагмент, нажмите задачу, а затем быстро нажмите назад, чтобы вернитесь к предыдущему действию, когда задача будет завершена, она попытается получить доступ к действию в onPostExecute (), вызвав метод getActivity (). Если действие уже отсоединено и этой проверки нет:
if (isAdded())
затем приложение аварийно завершает работу.
хорошо, я знаю, что этот вопрос на самом деле решен, но я решил поделиться своим решением для этого. Я создал абстрактный родительский класс для моего
Fragment
:public abstract class ABaseFragment extends Fragment{ protected IActivityEnabledListener aeListener; protected interface IActivityEnabledListener{ void onActivityEnabled(FragmentActivity activity); } protected void getAvailableActivity(IActivityEnabledListener listener){ if (getActivity() == null){ aeListener = listener; } else { listener.onActivityEnabled(getActivity()); } } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (aeListener != null){ aeListener.onActivityEnabled((FragmentActivity) activity); aeListener = null; } } @Override public void onAttach(Context context) { super.onAttach(context); if (aeListener != null){ aeListener.onActivityEnabled((FragmentActivity) context); aeListener = null; } } }
как вы можете видеть, я добавил слушателя так, всякий раз, когда мне нужно будет получить
Fragments
Activity
вместо стандартногоgetActivity()
, мне нужно позвонитьgetAvailableActivity(new IActivityEnabledListener() { @Override public void onActivityEnabled(FragmentActivity activity) { // Do manipulations with your activity } });
лучше всего избавиться от этого, чтобы сохранить ссылку на активность, когда
onAttach
вызывается и использует ссылку activity везде, где это необходимо, например@Override public void onAttach(Context context) { super.onAttach(context); mContext = context; } @Override public void onDetach() { super.onDetach(); mContext = null; }
редактировать, так как
onAttach(Activity)
амортизируется и сейчасonAttach(Context)
используется
не вызывайте методы внутри фрагмента, которые требуют getActivity () до onStart в Родительском действии.
private MyFragment myFragment; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); myFragment = new MyFragment(); ft.add(android.R.id.content, youtubeListFragment).commit(); //Other init calls //... } @Override public void onStart() { super.onStart(); //Call your Fragment functions that uses getActivity() myFragment.onPageSelected(); }
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // run the code making use of getActivity() from here }
я борьба с такого рода проблемой на некоторое время, и я думаю, что я придумал надежное решение.
это довольно трудно знать наверняка, что
this.getActivity()
не отвечаетnull
наFragment
, особенно если вы имеете дело с любым видом сетевого поведения, которое дает вашему коду достаточно времени для выводаActivity
ссылки.в решении ниже я объявляю небольшой класс управления под названием
ActivityBuffer
. По сути, этоclass
имеет дело с поддержанием надежной ссылки на владениеActivity
, и обещая выполнитьRunnable
s в пределах допустимогоActivity
контекст всякий раз, когда есть допустимая ссылка доступна. ЭлементRunnable
s запланированы для выполнения в потоке пользовательского интерфейса немедленно, еслиContext
доступно, в противном случае выполнение откладывается до этогоContext
готово./** A class which maintains a list of transactions to occur when Context becomes available. */ public final class ActivityBuffer { /** A class which defines operations to execute once there's an available Context. */ public interface IRunnable { /** Executes when there's an available Context. Ideally, will it operate immediately. */ void run(final Activity pActivity); } /* Member Variables. */ private Activity mActivity; private final List<IRunnable> mRunnables; /** Constructor. */ public ActivityBuffer() { // Initialize Member Variables. this.mActivity = null; this.mRunnables = new ArrayList<IRunnable>(); } /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */ public final void safely(final IRunnable pRunnable) { // Synchronize along the current instance. synchronized(this) { // Do we have a context available? if(this.isContextAvailable()) { // Fetch the Activity. final Activity lActivity = this.getActivity(); // Execute the Runnable along the Activity. lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } }); } else { // Buffer the Runnable so that it's ready to receive a valid reference. this.getRunnables().add(pRunnable); } } } /** Called to inform the ActivityBuffer that there's an available Activity reference. */ public final void onContextGained(final Activity pActivity) { // Synchronize along ourself. synchronized(this) { // Update the Activity reference. this.setActivity(pActivity); // Are there any Runnables awaiting execution? if(!this.getRunnables().isEmpty()) { // Iterate the Runnables. for(final IRunnable lRunnable : this.getRunnables()) { // Execute the Runnable on the UI Thread. pActivity.runOnUiThread(new Runnable() { @Override public final void run() { // Execute the Runnable. lRunnable.run(pActivity); } }); } // Empty the Runnables. this.getRunnables().clear(); } } } /** Called to inform the ActivityBuffer that the Context has been lost. */ public final void onContextLost() { // Synchronize along ourself. synchronized(this) { // Remove the Context reference. this.setActivity(null); } } /** Defines whether there's a safe Context available for the ActivityBuffer. */ public final boolean isContextAvailable() { // Synchronize upon ourself. synchronized(this) { // Return the state of the Activity reference. return (this.getActivity() != null); } } /* Getters and Setters. */ private final void setActivity(final Activity pActivity) { this.mActivity = pActivity; } private final Activity getActivity() { return this.mActivity; } private final List<IRunnable> getRunnables() { return this.mRunnables; } }
С точки зрения его реализации, мы должны позаботиться, чтобы применить жизнь методы совпадают с поведением, описанным выше Паван М:
public class BaseFragment extends Fragment { /* Member Variables. */ private ActivityBuffer mActivityBuffer; public BaseFragment() { // Implement the Parent. super(); // Allocate the ActivityBuffer. this.mActivityBuffer = new ActivityBuffer(); } @Override public final void onAttach(final Context pContext) { // Handle as usual. super.onAttach(pContext); // Is the Context an Activity? if(pContext instanceof Activity) { // Cast Accordingly. final Activity lActivity = (Activity)pContext; // Inform the ActivityBuffer. this.getActivityBuffer().onContextGained(lActivity); } } @Deprecated @Override public final void onAttach(final Activity pActivity) { // Handle as usual. super.onAttach(pActivity); // Inform the ActivityBuffer. this.getActivityBuffer().onContextGained(pActivity); } @Override public final void onDetach() { // Handle as usual. super.onDetach(); // Inform the ActivityBuffer. this.getActivityBuffer().onContextLost(); } /* Getters. */ public final ActivityBuffer getActivityBuffer() { return this.mActivityBuffer; } }
наконец, в любых областях в пределах вашего
Fragment
что расширяетBaseFragment
что вы не заслуживаете доверия о вызовеgetActivity()
, просто позвоните вthis.getActivityBuffer().safely(...)
и объявитьActivityBuffer.IRunnable
для задания!содержимое
void run(final Activity pActivity)
затем гарантируется выполнение вдоль потока пользовательского интерфейса.
Я знаю, что это старый вопрос, но я думаю, что я должен дать свой ответ на него, потому что моя проблема не была решена другими.
прежде всего: я динамически добавлял фрагменты с помощью fragmentTransactions. Во-вторых: мои фрагменты были изменены с помощью AsyncTasks (запросы БД на сервере). В-третьих: мой фрагмент не был создан при запуске активности В-четвертых: я использовал пользовательский экземпляр фрагмента "создать или загрузить его", чтобы получить переменную фрагмента. Четвертое: деятельность была воссоздана из-за изменения ориентации
проблема была в том, что я хотел "удалить" фрагмент из-за ответа на запрос, но фрагмент был неправильно создан непосредственно перед этим. Я не знаю, почему, вероятно, из-за того, что "фиксация" будет сделана позже, фрагмент еще не был добавлен, когда пришло время его удалить. Поэтому getActivity () возвращал null.
решение : 1) я должен был проверить, что я правильно пытался найти первый экземпляр фрагмента перед созданием нового один 2) я должен был поставить serRetainInstance (true) на этот фрагмент, чтобы сохранить его через изменение ориентации (не требуется backstack поэтому нет проблем) 3) вместо того, чтобы "воссоздавать или получать старый фрагмент" непосредственно перед "удалить его", я непосредственно помещаю фрагмент в начало действия. Создание экземпляра при запуске действия вместо "загрузки" (или создания экземпляра) переменной фрагмента перед ее удалением предотвратило проблемы getActivity.