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

194

В сообщениях об ошибках консоли разработчика иногда я вижу отчеты с проблемой 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
Когда я возобновляю свое приложение из фонового фрагмента onCreate, вызывается метод onResume перед действием метода onCreate / onResume. Кажется, какой-то отдельный фрагмент все еще жив и пытается возобновиться.
Георгий Гобозов
1
в этой строке firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adaptor.getItem (0) возвращает старый фрагмент, адаптер не удаляет фрагменты правильно
Георгий Гобозов
9
Кстати, отличная активность :) Вопрос задан, комментарии оставлены и дан ответ - все сделано одним человеком! +1 за это.
Призофф
сохраните Context (getActivity ()) в onCreateView (), так как это вызывается, когда представление воссоздается в фоновом случае.
Ша

Ответы:

123

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

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

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

ОБНОВИТЬ

Также рекомендуется использовать фрагменты для проверки 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()) 

тогда приложение вылетает.

Георгий Гобозов
источник
56
Это раздражает, хотя необходимость звонить isAdded()перед каждым доступом ... делает код ужасным.
Ixx
25
Кажется, нет большой разницы между наличием if(isAdded())илиif(getActivity() != null)
StackOverflowed
19

Хорошо, я знаю, что этот вопрос действительно решен, но я решил поделиться своим решением для этого. Я создал абстрактный родительский класс для моего 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
        }
    });
Пол Фриз
источник
Отличный ответ! должен быть помечен как правильный, поскольку это решает реальную проблему: в моем случае недостаточно проверить, что getActivity () не является нулевым, потому что я должен выполнить свою задачу, несмотря ни на что. Я использую это, и это работает отлично.
Хадас Камински
18

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

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

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

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

Паван Махешвари
источник
9
Фрагменты всегда сохраняют ссылку на родительскую активность и делают вас доступным с помощью метода getActivity (), здесь мы сохраняем ту же ссылку.
Паван Махешвари
8
Google фактически рекомендует это, если вам нужен ваш фрагмент, чтобы поделиться событиями с деятельностью. developer.android.com/guide/components/fragments.html (см. «Создание обратных вызовов событий для действия»)
Vering
6
Возможно, вы захотите добавить метод onDetach, который аннулирует ссылку на действие
полночь
2
да, инициализируйте mActivity = null в методе onDetach, чтобы аннулировать эту ссылку активности.
Паван Махешвари
19
никогда не делай этого. Вы пропускаете свою полную активность (и вместе с ней все дерево макетов, с рисованными объектами и т. д.). Если getActivity()возвращает ноль, то это потому, что вы больше не в активности. Это грязный обходной путь.
njzk2
10

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

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

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

В приведенном ниже решении я объявляю небольшой класс управления, называемый ActivityBuffer. По сути, это classкасается поддержания надежной ссылки на собственника Activityи обещания выполнить Runnables в допустимом Activityконтексте, когда есть допустимая ссылка. В Runnables запланированы для выполнения на тему UI немедленно , если 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;
    }

}

С точки зрения его реализации, мы должны позаботиться о применении методов жизненного цикла, чтобы они совпали с поведением, описанным выше Pawan M :

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) будет гарантированно исполнено в потоке пользовательского интерфейса.

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

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
источник
Можете ли вы добавить пример использования метода this.getActivityBuffer (). Safe (...).
fahad_sust
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Моханрай Баласубраманиам
источник
Не могли бы вы более подробно изложить свой ответ, добавив чуть больше описания предлагаемого вами решения?
abarisone
1

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

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

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

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

Feuby
источник
0

В Kotlin вы можете попробовать этот способ для обработки нулевого условия getActivity ().

   activity.let { // activity == getActivity() in java

        //your code here

   }

Он проверит активность на ноль или нет, а если не ноль, то выполнит внутренний код.

Сэчин
источник