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

StackOverflow https://stackoverflow.com/questions/1111980

Question

Mon programme fait une activité réseau dans un thread d'arrière-plan. Avant de commencer, il apparaît d'une boîte de dialogue Progress. Le dialogue est rejeté sur le gestionnaire. Tout cela fonctionne bien, sauf lorsque l'orientation de l'écran change pendant la fin de la boîte de dialogue (et le thread d'arrière-plan va). À ce stade, l'application s'écrase, soit des impasses, soit dans une étape étrange où l'application ne fonctionne pas du tout tant que tous les fils ont été tués.

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

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

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

Empiler:

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 rejeter le dialogue Progress dans OnsaveinstancEstate, mais cela empêche simplement un accident immédiat. Le thread d'arrière-plan continue et l'interface utilisateur est dans un état partiellement dessiné. Besoin de tuer toute l'application avant de recommencer à travailler.

Était-ce utile?

La solution

Lorsque vous changez d'orientation, Android créera une nouvelle vue. Vous obtenez probablement des accidents parce que votre fil d'arrière-plan essaie de changer l'état sur l'ancien. (Il peut également avoir des problèmes car votre fil d'arrière-plan n'est pas sur le fil d'interface utilisateur)

Je suggère de rendre ce Mhandler volatile et de le mettre à jour lorsque l'orientation change.

Autres conseils

Éditer: Les ingénieurs Google ne recommandent pas cette approche, comme décrit par Dianne Hackborn (alias hackbod) dans ce Post de stackOverflow. Vérifier Ce billet de blog pour plus d'informations.


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

android:configChanges="orientation|screenSize"

Alors ça ressemble

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

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

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

J'ai trouvé une solution solide pour ces problèmes conforme à la «voie Android» des choses. J'ai toutes mes opérations de longue durée en utilisant le modèle IntenService.

C'est-à-dire que mes activités diffusent les intentions, l'intervention fait le travail, enregistre les données dans la base de données puis diffuse collant Intention. La partie collante est importante, de sorte que même si l'activité a été interrompue pendant le temps après que l'utilisateur a lancé le travail et manque la diffusion en temps réel à partir de l'intensité, nous pouvons encore répondre et récupérer les données de l'activité d'appel. ProgressDialogs peut fonctionner avec ce modèle très bien avec onSaveInstanceState().

Fondamentalement, vous devez enregistrer un drapeau que vous avez une boîte de dialogue Progress en cours d'exécution dans le faisceau d'instance enregistré. Ne pas Enregistrez l'objet de dialogue Progress car cela fuyera toute l'activité. Pour avoir une poignée persistante à la boîte de dialogue Progress, je la stocke comme une référence faible dans l'objet d'application. Sur le changement d'orientation ou tout ce qui fait en sorte que l'activité s'arrête (appel téléphonique, utilisateur frappe à la maison, etc.), puis reprenez, je rejette l'ancien dialogue et recréte une nouvelle boîte de dialogue dans l'activité nouvellement créée.

Pour les boîtes de dialogue de progrès indéfinis, c'est facile. Pour le style de barre de progression, vous devez mettre les derniers progrès connus dans le bundle et toutes les informations que vous utilisez localement dans l'activité pour suivre les progrès. Lors de la restauration des progrès, vous utiliserez ces informations pour recouvrir la barre de progrès dans le même état qu'avant, puis à mettre à jour en fonction de l'état actuel des choses.

Donc, pour résumer, mettre des tâches de longue onSaveInstanceState() Vous permet de suivre efficacement les boîtes de dialogue et de restaurer ensuite les événements du cycle de vie de l'activité. Les bits pertinents du code d'activité sont ci-dessous. Vous aurez également besoin de logique dans votre BroadCastreceiver pour gérer les intentions collantes de manière appropriée, mais cela dépasse le cadre de cela.

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

J'ai rencontré le même problème. Mon activité doit analyser certaines données d'une URL et elle est lente. Je crée donc un fil pour le faire, puis affiche une boîte de dialogue Progress. Je laisse le fil publier un message sur le fil d'interface utilisateur via Handler quand c'est terminé. Dans Handler.handleMessage, J'obtiens l'objet de données (prêt maintenant) à partir du thread et je le remplisse à l'interface utilisateur. C'est donc très similaire à votre exemple.

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

Ce que j'ai fait est montré ci-dessous. L'objectif est de remplir mon modèle de données (mDataObject) puis le remplir à l'interface utilisateur. Devrait permettre la rotation de l'écran à tout moment 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;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    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 s'il s'agit de la méthode "correcte" conçue par Android - ils affirment que "détruire / recréer l'activité pendant la rotation de l'écran" rend les choses plus faciles, donc je suppose que cela ne devrait pas être trop délicat.

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

Le problème perçu d'origine était que le code ne survivrait pas à un changement d'orientation d'écran. Apparemment, cela a été "résolu" en faisant en sorte que le programme gère l'orientation de l'écran change lui-même, au lieu de laisser le framework d'interface utilisateur le faire (via l'appel OnDestroy)).

Je soumettrais que si le problème sous-jacent est que le programme ne survivra pas à Ontroy (), alors la solution acceptée n'est qu'une solution de contournement qui laisse le programme avec d'autres problèmes et vulnérabilités. N'oubliez pas que le framework Android indique spécifiquement que votre activité est à risque d'être détruite presque à tout moment en raison de circonstances hors de votre contrôle. Par conséquent, votre activité doit être capable de survivre sur ONDESTROY () et sur onCreate () ultérieure pour quelque raison que ce soit, pas seulement un changement d'orientation d'écran.

Si vous allez accepter la manipulation de l'orientation de l'écran vous modifie pour résoudre le problème de l'OP, vous devez vérifier que d'autres causes d'Ondestroy () ne se traduisent pas par la même erreur. Êtes-vous capable de faire cela? Sinon, je me demanderais si la réponse "acceptée" est vraiment très bonne.

Ma solution était d'étendre le ProgressDialog classe pour obtenir le mien MyProgressDialog.
J'ai redéfini show() et dismiss() Méthodes pour verrouiller l'orientation avant de montrer le Dialog et le déverrouiller quand Dialog est rejeté. Alors quand le Dialog est montré et l'orientation du dispositif change, l'orientation de l'écran reste jusqu'à ce que dismiss() est appelé, puis l'orientation de l'écran change en fonction des valeurs capteurs / orientation de dispositif.

Voici mon code:

public class MyProgressDialog extends ProgressDialog {
private Context mContext;

public MyProgressDialog(Context context) {
    super(context);
    mContext = context;
}

public MyProgressDialog(Context context, int theme) {
    super(context, theme);
    mContext = context;
}

public void show() {
    if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    else
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    super.show();
}

public void dismiss() {
    super.dismiss();
    ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

}

J'ai fait face à ce même problème, et j'ai trouvé une solution qui n'a pas involé en utilisant le progressdialog et j'obtiens des résultats plus rapides.

Ce que j'ai fait, c'est de créer une mise en page qui contient une barre de progrès.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

Ensuite, dans la méthode onCreate, faites ce qui suit

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

Ensuite, effectuez la longue tâche dans un fil, et lorsque cela est terminé, faites régler la vue de contenu sur la mise en page réelle que vous souhaitez utiliser pour cette activité.

Par exemple:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

C'est ce que j'ai fait, et j'ai constaté qu'il fonctionnait plus vite que de montrer le progressdialog et qu'il est moins intrusif et a un meilleur regard à mon avis.

Cependant, si vous souhaitez utiliser le ProgressDialog, cette réponse n'est pas pour vous.

J'ai découvert une solution à ceci que je n'ai pas encore vu ailleurs. Vous pouvez utiliser un objet d'application personnalisé qui sait si vous avez des tâches de fond, au lieu d'essayer de le faire dans l'activité qui est détruite et recréée lors du changement d'orientation. J'ai blogué à ce sujet dans ici.

Je vais contribuer mon approche à la gestion de ce problème de rotation. Cela peut ne pas être pertinent pour OP car il n'utilise pas AsyncTask, mais peut-être que d'autres le trouveront utile. C'est assez simple mais il semble faire le travail pour moi:

J'ai une activité de connexion avec une AsyncTask classe appelée BackgroundLoginTask.

Dans mon BackgroundLoginTask Je ne fais rien de l'ordinaire, sauf pour ajouter un chèque nul lors de l'appel ProgressDialogLe licenciement:

@Override
protected void onPostExecute(Boolean result)
{    
if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
[...]
}

Il s'agit de gérer le cas où la tâche d'arrière-plan se termine tandis que le Activity n'est pas visible et, par conséquent, la boîte de dialogue Progress a déjà été rejetée par le onPause() méthode.

Ensuite, dans mes parents Activity classe, je crée des poignées statiques globales à mon AsyncTask classe et mon ProgressDialog (la AsyncTask, être imbriqué, peut accéder à ces variables):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Cela sert deux objectifs: d'abord, cela permet à mon Activity Pour toujours accéder au AsyncTask Objet Même à partir d'une nouvelle activité post-rotative. Deuxièmement, cela permet à mon BackgroundLoginTask pour accéder et rejeter le ProgressDialog Même après une rotation.

Ensuite, j'ajoute ceci à onPause(), faisant disparaître la boîte de dialogue de progression lorsque notre Activity quitte le premier plan (empêcher ce vilain "accident de fermeture"):

    if (pleaseWaitDialog != null)
    pleaseWaitDialog.dismiss();

Enfin, j'ai ce qui suit dans mon onResume() méthode:

if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }

Cela permet au Dialog réapparaître après le Activity est recréé.

Voici toute la classe:

public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    private Controller cont;

    // This is the app entry point.
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (CredentialsAvailableAndValidated())
        {
        //Go to main menu and don't run rest of onCreate method.
            gotoMainMenu();
            return;
        }
        setContentView(R.layout.login);
        populateStoredCredentials();   
    }

    //Save current progress to options when app is leaving foreground
    @Override
    public void onPause()
    {
        super.onPause();
        saveCredentialsToPreferences(false);
        //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }
    }

    /**
     * Go to main menu, finishing this activity
     */
    private void gotoMainMenu()
    {
        startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
        finish();
    }

    /**
     * 
     * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
     */
    private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
        SharedPreferences.Editor prefEditor = settings.edit();
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
        prefEditor.putString(USERNAME, usernameText.getText().toString());
        prefEditor.putString(PASSWORD, pswText.getText().toString());
        if (setValidatedBooleanTrue)
        prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
        prefEditor.commit();
    }

    /**
     * Checks if user is already signed in
     */
    private boolean CredentialsAvailableAndValidated() {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
        if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
         return true;   
        else
        return false;
    }

    //Populate stored credentials, if any available
    private void populateStoredCredentials()
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
            MODE_PRIVATE);
        settings.getString(USERNAME, "");
       EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
       usernameText.setText(settings.getString(USERNAME, ""));
       EditText pswText = (EditText) findViewById(R.id.editTextPassword);
       pswText.setText(settings.getString(PASSWORD, ""));
    }

    /**
     * Validate credentials in a seperate thread, displaying a progress circle in the meantime
     * If successful, save credentials in preferences and proceed to main menu activity
     * If not, display an error message
     */
    public void loginButtonClick(View view)
    {
        if (phoneIsOnline())
        {
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           //Call background task worker with username and password params
           backgroundLoginTask = new BackgroundLoginTask();
           backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
        }
        else
        {
        //Display toast informing of no internet access
        String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
        Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
        toast.show();
        }
    }

    /**
     * 
     * Takes two params: username and password
     *
     */
    public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
    {       
       private Exception e = null;

       @Override
       protected void onPreExecute()
       {
           cont = Controller.getInstance();
           //Show progress dialog
           String pleaseWait = getResources().getString(R.string.pleaseWait);
           String commWithServer = getResources().getString(R.string.communicatingWithServer);
            if (pleaseWaitDialog == null)
              pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);

       }

        @Override
        protected Boolean doInBackground(Object... params)
        {
        try {
            //Returns true if credentials were valid. False if not. Exception if server could not be reached.
            return cont.validateCredentials((String)params[0], (String)params[1]);
        } catch (Exception e) {
            this.e=e;
            return false;
        }
        }

        /**
         * result is passed from doInBackground. Indicates whether credentials were validated.
         */
        @Override
        protected void onPostExecute(Boolean result)
        {
        //Hide progress dialog and handle exceptions
        //Progress dialog may be null if rotation has been switched
        if (pleaseWaitDialog != null)
             {
            pleaseWaitDialog.dismiss();
                pleaseWaitDialog = null;
             }

        if (e != null)
        {
         //Show toast with exception text
                String networkError = getResources().getString(R.string.serverErrorException);
                Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
            toast.show();
        }
        else
        {
            if (result == true)
            {
            saveCredentialsToPreferences(true);
            gotoMainMenu();
            }
            else
            {
            String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
            toast.show();
            } 
        }
        }

    }
}

