442 votes

Fragment MyFragment n'est pas attachée à l'Activité

J'ai créé une petite application de test qui représente mon problème. Je suis en utilisant ActionBarSherlock à mettre en œuvre des onglets (Sherlock)des Fragments.

Mon code: TestActivity.java

public class TestActivity extends SherlockFragmentActivity {
    private ActionBar actionBar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupTabs(savedInstanceState);
    }

    private void setupTabs(Bundle savedInstanceState) {
        actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        addTab1();
        addTab2();
    }

    private void addTab1() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("1");
        String tabText = "1";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));

        actionBar.addTab(tab1);
    }

    private void addTab2() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("2");
        String tabText = "2";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));

        actionBar.addTab(tab1);
    }
}

TabListener.java

public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        // Check if the fragment is already initialized
        if (preInitializedFragment == null) {
            // If not, instantiate and add it to the activity
            SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(preInitializedFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        if (preInitializedFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(preInitializedFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // User selected the already selected tab. Usually do nothing.
    }
}

MyFragment.java

public class MyFragment extends SherlockFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex) {
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result){
                getResources().getString(R.string.app_name);
            }

        }.execute();
    }
}

J'ai ajouté l' Thread.sleep partie de simuler le téléchargement de données. Le code de l' onPostExecute est de simuler l'utilisation de l' Fragment.

Lorsque je tourne l'écran très rapide entre le paysage et le portrait, j'obtiens une Exception à l' onPostExecute code:

java.lang.IllegalStateException: Fragment MyFragment{410f6060} pas rattaché à l'Activité

Je pense que c'est parce qu'un nouveau MyFragment a été créé dans le temps, et a été affecté à l'Activité avant l' AsyncTask fini. Le code en onPostExecute des appels sur une seule MyFragment.

Mais comment puis-je résoudre ce problème?

843voto

Niek Haarman Points 13931

J'ai trouvé la réponse très simple: isAdded():

De retour true si le fragment est actuellement ajoutée à son activité.

@Override
protected void onPostExecute(Void result){
    if(isAdded()){
        getResources().getString(R.string.app_name);
    }
}

Pour éviter onPostExecute d'être appelé lorsque l' Fragment n'est pas attaché à l' Activity est d'annuler l' AsyncTask lors de la pause ou l'arrêt de l' Fragment. Ensuite, isAdded() ne serait plus nécessaire.

26voto

luixal Points 263

J'ai dû faire face à deux scénarios différents ici:

1) Quand je veux la tâche asynchrone pour finir de toute façon: imaginez ma onPostExecute ne stocker les données reçues et alors appel à un auditeur de mise à jour des vues alors, pour être plus efficace, je veux la fin de la tâche de toute façon j'ai donc la préparation des données lorsque l'utilisateur cames en arrière. Dans ce cas, j'ai l'habitude de le faire:

@Override
protected void onPostExecute(void result) {
    // do whatever you do to save data
    if (this.getView() != null) {
        // update views
    }
}

2) Quand je veux la tâche asynchrone uniquement pour terminer lorsque les vues peuvent être mis à jour: le cas où vous proposons ici, la tâche que les mises à jour les points de vue, pas de stockage de données nécessaires, de sorte qu'il n'a pas d'idée pour la fin de la tâche si les points de vue ne sont plus en cours a montré. Je fais ceci:

@Override
protected void onStop() {
    // notice here that I keep a reference to the task being executed as a class member:
    if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
    super.onStop();
}

J'ai trouvé aucun problème avec cela, bien que j'utilise aussi (peut-être) plus complexe, ce qui comprend le lancement de tâches à partir de l'activité en lieu et place des fragments.

Voulez que cela aide quelqu'un! :)

18voto

Le problème avec votre code est la façon dont vous utilisez l'AsyncTask, parce que lorsque vous faites pivoter l'écran pendant votre sommeil thread:

Thread.sleep(2000) 

l'AsyncTask est encore à travailler, c'est parce que vous n'avez pas annuler l'AsyncTask instance correctement dans onDestroy() avant le fragment reconstruit (lorsque vous faites pivoter) et lorsque cette même AsyncTask instance (après rotation) s'exécute onPostExecute(), cette méthode tente de trouver les ressources avec getResources() avec l'ancien fragment d'instance(non valide, par exemple:

getResources().getString(R.string.app_name)

ce qui est équivalent à:

MyFragment.this.getResources().getString(R.string.app_name)

Donc, la solution finale est de gérer l'AsyncTask instance (pour annuler si c'est encore de travail) avant le fragment reconstruit lorsque vous faites pivoter l'écran, et en cas d'annulation au cours de la transition, le redémarrage de l'AsyncTask après la reconstruction à l'aide d'un indicateur booléen:

public class MyFragment extends SherlockFragment {

    private MyAsyncTask myAsyncTask = null;
    private boolean myAsyncTaskIsRunning = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState!=null) {
            myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
        }
        if(myAsyncTaskIsRunning) {
            myAsyncTask = new MyAsyncTask();
            myAsyncTask.execute();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(myAsyncTask!=null) myAsyncTask.cancel(true);
        myAsyncTask = null;

    }

    public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {

        public MyAsyncTask(){}

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            myAsyncTaskIsRunning = true;
        }
        @Override
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {}
            return null;
        }

        @Override
        protected void onPostExecute(Void result){
            getResources().getString(R.string.app_name);
            myAsyncTaskIsRunning = false;
            myAsyncTask = null;
        }

    }
}

10voto

Aristo Michael Points 150

J'ai été confrontée au même problème, j'ai juste ajouter le singletone exemple pour obtenir des ressources visées par Erick

MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);

vous pouvez également utiliser

getActivity().getResources().getString(R.string.app_name);

J'espère que cela aidera.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X