535 votes

Comment gérer le changement de l’orientation d’écran lorsque la boîte de dialogue de progression et d’arrière-plan thread actif ?

Mon programme fait de l'activité du réseau dans un thread d'arrière-plan. Avant de commencer, il affiche une boîte de dialogue de progression. La boîte de dialogue fermée sur le gestionnaire. Tout cela fonctionne bien, sauf lorsque l'orientation de l'écran change alors que le dialogue est en place (et le thread d'arrière-plan est en cours). À ce stade, l'application se bloque, ou de blocages, ou pénètre dans une étrange scène où l'application ne fonctionne pas du tout jusqu'à ce que tous les fils ont été tués.

Comment puis-je gérer l'orientation de l'écran changement gracieusement?

L'exemple de code ci-dessous correspond à peu près ce que mon programme n':

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

Pile:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

J'ai essayé de faire disparaître la boîte de dialogue de progression dans onSaveInstanceState, mais cela ne l'empêche immédiate d'un crash. Le thread d'arrière-plan est toujours en cours, et l'INTERFACE utilisateur est en partie tiré de l'état. Besoin de tuer l'ensemble de l'application avant qu'il ne commence à travailler de nouveau.

265voto

sonxurxo Points 2267

Edit: les ingénieurs Google ne recommandons pas cette approche, telle que décrite par Dianne Hackborn (un.k.un. hackbod) dans ce StackOverflow post. Découvrez ce blog pour plus d'informations.


Vous avez à ajouter à la déclaration d'activité dans le manifeste:

android:configChanges="orientation"

de sorte qu'il ressemble

<activity android:label="@string/app_name" 
        android:configChanges="orientation|keyboardHidden" 
        android:name=".your.package">

La question est que le système détruit l'activité lors d'un changement de configuration se produit. Voir ConfigurationChanges.

Afin de le mettre dans le fichier de configuration évite le système de détruire votre activité. Au lieu de cela, il invoque l' onConfigurationChanged(Configuration) méthode.

157voto

haseman Points 6071

Lorsque vous basculez d’orientations, Android va créer une nouvelle vue. Vous obtenez probablement tombe en panne, parce que votre thread d’arrière-plan tente de modifier l’État sur l’ancien. (Il peut aussi être la difficulté parce que votre thread d’arrière-plan n’est pas sur le thread d’interface utilisateur)

Je suggère de faire cette mHandler volatils et mettre à jour lorsque l’orientation change.

69voto

jfelectron Points 778

Je suis venu avec une solide solution de ces questions qui est conforme avec le 'Android' Des choses. J'ai toutes mes opérations de longue durée à l'aide de la IntentService modèle.
C'est à mes activités de diffusion intentions, le IntentService fait le travail, enregistre les données dans la base de données puis l'émission COLLANTE intentions.
Le collant partie est importante, de sorte que même si l'Activité a été suspendue pendant le temps après que l'utilisateur a lancé les travaux et rate le temps réel de la diffusion de la ItentService on peut encore réagir et de prendre les données de la vocation de l'Activité.
ProgressDialogs pouvez travailler dans ce modèle très agréable avec onSaveInstanceSate().
Fondamentalement, vous avez besoin d'enregistrer un drapeau que vous avez une boîte de dialogue de progression de l'exécution dans le sauvé exemple bundle. NE PAS enregistrer la boîte de dialogue de progression de l'objet, car cela provoquera une fuite à l'ensemble de l'Activité.
Pour avoir une persistance de la poignée de la boîte de dialogue de progression, je le conserver comme une Faiblesse de la référence dans l'objet application.
Sur changement d'orientation ou toute autre chose qui provoque l'Activité à la pause (appel téléphonique, l'utilisateur accède à la maison, etc), puis de reprendre, je rejette l'ancien dialogue et de recréer une nouvelle boîte de dialogue dans le nouvellement créé de l'Activité.
Pour une durée indéterminée de progression c'est facile. Pour la barre de progression du style, vous avez à mettre la dernière progrès dans l'ensemble et quelle que soit l'information que vous utilisez localement de l'activité pour garder une trace de la progression.
Sur la restauration de la progression, vous pourrez utiliser ces informations pour re-spawn la barre de progression dans le même état qu'avant et puis la mise à jour basé sur l'état actuel des choses.
Donc, pour résumer, en mettant à long tâches en cours d'exécution dans un IntentService couplé avec juidcious utilisation de onSaveInstanceState() vous permet de garder une trace efficacement des dialogues et de restauration puis à travers l'Activité du cycle de vie des événements. Pertinentes des morceaux de code d'Activité sont ci-dessous. Vous aurez également besoin de logique dans votre BroadcastReceiver à poignée Collante intentions de manière appropriée, mais qui est au-delà de la portée de cette.

   public void doSignIn(View view) {
        waiting=true;
        AppClass app=(AppClass) getApplication();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
..}


@Override
    protected void onSaveInstanceState(Bundle saveState) {
        super.onSaveInstanceState(saveState);
        saveState.putBoolean("waiting",waiting);
    }

    @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if(savedInstanceState!=null) {
                restoreProgress(savedInstanceState);

            }
...}


private void restoreProgress(Bundle savedInstanceState) {
        waiting=savedInstanceState.getBoolean("waiting");
        if (waiting) {
            AppClass app=(AppClass) getApplication();
            ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
            refresher.dismiss();
            String logingon=getString(R.string.signon);
            app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));



        }

    }