Je ne suis en aucun cas un développeur Android chevronné, alors n'hésitez pas à commenter.

Déplacez la longue tâche vers une classe séparée. Implémentez-le en tant que modèle Observateur sujet. Chaque fois que l'activité est créée, inscrivez-vous et fermez-vous en désinscrivant avec la classe de tâches. La classe de tâches peut utiliser AsyncTask.

L'astuce consiste à afficher / rejeter la boîte de dialogue au sein d'AsyncTask pendant onPreExEcute / Onpostexecute comme d'habitude, bien que dans le cas de changement d'orientation, créez / affichez une nouvelle instance de la boîte de dialogue dans l'activité et transmettez sa référence à la tâche.

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}

Je l'ai fait comme ceci:

    package com.palewar;
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;

    public class ThreadActivity extends Activity {


        static ProgressDialog dialog;
        private Thread downloadThread;
        final static Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

        protected void onDestroy() {
    super.onDestroy();
            if (dialog != null && dialog.isShowing()) {
                dialog.dismiss();
                dialog = null;
            }

        }

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            downloadThread = (Thread) getLastNonConfigurationInstance();
            if (downloadThread != null && downloadThread.isAlive()) {
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in...", false);
            }

            dialog = ProgressDialog.show(ThreadActivity.this, "",
                    "Signing in ...", false);

            downloadThread = new MyThread();
            downloadThread.start();
            // processThread();
        }

        // Save the thread
        @Override
        public Object onRetainNonConfigurationInstance() {
            return downloadThread;
        }


        static public class MyThread extends Thread {
            @Override
            public void run() {

                try {
                    // Simulate a slow network
                    try {
                        new Thread().sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);

                } finally {

                }
            }
        }

    }

