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
Коли я відновляю свою програму з фонового fragmetn onCreate onResume, викликаного перед активом onCreate / onResume методом. Здається, якийсь відірваний фрагмент ще живий і намагається відновити.
Георгій Гобозов

1
у цьому рядку firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) повернути старий фрагмент, адаптер не видалити фрагменти належним чином
Георгій

9
Велика активність, до речі :), запитання, залишені коментарі та відповідь дано - все це робить одна людина! +1 для цих.
Призов

збережіть контекст (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) {}
 });

Основна ідея цього коду полягає в тому, що, нормально запускаючи додаток, ви створюєте нові фрагменти і передаєте їх адаптеру. Коли ви поновлюєте свій менеджер фрагментів програми, у нього вже є екземпляр цього фрагмента, і вам потрібно отримати його з менеджера фрагментів і передати його в адаптер.

ОНОВЛЕННЯ

Крім того, є хорошою практикою використання фрагментів для перевірки доданого до виклику 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 (), тут ми зберігаємо ту саму посилання.
Pawan Maheshwari

8
Google насправді рекомендує це, якщо вам потрібен ваш фрагмент, щоб поділитися подіями з активністю. developer.android.com/guide/components/fragments.html (шукайте "Створення зворотних викликів подій до активності")
Vering

6
ви можете додати метод onDetach, який анулює посилання на активність
опівночі

2
так, ініціалізуйте mActivity = null на методі onDetach, щоб звести нанівець цю посилання на активність.
Pawan Maheshwari

19
ніколи цього не роби. ви просочуєтеся вашою повною діяльністю (а з нею все дерево макету, з малюнком та ін.). Якщо ви getActivity()повернетеся до нуля, це тому, що ви більше не займаєтесь діями. Це брудне рішення.
njzk2

10

Не викликайте методів у фрагменті, які потребують 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();
}

Власне, у мене була подібна проблема, оскільки я починав завдання в конструкторі фрагментів. Дуже дякую.
Верховний Дельфін

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

}

Що стосується його реалізації, ми повинні дбати про те, щоб застосувати методи життєвого циклу, щоб збігатися з поведінкою, описаною вище Паваном М :

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

Чи можете ви додати приклад використання методу this.getActivityBuffer (). Safe (...).
fahad_sust

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

Чи можете ви детальніше розглянути свою відповідь, додавши трохи більше опису про рішення, яке ви надаєте?
абарізон

1

Я знаю, що це старе питання, але я думаю, що я повинен надати свою відповідь на нього, оскільки мою проблему не вирішили інші.

Перш за все: я динамічно додавав фрагменти, використовуючи fragmentTransaction. По-друге: мої фрагменти були модифіковані за допомогою AsyncTasks (БД-запити на сервері). По-третє: мій фрагмент не був ініційований під час початку діяльності Четверте: я використав власну інстанціювання фрагмента "створити або завантажити", щоб отримати змінну фрагмента. Четверте: діяльність відтворена через зміну орієнтації

Проблема полягала в тому, що я хотів "видалити" фрагмент через відповідь на запит, але фрагмент був створений неправильно безпосередньо раніше. Я не знаю, чому, ймовірно, через "фіксацію", яку було зроблено пізніше, фрагмент ще не був доданий, коли настав час його видалити. Тому getActivity () повертався null.

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


0

У Котліні ви можете спробувати таким чином обробити нульову умову getActivity ().

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

        //your code here

   }

Він перевірить, що активність є нульовою чи ні, а якщо ні, то виконати внутрішній код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.