28voto

samsonsu Points 226

J'ai rencontré le même problème. Mon activité a besoin d'analyser des données à partir d'une URL et c'est lent. J'ai donc créer un thread pour faire afficher un progressdialog. Je laisse le fil de poster un msg retour à la thread d'INTERFACE utilisateur via le Gestionnaire quand il est terminé. Dans Le Gestionnaire.handleMessage, je reçois l'objet de données (prêt maintenant) à partir du fil et de le remplir à l'INTERFACE utilisateur. C'est donc très similaire à votre exemple.

Après de nombreux essais et d'erreurs, il semble que j'ai trouvé une solution. Au moins maintenant, je peux faire pivoter l'écran à tout moment, avant ou après le thread est terminé. Dans tous les essais, la boîte de dialogue est correctement fermé et que tous les comportements sont comme prévu.

Ce que j'ai fait est illustré ci-dessous. L'objectif est de remplir mon modèle de données (mDataObject) et ensuite de le remplir à l'INTERFACE utilisateur. Doit permettre la rotation de l'écran à tout moment et sans surprise.

class MyActivity {

private MyDataObject mDataObject = null;
private static MyThread mParserThread = null; // static, or make it singleton

OnCreate() {
        ...
        Object retained = this.getLastNonConfigurationInstance();
        if(retained != null) {
            // data is already completely obtained before config change
            // by my previous self.
            // no need to create thread or show dialog at all
            mDataObject = (MyDataObject) retained;
            populateUI();
        } else if(mParserThread != null && mParserThread.isAlive()){
            // note: mParserThread is a static member or singleton object.
            // config changed during parsing in previous instance. swap handler
            // then wait for it to finish.
            mParserThread.setHandler(new MyHandler());
        } else {
            // no data and no thread. likely initial run
            // create thread, show dialog
            mParserThread = new MyThread(..., new MyHandler());
            mParserThread.start();
            showDialog(DIALOG_PROGRESS);
        }
}

// http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
public Object onRetainNonConfigurationInstance() {
        // my future self can get this without re-downloading
        // if it's already ready.
        return mDataObject;
}

// use Activity.showDialog instead of ProgressDialog.show
// so the dialog can be automatically managed across config change
@Override
protected Dialog onCreateDialog(int id) {
    // show progress dialog here
}

// inner class of MyActivity
private class MyHandler extends Handler {
    public void handleMessage(msg) {
        mDataObject = mParserThread.getDataObject();
        populateUI();
        dismissDialog(DIALOG_PROGRESS);
    }
}
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    public MyHandler(..., Handler h) {...; mHandler = h;} // constructor with handler param
    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller
    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

C'est ce qui fonctionne pour moi. Je ne sais pas si c'est la "bonne" méthode conçue par Android-ils demander ce "détruire/recréer de l'activité au cours de la rotation de l'écran" en fait, rend les choses plus faciles, donc je suppose que ça ne devrait pas être trop difficile.

Laissez-moi savoir si vous voyez un problème dans mon code. Comme dit ci-dessus, je ne sais pas vraiment si il n'y a aucun effet secondaire.

15voto

gymshoe Points 1245

L'origine perçue problème est que le code ne survit pas à un écran de changement d'orientation. Apparemment, cela a été "résolu" par le fait que le programme gérer l'orientation de l'écran le changement lui-même, au lieu de laisser le framework d'INTERFACE (via l'appel de onDestroy)).

Je tiens à signaler que si le problème sous-jacent est que le programme ne va pas survivre onDestroy(), alors la solution retenue est juste une solution de contournement qui quitte le programme avec d'autres graves problèmes et les vulnérabilités. Rappelez-vous que l'Android-cadre stipule expressément que votre activité est en danger d'être détruit presque à tout moment en raison de circonstances hors de votre contrôle. Par conséquent, votre activité doit être en mesure de survivre onDestroy (), et ensuite onCreate() pour une raison, pas seulement une orientation de l'écran change.

Si vous allez accepter la manipulation de l'orientation de l'écran modifications vous-même afin de résoudre le cas des OP problème, vous devez vérifier que les autres causes de onDestroy() n'entraînent pas la même erreur. Êtes-vous capable de faire cela? Si pas, je le ferais question de savoir si la "accepté" la réponse est vraiment très bon.

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