Vous pouvez également essayer de me faire savoir que cela fonctionne pour vous ou non

Si vous créez un arrière-plan Service Cela fait tout le levage de lourds (requêtes / réponse TCP, Unmarshalling), le View et Activity Peut être détruit et recréé sans divulguer des fenêtres ou perdre des données. Cela permet au comportement recommandé par Android, à savoir Détruisez une activité sur chaque changement de configuration (par exemple pour chaque changement d'orientation).

C'est un peu plus complexe, mais c'est le meilleur moyen d'invoquer la demande de serveur, les données pré / post-traitement, etc.

Vous pouvez même utiliser votre Service Pour faire la queue de chaque demande à un serveur, il est donc facile et efficace de gérer ces choses.

Le Guide Dev a un complet chapitre sur Services.

J'ai une implémentation qui permet de détruire l'activité lors d'un changement d'orientation d'écran, mais détruit toujours la boîte de dialogue dans l'activité recréée avec succès. j'utilise ...NonConfigurationInstance Pour attacher la tâche d'arrière-plan à l'activité recréée. Le framework Android normal s'occupe de recréer la boîte de dialogue elle-même, rien ne y est changé.

J'ai sous-classé AsyncTask ajoutant un champ pour l'activité «propriétaire» et une méthode pour mettre à jour ce propriétaire.

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

Dans ma classe d'activités, j'ai ajouté un champ backgroundTask se référant à la background task `` possédé '' et je met à jour ce champ en utilisant onRetainNonConfigurationInstance et getLastNonConfigurationInstance.

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

Suggestions d'amélioration supplémentaire:

  • Effacer le backgroundTask Référence dans l'activité une fois la tâche terminée pour libérer n'importe quelle mémoire ou d'autres ressources qui y sont associées.
  • Effacer le ownerActivity Référence en arrière-plan avant que l'activité ne soit détruite au cas où elle ne sera pas recréée immédiatement.
  • Créer un BackgroundTask Interface et / ou collection pour permettre à différents types de tâches d'exécuter de la même activité de possession.

Si vous maintenez deux dispositions, tout le fil d'interface utilisateur doit être terminé.

Si vous utilisez Asyntask, vous pouvez facilement appeler .cancel() Méthode à l'intérieur onDestroy() Méthode d'activité actuelle.

@Override
protected void onDestroy (){
    removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
    if (loginTask != null){
        if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
            loginTask.cancel(true); //cancel AsyncTask
    }
    super.onDestroy();
}

Pour Asynctask, lisez la suite dans la section "Annuler une tâche" à ici.

Mise à jour:Ajout de l'état pour vérifier l'état, car il ne peut être annulé que s'il est en cours d'exécution. Notez également que l'AsyncTask ne peut être exécuté qu'une seule fois.

Essayé de mettre en œuvre jfelectronSolution parce que c'est un "Solution solide de roche à ces problèmes qui se conforment à la «voie Android» des choses"Mais il a fallu un certain temps pour lever les yeux et assembler tous les éléments mentionnés. s'est retrouvé avec cette solution légèrement différente, et je pense que plus élégante, une solution publiée ici dans son intégralité.

Utilise un service d'intention tiré d'une activité pour effectuer la longue tâche en cours d'exécution sur un thread séparé. Le service retient les intentions de diffusion collantes à l'activité qui met à jour la boîte de dialogue. L'activité utilise showDialog (), onCreateDialog () et onPrepareDialog () pour éliminer la nécessité de faire passer des données persistantes dans l'objet d'application ou le bundle SavedInstancetate. Cela devrait fonctionner, peu importe comment votre demande est interrompue.

Classe d'activité:

public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button b = (Button) this.findViewById(R.id.test_button);
    b.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            buttonClick();
        }
    });
}

