Android. Il frammento getActivity () a volte restituisce null


194

Nelle segnalazioni di errori della console per sviluppatori a volte vedo segnalazioni con problemi di NPE. Non capisco cosa c'è di sbagliato nel mio codice. Sull'emulatore e l'applicazione del mio dispositivo funziona bene senza forzature, tuttavia alcuni utenti ottengono NullPointerException nella classe di frammenti quando viene chiamato il metodo getActivity ().

Attività

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

Classe AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Classe di frammenti

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

Forse questo errore si verifica quando le applicazioni riprendono dallo sfondo. In questo caso come dovrei gestire correttamente questa situazione?


Ho capito un problema, ma non una soluzione. Non so perché ma il frammento riprende l'attività precedente. E questo accade solo quando la mia app all'ultima posizione nell'elenco elenca le app recenti, sembra che il sistema distrugga la mia applicazione.
Georgy Gobozov,

1
Quando riprendo la mia applicazione dallo sfondo fragmetn onCreate un onResume chiamato prima dell'attività onCreate / onResume metodo. Sembra che un frammento distaccato sia ancora vivo e cerchi di riprendere.
Georgy Gobozov,

1
in questa stringa firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) restituisce il vecchio frammento, l'adattatore non rimuove i frammenti in modo corretto
Georgy Gobozov,

9
Grande attività a proposito :) domanda posta, commenti lasciati e risposta data - tutto è fatto da una sola persona! +1 per questi.
Prizoff,

salva il contesto (getActivity ()) in onCreateView () poiché viene chiamato quando la vista viene ricreata in background.
sha

Risposte:


123

Sembra che ho trovato una soluzione al mio problema. Ottime spiegazioni sono date qui e qui . Ecco il mio esempio:

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

L'idea principale in questo codice è che, durante l'esecuzione normale dell'applicazione, si creano nuovi frammenti e li si passa all'adattatore. Quando si riprende il gestore dei frammenti dell'applicazione ha già l'istanza di questo frammento ed è necessario ottenerlo dal gestore dei frammenti e passarlo all'adattatore.

AGGIORNARE

Inoltre, è buona norma utilizzare frammenti per verificare isAdded prima di chiamare getActivity (). Ciò consente di evitare un'eccezione puntatore null quando il frammento è staccato dall'attività. Ad esempio, un'attività potrebbe contenere un frammento che invia un'attività asincrona. Al termine dell'attività, viene chiamato il listener 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();
    }

}

Se apriamo il frammento, inviamo un'attività e quindi premiamo rapidamente indietro per tornare a un'attività precedente, al termine dell'attività, tenterà di accedere all'attività in onPostExecute () chiamando il metodo getActivity (). Se l'attività è già disconnessa e questo controllo non è presente:

if (isAdded()) 

quindi l'applicazione si arresta in modo anomalo.


56
Questo è fastidioso però, dover chiamare isAdded()prima di ogni accesso ... rende il codice brutto.
Ixx,

25
Non sembra esserci molta differenza tra avere if(isAdded())oif(getActivity() != null)
StackOverflow del

19

Ok, so che questa domanda è stata effettivamente risolta, ma ho deciso di condividere la mia soluzione per questo. Ho creato una classe genitore astratta per il mio 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;
        }
    }
}

Come puoi vedere, ho aggiunto un ascoltatore, quindi, ogni volta che avrò bisogno di ottenere Fragments Activityinvece che standard getActivity(), dovrò chiamare

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });

Bella risposta! dovrebbe essere contrassegnato come quello corretto poiché risolve il vero problema: nel mio caso non è sufficiente verificare che getActivity () non sia nullo perché devo completare il mio compito in ogni caso. Sto usando questo e funziona perfettamente.
Hadas Kaminsky,

18

Il modo migliore per sbarazzarsi di questo è mantenere il riferimento all'attività quando onAttachviene chiamato e utilizzare il riferimento all'attività dove necessario, ad es

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

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

Modificato, poiché onAttach(Activity)è ammortizzato e ora onAttach(Context)viene utilizzato


9
Frammenti mantiene sempre il riferimento dell'attività principale e ti rende disponibile con il metodo getActivity (), qui manteniamo lo stesso riferimento.
Pawan Maheshwari,

8
Google in realtà lo consiglia se hai bisogno del tuo frammento per condividere eventi con l'attività. developer.android.com/guide/components/fragments.html (cercare "Creazione di callback di eventi per l'attività")
Vering

