Android. Fragment getActivity() czasami zwraca null

W raportach błędów Konsoli programisty czasami widzę raporty z problemem NPE. Nie rozumiem, co jest nie tak z moim kodem. Na emulatorze i moim urządzeniu aplikacja działa dobrze bez forcecloses, jednak niektórzy użytkownicy otrzymują NullPointerException w klasie fragment po wywołaniu metody getActivity ().

Aktywność

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

Klasa AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

Klasa Fragment

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

Być może ten błąd występuje, gdy aplikacje wznowione z tła. W tym przypadku jak czy należy poradzić sobie z tą sytuacją prawidłowo?

Author: scohe001, 2012-07-24

7 answers

Wygląda na to, że znalazłem rozwiązanie mojego problemu. Bardzo dobre wyjaśnienia podane są tutaj i tutaj . Oto mój przykład:

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

Główną ideą tego kodu jest to, że podczas normalnego uruchamiania aplikacji tworzysz nowe fragmenty i przekazujesz je do adaptera. Podczas wznawiania aplikacji Menedżer fragmentów ma już instancję tego fragmentu i musisz ją pobrać z menedżera fragmentów i przekazać do adapter.

UPDATE

Jest to również dobra praktyka, gdy używa się fragmentów do sprawdzania isAdded przed wywołaniem getActivity (). Pomaga to uniknąć wyjątku wskaźnika null, gdy fragment jest odłączony od aktywności. Na przykład czynność może zawierać fragment, który popycha zadanie asynchroniczne. Po zakończeniu zadania wywoływany jest słuchacz 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();
    }

}

Jeśli otworzymy fragment, popchniemy zadanie, a następnie szybko naciśniemy Wstecz, aby powrócić do poprzedniego activity, po zakończeniu zadania spróbuje uzyskać dostęp do activity w onpostexecute() przez wywołanie metody getActivity (). Jeśli aktywność jest już odłączona, a tego sprawdzenia nie ma:

if (isAdded()) 

Następnie aplikacja ulega awarii.

 110
Author: Georgy Gobozov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:47

Ok, wiem, że to pytanie jest rzeczywiście rozwiązane, ale postanowiłem podzielić się moim rozwiązaniem na to. Stworzyłem abstrakcyjną klasę rodzica dla mojego 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;
        }
    }
}

Jak widzisz, dodałem słuchacza więc, kiedy będę potrzebował Fragments Activity zamiast standardowego getActivity(), będę musiał zadzwonić

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
 17
Author: Paul Freez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-24 09:16:40

Najlepszym sposobem pozbycia się tego jest zachowanie odniesienia do aktywności, gdy wywołane jest onAttach i użycie odniesienia do aktywności wszędzie tam, gdzie jest to potrzebne, na przykład

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

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

Edited, since onAttach(Activity) is deprecjated & now onAttach(Context) is being used

 14
Author: Pawan Maheshwari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-30 18:09:00

Nie wywołaj metod wewnątrz fragmentu, które wymagają getActivity (), dopóki nie rozpocznie się OnStart w aktywności rodzica.

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();
}
 9
Author: bvmobileapps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-14 16:12:20
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
 3
Author: dheeran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-14 16:04:25

Od jakiegoś czasu zmagam się z tego typu problemami i myślę, że wymyśliłam niezawodne rozwiązanie.

Trudno jest być pewnym, że this.getActivity() nie zwróci null Za Fragment, zwłaszcza jeśli masz do czynienia z jakimkolwiek rodzajem zachowania sieciowego, które daje Twojemu kodowi wystarczająco dużo czasu na wycofanie Activity odniesień.

W poniższym rozwiązaniu deklaruję małą klasę zarządzania zwaną ActivityBuffer. Zasadniczo ta class zajmuje się utrzymaniem niezawodne odniesienie do posiadającego Activity i obiecujące wykonanie Runnable S w prawidłowym kontekście Activity, gdy tylko dostępne jest poprawne odniesienie. Runnable S są zaplanowane do wykonania w wątku UI natychmiast, jeśli Context jest dostępny, w przeciwnym razie wykonanie jest odroczone do momentu, gdy Context jest gotowy.

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

}

Jeśli chodzi o jego realizację, musimy zadbać o stosowanie metod cyklu życia , aby pokrywały się z zachowaniem opisanym powyżej przez Pawana 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;
    }

}

Wreszcie, w każdym obszarze w Fragment, który rozciąga się BaseFragment, że nie jesteś godny zaufania w sprawie połączenia do getActivity(), po prostu wykonaj połączenie do this.getActivityBuffer().safely(...) i zadeklaruj ActivityBuffer.IRunnable do zadania!

Zawartość Twojego void run(final Activity pActivity) jest następnie gwarantowana do wykonania wzdłuż wątku UI.

 3
Author: Cawfree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-19 21:37:31

Wiem, że to stare pytanie, ale myślę, że muszę udzielić na nie odpowiedzi, ponieważ mój problem nie został rozwiązany przez innych.

Po pierwsze: dynamicznie dodawałem fragmenty używając fragmentTransactions. Po drugie: moje fragmenty zostały zmodyfikowane przy użyciu asynchronicznych Zadań (zapytań DB na serwerze). Po trzecie: mój fragment nie został utworzony na początku aktywności Po czwarte: użyłem niestandardowej instancji fragmentu "create or load it", aby uzyskać zmienną fragment. Po czwarte: aktywność została odtworzona z powodu zmiana orientacji

Problem polegał na tym, że chciałem" usunąć " fragment z powodu odpowiedzi na zapytanie, ale fragment został nieprawidłowo utworzony tuż przed. Nie wiem dlaczego, prawdopodobnie ze względu na" commit " być wykonane później, fragment nie został jeszcze dodany, gdy nadszedł czas, aby go usunąć. Dlatego getActivity () zwracało null.

Rozwiązanie : 1)musiałem sprawdzić, czy poprawnie próbuję znaleźć pierwszą instancję fragmentu przed utworzeniem nowego 2) musiałem umieścić serRetainInstance (true) na tym fragmencie w celu utrzymania go przez zmianę orientacji (nie jest potrzebny backstack, więc nie ma problemu) 3) zamiast "odtwarzać lub starzeć fragment" tuż przed "usuń go", bezpośrednio umieszczam fragment na początku aktywności. Utworzenie jej na początku aktywności zamiast "ładowania" (lub tworzenia instancji) zmiennej fragment przed jej usunięciem zapobiegło problemom z getActivity.

 1
Author: Feuby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-03 15:20:05