private void buttonClick(){
    clearPriorBroadcast();
    showDialog(PROGRESS_DIALOG);
    Intent svc = new Intent(this, MyService.class);
    startService(svc);
}

protected Dialog onCreateDialog(int id) {
    switch(id) {
    case PROGRESS_DIALOG:
        mProgressDialog = new ProgressDialog(TesterActivity.this);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setMax(MyService.MAX_COUNTER);
        mProgressDialog.setMessage("Processing...");
        return mProgressDialog;
    default:
        return null;
    }
}

@Override
protected void onPrepareDialog(int id, Dialog dialog) {
    switch(id) {
    case PROGRESS_DIALOG:
        // setup a broadcast receiver to receive update events from the long running process
        IntentFilter filter = new IntentFilter();
        filter.addAction(MyService.BG_PROCESS_INTENT);
        registerReceiver(new MyBroadcastReceiver(), filter);
        break;
    }
}

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(MyService.KEY_COUNTER)){
            int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
            mProgressDialog.setProgress(count);
            if (count >= MyService.MAX_COUNTER){
                dismissDialog(PROGRESS_DIALOG);
            }
        }
    }
}

/*
 * Sticky broadcasts persist and any prior broadcast will trigger in the 
 * broadcast receiver as soon as it is registered.
 * To clear any prior broadcast this code sends a blank broadcast to clear 
 * the last sticky broadcast.
 * This broadcast has no extras it will be ignored in the broadcast receiver 
 * setup in onPrepareDialog()
 */
