Android. Фрагмент getActivity() иногда возвращает null

В отчетах об ошибках консоли разработчика иногда появляются отчеты с проблемой NPE. Я не понимаю, что не так с моим кодом. В эмуляторе и приложении моего устройства хорошо работает без принудительной блокировки, однако некоторые пользователи получают 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();
        ...
    }
}

Возможно, эта ошибка возникает, когда приложения возобновляются с фона. В этом случае, как я должен правильно обрабатывать эту ситуацию?

Ответ 1

Кажется, я нашел решение своей проблемы. Очень хорошие объяснения даны здесь и здесь. Вот мой пример:

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

Основная идея этого кода заключается в том, что при запуске приложения обычно создаются новые фрагменты и передаются в адаптер. Когда вы возобновляете свой менеджер фрагментов приложения, этот экземпляр фрагмента уже нужен, и вам нужно получить его из диспетчера фрагментов и передать его в адаптер.

UPDATE

Кроме того, это хорошая практика при использовании фрагментов для проверкиAdAdded перед вызовом 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()) 

то приложение сработает.

Ответ 2

Хорошо, я знаю, что этот вопрос действительно решен, но я решил поделиться своим решением для этого. Я создал абстрактный родительский класс для моего 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
        }
    });

Ответ 3

Лучше всего избавиться от этого - сохранить ссылку на активность при onAttach и использовать ссылку на активность, где это необходимо, например

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;
}

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

Отредактировано, так как onAttach(Activity) обесценивается и теперь используется onAttach(Context)

Ответ 4

Не вызывайте методы внутри Фрагмента, для которых требуется getActivity(), пока он не будет включен в родительскую активность.

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

Ответ 5

Некоторое время я боролся с такой проблемой и думаю, что нашел надежное решение.

Довольно трудно точно знать, что this.getActivity() не будет возвращать null для Fragment, особенно если вы имеете дело с каким-либо поведением сети, которое дает вашему коду достаточно времени для отзыва ссылок Activity.

В приведенном ниже решении я объявляю небольшой класс управления, называемый ActivityBuffer. По сути, этот class имеет дело с поддержанием надежной ссылки на владелец Activity и обещанием выполнить Runnable в допустимом контексте Activity когда есть действительная ссылка. Runnable планируются для выполнения в потоке пользовательского интерфейса немедленно, если 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 an available Context. */
    public interface IRunnable {
        /** Executes when there 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 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 ready to receive a valid reference.
                this.getRunnables().add(pRunnable);
            }
        }
    }

    /** Called to inform the ActivityBuffer that there 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 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) гарантированно будет выполнено в void run(final Activity pActivity) пользовательского интерфейса.

ActivityBuffer может затем использоваться следующим образом:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);

Ответ 6

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}

Ответ 7

Я знаю, что это старый вопрос, но я думаю, что должен ответить на него, потому что моя проблема не была решена другими.

в первую очередь: я динамически добавлял фрагменты с использованием фрагментарных транзакций. Во-вторых: мои фрагменты были изменены с использованием AsyncTasks (запросы БД на сервере). В-третьих: мой фрагмент не был создан при начале работы В-четвертых: я использовал пользовательский экземпляр экземпляра "создайте или загрузите его", чтобы получить переменную фрагмента. Четвертое: активность была воссоздана из-за изменения ориентации

Проблема заключалась в том, что я хотел "удалить" фрагмент из-за ответа на запрос, но фрагмент был неправильно создан раньше. Я не знаю, почему, вероятно, из-за того, что "commit" будет сделано позже, фрагмент еще не был добавлен, когда пришло время его удалить. Поэтому getActivity() возвращал значение null.

Решение: 1) Я должен был проверить, что я правильно пытался найти первый экземпляр фрагмента, прежде чем создавать новый 2) Мне пришлось поместить serRetainInstance (true) на этот фрагмент, чтобы сохранить его с помощью изменения ориентации (без необходимости backstack, поэтому никаких проблем) 3) Вместо "воссоздания или получения старого фрагмента" непосредственно перед "удалением" я непосредственно помещаю фрагмент в начало действия. Выполняя его при запуске активности вместо "загрузки" (или создания экземпляра) переменной фрагмента перед удалением, это предотвращает проблемы с getActivity.