Android. Le fragment getActivity () renvoie parfois null


194

Dans les rapports d'erreur de la console développeur, je vois parfois des rapports avec un problème NPE. Je ne comprends pas ce qui ne va pas avec mon code. Sur l'émulateur et mon application de périphérique fonctionne bien sans forcecloses, cependant certains utilisateurs obtiennent NullPointerException dans la classe de fragments lorsque la méthode getActivity () est appelée.

Activité

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

Peut-être que cette erreur se produit lorsque les applications reprennent en arrière-plan. Dans ce cas, comment dois-je gérer cette situation correctement?


J'ai trouvé un problème, mais pas une solution. Je ne sais pas pourquoi mais le fragment reprend une activité antérieure. Et cela ne se produit que lorsque mon application se trouve en dernière position dans la liste des applications récentes, il semble que le système détruise mon application.
Georgy Gobozov

1
Lorsque je reprends mon application à partir de fragmetn d'arrière-plan, onCreate un onResume appelé avant l'activité onCreate / onResume. Il semble qu'un fragment détaché soit encore vivant et essaie de reprendre.
Georgy Gobozov

1
dans cette chaîne firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) retourne l'ancien fragment, l'adaptateur ne supprime pas correctement les fragments
Georgy Gobozov

9
Grande activité d'ailleurs :) question posée, commentaires laissés et réponse donnée - tout est fait par une seule personne! +1 pour ceux-ci.
Prizoff

enregistrez le contexte (getActivity ()) dans onCreateView () car cela est appelé lorsque la vue est recréée en arrière-plan.
sha

Réponses:


123

Il semble que j'ai trouvé une solution à mon problème. De très bonnes explications sont données ici et ici . Voici mon exemple:

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'idée principale de ce code est que, tout en exécutant votre application normalement, vous créez de nouveaux fragments et les passez à l'adaptateur. Lorsque vous reprenez votre gestionnaire de fragments d'application possède déjà l'instance de ce fragment et vous devez l'obtenir du gestionnaire de fragments et le transmettre à l'adaptateur.

METTRE À JOUR

En outre, c'est une bonne pratique lorsque vous utilisez des fragments pour vérifier isAdded avant d'appeler getActivity (). Cela permet d'éviter une exception de pointeur nul lorsque le fragment est détaché de l'activité. Par exemple, une activité peut contenir un fragment qui envoie une tâche asynchrone. Une fois la tâche terminée, l'écouteur onTaskComplete est appelé.

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

}

Si nous ouvrons le fragment, poussons une tâche, puis appuyons rapidement pour revenir à une activité précédente, lorsque la tâche est terminée, il tentera d'accéder à l'activité dans onPostExecute () en appelant la méthode getActivity (). Si l'activité est déjà détachée et que cette vérification n'est pas là:

if (isAdded()) 

puis l'application se bloque.


56
C'est ennuyeux cependant, avoir à appeler isAdded()avant chaque accès ... rend le code laid.
Ixx

25
Il ne semble pas y avoir beaucoup de différence entre avoir if(isAdded())ouif(getActivity() != null)
StackOverflowed

19

Ok, je sais que cette question est en fait résolue mais j'ai décidé de partager ma solution pour cela. J'ai créé une classe parent abstraite pour mon 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;
        }
    }
}

Comme vous pouvez le voir, j'ai ajouté un écouteur donc, chaque fois que je devrai obtenir Fragments Activityau lieu de standard getActivity(), je devrai appeler

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

Très bonne réponse! devrait être marqué comme le bon car il résout le vrai problème: Dans mon cas, il ne suffit pas de vérifier que getActivity () n'est pas nul car je dois terminer ma tâche quoi qu'il arrive. J'utilise cela et cela fonctionne parfaitement.
Hadas Kaminsky

18

Le meilleur moyen de s'en débarrasser est de conserver la référence d'activité lorsqu'elle onAttachest appelée et d'utiliser la référence d'activité partout où cela est nécessaire, par exemple

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

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

Modifié, car onAttach(Activity)est amorti et onAttach(Context)est maintenant utilisé


9
Fragments conserve toujours la référence de son activité parent et vous rend disponible avec la méthode getActivity (), ici nous gardons la même référence.
Pawan Maheshwari

8
Google le recommande en fait si vous avez besoin de votre fragment pour partager des événements avec l'activité. developer.android.com/guide/components/fragments.html (recherchez "Création de rappels d'événements à l'activité")
Vering