private void clearPriorBroadcast(){
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
    sendStickyBroadcast(broadcastIntent);
}}

Classe IntetenService:

public class MyService extends IntentService {

public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;

public MyService() {
  super("");
}

@Override
protected void onHandleIntent(Intent intent) {
    for (int i = 0; i <= MAX_COUNTER; i++) {
        Log.e("Service Example", " " + i);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(BG_PROCESS_INTENT);
        broadcastIntent.putExtra(KEY_COUNTER, i);
        sendStickyBroadcast(broadcastIntent);
    }
}}

Image de fichiers manifeste:

Avant la section de la demande:

uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"

section de demande intérieure

service android:name=".MyService"

Ceci est ma solution proposée:

  • Déplacez l'asynctasque ou le fil vers un fragment conservé, comme expliqué ici. Je crois que c'est une bonne pratique de déplacer tous les appels de réseau vers des fragments. Si vous utilisez déjà des fragments, l'un d'eux pourrait être rendu responsable des appels. Sinon, vous pouvez créer un fragment juste pour faire la demande, comme le propose l'article lié.
  • Le fragment utilisera une interface d'écoute pour signaler l'achèvement / défaillance de la tâche. Vous n'avez pas à vous soucier des changements d'orientation là-bas. Le fragment aura toujours le bon lien vers la boîte de dialogue actuelle de l'activité et de la progression peut reprendre en toute sécurité.
  • Faites de votre dialogue Progress un membre de votre classe. En fait, vous devriez le faire pour toutes les boîtes de dialogue. Dans la méthode onPause, vous devez les rejeter, sinon vous divulguerez une fenêtre sur le changement de configuration. L'État occupé doit être conservé par le fragment. Lorsque le fragment est attaché à l'activité, vous pouvez à nouveau afficher la boîte de dialogue Progress, si l'appel est toujours en cours d'exécution. UN void showProgressDialog() Une méthode peut être ajoutée à l'interface de l'écouteur des fragments-activité à cet effet.