6
potresti voler aggiungere il metodo Detach, che annulla il riferimento all'attività
mezzanotte

2
Sì, inizializzare mActivity = null sul metodo onDetach per annullare quel riferimento all'attività.
Pawan Maheshwari,

19
mai e poi mai. stai perdendo la tua attività completa (e con essa l'intero albero del layout, con drawable e simili). Se getActivity()restituisce null, è perché non sei più in un'attività. Questa è una soluzione sporca.
njzk2,

10

Non chiamare metodi all'interno del frammento che richiedono getActivity () fino a quando OnStart nell'attività padre.

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

In realtà, ho avuto un problema simile perché stavo iniziando l'attività nel costruttore del frammento. Molte grazie.
Supreme Dolphin,

4

Ho combattuto questo tipo di problema per un po 'e penso di aver trovato una soluzione affidabile.

È abbastanza difficile sapere con certezza che this.getActivity()non tornerà nullper a Fragment, specialmente se hai a che fare con qualsiasi tipo di comportamento di rete che dà al tuo codice molto tempo per ritirare i Activityriferimenti.

Nella soluzione di seguito, dichiaro una piccola classe di gestione chiamata ActivityBuffer. In sostanza, si classtratta di mantenere un riferimento affidabile a una proprietà Activitye di promettere di eseguire Runnables in un Activitycontesto valido ogni volta che è disponibile un riferimento valido. Gli Runnables sono pianificati per l'esecuzione sul thread dell'interfaccia utente immediatamente se Contextsono disponibili, altrimenti l'esecuzione viene rinviata fino a quando non Contextè pronta.

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

}

In termini di implementazione, dobbiamo fare attenzione ad applicare i metodi del ciclo di vita in modo che coincidano con il comportamento sopra descritto da 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;
    }

}

Infine, in tutte le aree all'interno delle Fragmentquali BaseFragmentnon sei affidabile in merito a una chiamata getActivity(), effettua semplicemente una chiamata this.getActivityBuffer().safely(...)e dichiarane una ActivityBuffer.IRunnableper l'attività!

I contenuti del tuo void run(final Activity pActivity)sono quindi garantiti per l'esecuzione lungo il thread dell'interfaccia utente.

L' ActivityBufferpuò quindi essere utilizzati nel modo seguente:

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

Puoi aggiungere un esempio dell'uso di this.getActivityBuffer (). Metodo sicuro (...).
fahad_sust,

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

Potresti per favore elaborare di più la tua risposta aggiungendo un po 'più di descrizione della soluzione che offri?
Abarisone,

1

So che questa è una vecchia domanda, ma penso che devo fornire la mia risposta perché il mio problema non è stato risolto da altri.

prima di tutto: stavo aggiungendo dinamicamente frammenti usando fragmentTransactions. Secondo: i miei frammenti sono stati modificati usando AsyncTasks (query DB su un server). Terzo: il mio frammento non è stato istanziato all'avvio dell'attività Quarto: ho usato un'istanza personalizzata del frammento "crea o carica" ​​per ottenere la variabile del frammento. Quarto: l'attività è stata ricreata a causa del cambio di orientamento

Il problema era che volevo "rimuovere" il frammento a causa della risposta alla query, ma il frammento era stato creato in modo errato poco prima. Non so perché, probabilmente a causa del "commit" da eseguire in seguito, il frammento non è stato ancora aggiunto quando era il momento di rimuoverlo. Pertanto getActivity () restituiva null.

Soluzione: 1) Ho dovuto verificare che stavo cercando correttamente di trovare la prima istanza del frammento prima di crearne uno nuovo 2) Ho dovuto inserire serRetainInstance (true) su quel frammento per mantenerlo attraverso il cambio di orientamento (senza backstack necessario quindi nessun problema) 3) Invece di "ricreare o ottenere il vecchio frammento" appena prima di "rimuoverlo", ho messo direttamente il frammento all'inizio dell'attività. La creazione di un'istanza all'avvio dell'attività anziché "caricamento" (o creazione di un'istanza) della variabile del frammento prima di rimuoverla ha impedito i problemi di getActivity.


0

In Kotlin puoi provare in questo modo a gestire la condizione null getActivity ().

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

        //your code here

   }

Controllerà se l'attività è nulla o meno e, se non è nulla, eseguirà il codice interno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.