6
vous voudrez peut-être ajouter la méthode onDetach, qui annule la référence d'activité
minuit

2
ouais initialiser mActivity = null sur la méthode onDetach pour annuler cette référence d'activité.
Pawan Maheshwari

19
ne fais jamais ça. vous perdez votre activité complète (et avec elle tout l'arborescence de mise en page, avec des tirages et autres). Si getActivity()renvoie null, c'est parce que vous n'êtes plus dans une activité. Il s'agit d'une solution de contournement sale.
njzk2

10

N'appelez pas de méthodes dans le fragment qui nécessitent getActivity () jusqu'à onStart dans l'activité parent.

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

En fait, j'ai eu un problème similaire parce que je commençais la tâche dans le constructeur de fragment. Merci beaucoup.
Supreme Dolphin

4

J'ai été lutte contre ce type de problème depuis un certain temps et je pense avoir trouvé une solution fiable.

Il est assez difficile de savoir avec certitude que cela this.getActivity()ne reviendra pas nullpour un Fragment, surtout si vous faites face à tout type de comportement réseau qui donne à votre code suffisamment de temps pour se retirerActivity références.

Dans la solution ci-dessous, je déclare une petite classe de gestion appelée ActivityBuffer. Essentiellement, cela classconcerne le maintien d'une référence fiable à un propriétaire Activityet la promesse d'exécuter des Runnables dans un Activitycontexte valide chaque fois qu'une référence valide est disponible. Les Runnables sont planifiés pour être exécutés sur le thread d'interface utilisateur immédiatement s'ils Contextsont disponibles, sinon l'exécution est différée jusqu'à ce que cela Contextsoit prêt.

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

}

Au niveau de sa mise en œuvre, il faut veiller à appliquer les méthodes du cycle de vie pour coïncider avec le comportement décrit ci-dessus par 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;
    }

}

Enfin, dans tous les domaines au sein de votre Fragmentqui s'étend BaseFragmentque vous n'êtes pas digne de confiance sur un appel à getActivity(), il suffit d'appeler this.getActivityBuffer().safely(...)et de déclarer unActivityBuffer.IRunnable pour la tâche!

Le contenu de votre void run(final Activity pActivity)est alors garanti de s'exécuter le long du fil d'interface utilisateur.

Le ActivityBufferpeut alors être utilisé comme suit:

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

Pouvez-vous ajouter un exemple d'utilisation de cette méthode .getActivityBuffer (). Safe (...).
fahad_sust

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

Pourriez-vous élaborer davantage votre réponse en ajoutant un peu plus de description sur la solution que vous proposez?
abarisone

1

Je sais que c'est une vieille question mais je pense que je dois y répondre parce que mon problème n'a pas été résolu par d'autres.

tout d'abord: j'ajoutais dynamiquement des fragments en utilisant fragmentTransactions. Deuxièmement: mes fragments ont été modifiés à l'aide d'AsyncTasks (requêtes DB sur un serveur). Troisièmement: mon fragment n'a pas été instancié au début de l'activité Quatrièmement: j'ai utilisé une instanciation de fragment personnalisée "créer ou charger" afin d'obtenir la variable de fragment. Quatrièmement: l'activité a été recréée en raison d'un changement d'orientation

Le problème était que je voulais "supprimer" le fragment à cause de la réponse à la requête, mais le fragment a été créé incorrectement juste avant. Je ne sais pas pourquoi, probablement à cause du "commit" qui sera fait plus tard, le fragment n'a pas encore été ajouté au moment de le supprimer. Par conséquent, getActivity () renvoyait null.

Solution: 1) J'ai dû vérifier que j'essayais correctement de trouver la première instance du fragment avant d'en créer un nouveau 2) J'ai dû mettre serRetainInstance (true) sur ce fragment afin de le conserver par le changement d'orientation (pas de backstack nécessaire donc pas de problème) 3) Au lieu de "recréer ou obtenir un vieux fragment" juste avant de "le retirer", je mets directement le fragment au début de l'activité. L'instanciation au démarrage de l'activité au lieu de «charger» (ou instancier) la variable de fragment avant de la supprimer a empêché les problèmes de getActivity.


0

Dans Kotlin, vous pouvez essayer de cette façon de gérer la condition null getActivity ().

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

        //your code here

   }

Il vérifiera que l'activité est nulle ou non et s'il n'est pas nul, exécutera le code interne.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.