J'ai fait face à la même situation. Ce que j'ai fait, c'est d'obtenir une seule instance pour ma boîte de dialogue Progress dans toute l'application.

Tout d'abord, j'ai créé une classe de dialogue pour obtenir une seule instance (singleton motif)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

Comme je le montre dans cette classe, j'ai la boîte de dialogue Progress comme attribut. Chaque fois que j'ai besoin de montrer une boîte de dialogue Progress, j'obtiens l'instance unique et je crée un nouveau ProgressDialog.

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Lorsque j'ai fini avec la tâche d'arrière-plan, j'appelle à nouveau l'instance unique et je rejette sa boîte de dialogue.

DialogSingleton.GetInstance().DialogDismiss(this);

J'enregistre l'état de la tâche d'arrière-plan dans mes préférences partagées. Quand je tourne l'écran, je demande si j'ai une tâche en cours d'exécution pour cette activité: (OnCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

Lorsque je commence à exécuter une tâche d'arrière-plan:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Quand j'ai fini d'exécuter une tâche d'arrière-plan:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

J'espère que ça aide.

C'est une très vieille question qui s'est posée sur la barre latérale pour une raison quelconque.

Si la tâche d'arrière-plan n'a besoin que de survivre pendant que l'activité est au premier plan, la "nouvelle" solution consiste à héberger le thread d'arrière-plan (ou, de préférence, AsyncTask) dans un fragment retenu, comme décrit dans ce guide du développeur et Nombreux questions et réponses.

Un fragment retenu survit si l'activité est détruite pour un changement de configuration, mais ne pas Lorsque l'activité est détruite en arrière-plan ou en pile arrière. Par conséquent, la tâche d'arrière-plan doit toujours être interrompue si isChangingConfigurations() est faux dans onPause().

Je suis plus frais dans Android et j'ai essayé cela et cela a fonctionné.

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}

J'ai tout essayé. Passé des jours à expérimenter. Je ne voulais pas empêcher l'activité de rotation. Mon scénario était:

  1. Une boîte de dialogue Progress montrant des informations dynamiques à l'utilisateur. EG: "Connexion au serveur ...", "Doading Data ...", etc.
  2. Un fil faisant les trucs lourds et mettant à jour la boîte de dialogue
  3. Mise à jour de l'interface utilisateur avec les résultats à la fin.

Le problème était que, lors de la rotation de l'écran, chaque solution du livre a échoué. Même avec la classe AsyncTask, qui est la bonne façon Android de gérer ces situations. Lors de la rotation de l'écran, le contexte actuel avec lequel le thread de départ fonctionne, a disparu et qui gâche la boîte de dialogue qui affiche. Le problème a toujours été la boîte de dialogue, peu importe le nombre d'astuces que j'ai ajoutées au code (passer de nouveaux contextes aux threads en cours d'exécution, conserver les états de threads à travers les rotations, etc ...). La complexité du code à la fin a toujours été énorme et il y avait toujours quelque chose qui pouvait mal tourner.

La seule solution qui a fonctionné pour moi était l'activité / dialogue. C'est simple et génial et tout est une preuve de rotation:

  1. Au lieu de créer une boîte de dialogue et de demander à le montrer, créez une activité qui a été définie dans le manifeste avec Android: thème = "@ Android: Style / Theme.Dialog". Donc, cela ressemble à une boîte de dialogue.

  2. Remplacer showDialog (dialog_id) par startActivityForResult (yourActivityDialog, yourCode);

  3. Utilisez OnActivityResult dans l'activité d'appel pour obtenir les résultats du fil d'exécution (même les erreurs) et mettez à jour l'interface utilisateur.

  4. Sur votre 'ActivityDialog', utilisez des threads ou AsyncTask pour exécuter de longues tâches et onRetainNonConfigurationInstance pour enregistrer l'état "Dialog" lors de la rotation de l'écran.

C'est rapide et fonctionne bien. J'utilise toujours des boîtes de dialogue pour d'autres tâches et l'AsyncTask pour quelque chose qui ne nécessite pas de boîte de dialogue constante à l'écran. Mais avec ce scénario, je vais toujours pour le modèle d'activité / dialogue.

Et, je ne l'ai pas essayé, mais il est même possible de bloquer cette activité / boîte de dialogue en rotation, lorsque le fil est en cours d'exécution, accélérant les choses, tout en permettant à l'activité d'appel de tourner.

De nos jours, il existe un moyen beaucoup plus distinct de gérer ces types de problèmes. L'approche typique est:

1. Assurez-vous que vos données sont correctement séparées de l'interface utilisateur:

Tout ce qui est un processus de fond devrait être dans un Fragment (Réglez ceci avec Fragment.setRetainInstance(). Cela devient votre «stockage de données persistant» où tout ce que les données basées que vous souhaitez conserver sont conservées sont conservées. Après l'événement de changement d'orientation, ce Fragment sera toujours accessible dans son état d'origine via un FragmentManager.findFragmentByTag() Appelez (lorsque vous le créez, vous devez lui donner une balise pas un identifiant car il n'est pas attaché à un View).

Voir le Gestion des modifications d'exécution Guide développé pour les informations sur le fait correctement et pourquoi c'est la meilleure option.

2. Assurez-vous que vous interfacez correctement et en toute sécurité entre les processus d'arrière-plan et votre interface utilisateur:

Vous devez inverse votre processus de liaison. Pour le moment, votre processus de fond s'attache à un View - Au lieu de cela votre View devrait s'attacher à se fixer au processus d'arrière-plan. Cela a plus de sens, non? La ViewL'action dépend du processus de fond, tandis que le processus de fond ne dépend pas du View. Cela signifie changer le lien vers une norme Listener interface. Dites votre processus (quelle que soit la classe qu'il s'agit - que ce soit un AsyncTask, Runnable ou peu importe) définit un OnProcessFinishedListener, lorsque le processus est terminé, il devrait appeler cet auditeur s'il existe.

Cette réponse est une belle description concise de la façon de faire des auditeurs personnalisés.

3. Liez votre interface utilisateur dans le processus de données chaque fois que l'interface utilisateur est créée (y compris les modifications d'orientation):

Vous devez maintenant vous soucier d'interfacer la tâche d'arrière-plan avec tout votre actuel View la structure est. Si vous gérez vos changements d'orientation correctement (pas le configChanges Hack les gens recommandent toujours), puis votre Dialog sera recréé par le système. Cela est important, cela signifie que sur le changement d'orientation, tous vos DialogLes méthodes de cycle de vie sont rappelées. Donc, dans l'une de ces méthodes (onCreateDialog est généralement un bon endroit), vous pourriez faire un appel comme le suivant:

DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
    f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
        public void onProcessFinished() {
            dismiss();
        }
    });
 }

Voir le Cycle de vie de fragment Pour décider où la définition de l'auditeur s'adapte au mieux dans votre implémentation individuelle.

Il s'agit d'une approche générale pour fournir une solution robuste et complète au problème générique posé dans cette question. Il manque probablement quelques pièces mineures dans cette réponse en fonction de votre scénario individuel, mais c'est généralement l'approche la plus correcte pour gérer correctement les événements de changement d'orientation.

J'ai trouvé une solution plus facile pour gérer les threads lorsque l'orientation change. Vous pouvez simplement garder une référence statique à votre activité / fragment et vérifier si c'est nul avant d'agir sur l'interface utilisateur. Je suggère d'utiliser un attrait d'essai aussi:

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

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

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}

Si vous avez du mal à détecter les événements de changement d'orientation d'une boîte de dialogue Indépendamment d'une référence d'activité, cette méthode fonctionne bien. J'utilise ceci parce que j'ai ma propre classe de dialogue qui peut être affichée dans plusieurs activités différentes, donc je ne sais pas toujours dans quelle activité il est montré. Avec cette méthode, vous n'avez pas besoin de changer AndroidManifest, vous inquiétez des références d'activité, Et vous n'avez pas besoin d'une boîte de dialogue personnalisée (comme moi). Vous avez cependant besoin d'une vue de contenu personnalisée afin que vous puissiez détecter les modifications d'orientation en utilisant cette vue particulière. Voici mon exemple:

Installer

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);

        //DO SOMETHING HERE!! :D
    }
}

Implémentation 1 - boîte de dialogue

Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();

Implémentation 2 - AlertDialog.builder

AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context));        //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();

Implémentation 3 - ProgressDialog / AlertDialog

ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context));        //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();

C'est ma solution quand je l'ai fait face:ProgressDialog n'est pas un Fragment enfant, donc ma classe personnalisée "ProgressDialogFragment"Peut s'étendre DialogFragment Au lieu de cela, afin de conserver la boîte de dialogue affichée pour les modifications de configuration.

import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle; 
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

 /**
 * Usage:
 * To display the dialog:
 *     >>> ProgressDialogFragment.showProgressDialogFragment(
 *              getSupportFragmentManager(), 
 *              "fragment_tag", 
 *              "my dialog title", 
 *              "my dialog message");
 *              
 * To hide the dialog
 *     >>> ProgressDialogFragment.hideProgressDialogFragment();
 */ 


public class ProgressDialogFragment extends DialogFragment {

    private static String sTitle, sMessage;
    private static ProgressDialogFragment sProgressDialogFragment;

    public ProgressDialogFragment() {
    }

    private ProgressDialogFragment(String title, String message) {
        sTitle = title;
        sMessage = message;
    }


    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return ProgressDialog.show(getActivity(), sTitle, sMessage);
    }

    public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
        if (sProgressDialogFragment == null) {
            sProgressDialogFragment = new ProgressDialogFragment(title, message);
            sProgressDialogFragment.show(fragmentManager, fragmentTag);

        } else { // case of config change (device rotation)
            sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
            sTitle = title;
            sMessage = message;
        }

    }

    public static void hideProgressDialogFragment() {
        if (sProgressDialogFragment != null) {
            sProgressDialogFragment.dismiss();
        }
    }
}

Le défi consistait à conserver le titre et le message de dialogue pendant la rotation de l'écran lorsqu'ils se réinitialisent à la chaîne vide par défaut, bien que la boîte de dialogue soit toujours affichée

Il y a 2 approches pour résoudre ce problème:

Première approche: Faire l'activité qui utilise la boîte de dialogue pour conserver l'état pendant le changement de configuration dans le fichier manifeste:

android:configChanges="orientation|screenSize|keyboardHidden"

Cette approche n'est pas préférée par Google.

Deuxième approche:sur l'activité onCreate() Méthode, vous devez conserver votre DialogFragment en reconstruisant le ProgressDialogFragment encore avec le titre et le message comme suit si le savedInstanceState est non nulle:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_deal);

 if (savedInstanceState != null) {
      ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
              .findFragmentByTag("fragment_tag");
      if (saveProgressDialog != null) {
          showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
      }
  }
}

Cela semble beaucoup trop `` rapide et sale '' pour être vrai, alors soulignez les défauts, mais ce que j'ai trouvé a fonctionné, c'était ...

Dans la méthode Onpostexecute de mon asynctask, j'ai simplement enveloppé le «.dismiss» pour la boîte de dialogue Progress dans un bloc d'essai / capture (avec une prise vide), puis j'ai simplement ignoré l'exception qui a été soulevée. Cela semble mal à faire, mais il semble qu'il n'y ait pas d'effets néfastes (du moins pour ce que je fais par la suite, c'est-à-dire pour commencer une autre activité qui passe dans le résultat de ma requête de longue date en tant que supplémentaire)

La solution la plus simple et la plus flexible consiste à utiliser un Asynctasque avec une référence statique à Barre de progression. Cela fournit une solution encapsulée et donc réutilisable aux problèmes de changement d'orientation. Cette solution m'a bien servi pour différentes tâches asyncrones, y compris les téléchargements sur Internet, la communication avec Prestations de service, et scans du système de fichiers. La solution a été bien testée sur plusieurs versions Android et modèles de téléphone. Une démo complète peut être trouvée ici avec un intérêt spécifique pour Downloadfile.java

Je présente ce qui suit comme un exemple de concept

public class SimpleAsync extends AsyncTask<String, Integer, String> {
    private static ProgressDialog mProgressDialog = null;
    private final Context mContext;

    public SimpleAsync(Context context) {
        mContext = context;
        if ( mProgressDialog != null ) {
            onPreExecute();
        }
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog( mContext );
        mProgressDialog.show();
    }

    @Override
    protected void onPostExecute(String result) {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        mProgressDialog.setProgress( progress[0] );
    }

    @Override
    protected String doInBackground(String... sUrl) {
        // Do some work here
        publishProgress(1);
        return null;
    }

    public void dismiss() {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
        }
    }
}

L'utilisation dans une activité Android est simple

public class MainActivity extends Activity {
    DemoServiceClient mClient = null;
    DownloadFile mDownloadFile = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        mDownloadFile = new DownloadFile( this );

        Button downloadButton = (Button) findViewById( R.id.download_file_button );
        downloadButton.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
            }
        });
    }

    @Override
    public void onPause() {
        super.onPause();
        mDownloadFile.dismiss();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top