Wie gehe ich mit einer Änderung der Bildschirmausrichtung um, wenn der Fortschrittsdialog und der Hintergrundthread aktiv sind?

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

Frage

Mein Programm führt einige Netzwerkaktivitäten in einem Hintergrundthread aus.Vor dem Start wird ein Fortschrittsdialog angezeigt.Der Dialog wird im Handler geschlossen.Das funktioniert alles einwandfrei, außer wenn sich die Bildschirmausrichtung ändert, während der Dialog aktiv ist (und der Hintergrundthread läuft).An diesem Punkt stürzt die App entweder ab, gerät in einen Deadlock oder gerät in eine seltsame Phase, in der die App überhaupt nicht funktioniert, bis alle Threads beendet wurden.

Wie kann ich mit der Änderung der Bildschirmausrichtung elegant umgehen?

Der folgende Beispielcode entspricht ungefähr dem, was mein echtes Programm tut:

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

Stapel:

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)

Ich habe versucht, den Fortschrittsdialog in onSaveInstanceState zu schließen, aber das verhindert lediglich einen sofortigen Absturz.Der Hintergrundthread läuft noch und die Benutzeroberfläche befindet sich in einem teilweise gezeichneten Zustand.Sie müssen die gesamte App beenden, bevor sie wieder funktioniert.

War es hilfreich?

Lösung

Wenn Sie die Orientierungen wechseln, erstellt Android eine neue Ansicht. Sie erhalten wahrscheinlich Abstürze, weil Ihr Hintergrund -Thread versucht, den Status auf dem alten zu ändern. (Es kann auch Probleme haben, weil Ihr Hintergrund -Thread nicht auf dem UI -Thread liegt.)

Ich würde vorschlagen, dass Mhandler volatile und aktualisiert wird, wenn sich die Orientierung ändert.

Andere Tipps

Bearbeiten: Google Engineers empfehlen diesen Ansatz nicht, wie von Dianne Hackborn (auch bekannt als Hackbod) in diesem Stackoverflow -Post. Kasse Dieser Blog -Beitrag für mehr Informationen.


Sie müssen dies der Aktivitätserklärung im Manifest hinzufügen:

android:configChanges="orientation|screenSize"

Also sieht es aus wie aus

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

Die Angelegenheit ist, dass das System die Aktivität zerstört, wenn eine Änderung der Konfiguration auftritt. Sehen KonfigurationChanges.

Wenn Sie dies in die Konfigurationsdatei einfügen, wird das System vermieden, um Ihre Aktivität zu zerstören. Stattdessen ruft es die auf onConfigurationChanged(Configuration) Methode.

Ich habe eine rock-solide Lösung für diese Probleme entwickelt, die sich der "Android-Art" der Dinge entsprechen. Ich habe alle meine langlebigen Operationen mit dem IntentService-Muster.

Das heißt, meine Aktivitäten Broadcast -Absichten, der Inventservice erledigt die Arbeit, speichert die Daten in der DB und sendet dann die Sendung klebrig Absichten. Der klebrige Teil ist wichtig, so dass, selbst wenn die Aktivität während der Zeit nach dem Initiieren des Benutzers die Arbeit in Anspruch genommen und die Echtzeit -Sendung aus dem Interservice verpasst, immer noch antworten und die Daten aus der Anrufaktivität abholen können. ProgressDialogS kann mit diesem Muster ziemlich gut mit arbeiten onSaveInstanceState().

Grundsätzlich müssen Sie ein Flag speichern, dass Sie im gespeicherten Instanzpaket einen Fortschrittsdialog erhalten. Unterlassen Sie Speichern Sie das Dialogobjekt des Fortschritts, da dies die gesamte Aktivität ausgeht. Um einen anhaltenden Handlungen zum Fortschrittsdialog zu haben, speichere ich ihn als schwache Referenz im Anwendungsobjekt. Bei Orientierungsänderung oder irgendetwas anderem, das die Aktivität innehalt (Telefonanruf, Benutzer zu Hause usw.) und dann wieder aufnehmen, lehne ich den alten Dialog entlassen und einen neuen Dialog in der neu erstellten Aktivität neu erstelle.

Für unbestimmte Fortschrittsdialoge ist dies einfach. Für den Fortschrittsbalkenstil müssen Sie den letzten bekannten Fortschritt in das Bundle und alle Informationen, die Sie lokal in der Aktivität verwenden, einsetzen, um den Fortschritt zu verfolgen. Wenn Sie den Fortschritt wiederherstellen, verwenden Sie diese Informationen, um die Fortschrittsleiste im selben Zustand wie zuvor erneut zu lehren und dann basierend auf dem aktuellen Stand der Dinge zu aktualisieren.

Also zusammenfassen und langlebige Aufgaben in einen Abstandsservice in Verbindung mit vernünftigem Gebrauch von gestalten onSaveInstanceState() Ermöglicht Ihnen, Dialoge effizient im Auge zu behalten und dann über die Aktivitäts-Lebenszyklus-Ereignisse zurückzuführen. Relevante Bits von Aktivitätscode finden Sie unten. Sie benötigen auch Logik in Ihrem Rundfunkveranstalter, um angemessen mit klebrigen Absichten umzugehen, aber das liegt außerhalb des Rahmens davon.

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

Ich bin auf das gleiche Problem gestoßen.Meine Aktivität muss einige Daten von einer URL analysieren und ist langsam.Dazu erstelle ich einen Thread und zeige dann einen Fortschrittsdialog an.Ich lasse den Thread über eine Nachricht an den UI-Thread zurücksenden Handler wenn es fertig ist.In Handler.handleMessage, erhalte ich das Datenobjekt (jetzt bereit) vom Thread und fülle es in die Benutzeroberfläche ein.Es ist also Ihrem Beispiel sehr ähnlich.

Nach vielen Versuchen und Irrtümern scheint es so, als hätte ich eine Lösung gefunden.Zumindest kann ich den Bildschirm jetzt jederzeit drehen, bevor oder nachdem der Thread fertig ist.In allen Tests wird der Dialog ordnungsgemäß geschlossen und alle Verhaltensweisen sind wie erwartet.

Was ich getan habe, wird unten gezeigt.Das Ziel ist es, mein Datenmodell zu füllen (mDataObject) und füllen Sie es dann in die Benutzeroberfläche ein.Sollte eine Bildschirmdrehung jederzeit und ohne Überraschung ermöglichen.

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.
    }
}

Das ist es, was bei mir funktioniert.Ich weiß nicht, ob dies die „richtige“ Methode ist, wie sie von Android entwickelt wurde – sie behaupten, dass diese „Aktivität während der Bildschirmdrehung zerstören/neu erstellen“ die Dinge tatsächlich einfacher macht, also denke ich, dass es nicht zu schwierig sein sollte.

Lassen Sie mich wissen, wenn Sie ein Problem in meinem Code sehen.Wie oben erwähnt, weiß ich nicht wirklich, ob es irgendwelche Nebenwirkungen gibt.

Das ursprüngliche wahrgenommene Problem war, dass der Code eine Änderung der Bildschirmausrichtung nicht überleben würde. Anscheinend wurde dies "gelöst", indem das Programm die Bildschirmausrichtung ändern ließ, anstatt das UI -Framework (über das Aufrufen von OneDestroy)).

Ich würde angeben, dass die akzeptierte Lösung nur eine Problemumgehung ist, die das Programm mit schwerwiegenden Problemen und Schwachstellen überliefert, wenn das zugrunde liegende Problem nicht überlebt, dass das Programm nicht überlebt. Denken Sie daran, dass im Android -Framework ausdrücklich angegeben ist, dass Ihre Aktivität aufgrund von Umständen außerhalb Ihrer Kontrolle zu jeder Zeit gefährdet ist. Daher muss Ihre Aktivität in der Lage sein, ONDestroy () und anschließend OnCreate () aus irgendeinem Grund zu überleben, nicht nur eine Änderung der Bildschirmausrichtung.

Wenn Sie die Ausrichtung des Handlingsbildschirms akzeptieren, um das Problem des OP zu lösen, müssen Sie überprüfen, ob andere Ursachen von OnDestroy () nicht zu demselben Fehler führen. Können Sie das tun? Wenn nicht, würde ich mich fragen, ob die "akzeptierte" Antwort wirklich sehr gut ist.

Meine Lösung war es, die zu erweitern ProgressDialog Klasse, um meine eigenen zu bekommen MyProgressDialog.
Ich definierte neu show() und dismiss() Methoden zur Sperrung der Orientierung, bevor Sie das zeigen Dialog und schalte es zurück, wenn Dialog wird entlassen. Also wenn das Dialog wird angezeigt und die Ausrichtung des Geräts ändert sich, die Ausrichtung des Bildschirms bleibt bis dismiss() wird genannt, dann ändert sich Bildschirm-Orientierungsänderungen gemäß Sensorwerten/Geräteorientierung.

Hier ist mein 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);
}

}

Ich habe dem gleichen Problem konfrontiert und habe eine Lösung entwickelt, die nicht mit dem ProgressDialog involiert ist, und ich erzielte schnellere Ergebnisse.

Was ich tat, war ein Layout, das eine Fortschrittsbar enthält.

<?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>

Dann in der OnCreate -Methode Folgendes machen

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

Lassen Sie dann die lange Aufgabe in einem Thread ausführen, und wenn dies fertig ist, haben Sie eine runnable die Inhaltsansicht auf das reale Layout ein, das Sie für diese Aktivität verwenden möchten.

Zum Beispiel:

mHandler.post(new Runnable(){

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

Das habe ich getan, und ich habe festgestellt, dass es schneller läuft, als den ProgressDialog zu zeigen und weniger aufdringlich zu sein und meiner Meinung nach ein besseres Aussehen zu haben.

Wenn Sie jedoch den ProgressDialog verwenden möchten, ist diese Antwort nicht für Sie.

Ich habe eine Lösung dafür entdeckt, die ich noch nicht anderswo gesehen habe. Sie können ein benutzerdefiniertes Anwendungsobjekt verwenden, das weiß, ob Sie Hintergrundaufgaben haben, anstatt dies in der Aktivität zu tun, die bei der Änderung der Orientierungsänderung zerstört und nachgebildet wird. Ich habe darüber gebloggt hier.

Ich werde meinen Ansatz zur Behandlung dieses Rotationsproblems beitragen. Dies ist möglicherweise nicht relevant für OP, da er nicht verwendet AsyncTask, aber vielleicht werden andere es nützlich finden. Es ist ziemlich einfach, aber es scheint den Job für mich zu machen:

Ich habe eine Anmeldeaktivität mit einem verschachtelten AsyncTask Klasse gerufen BackgroundLoginTask.

In meinem BackgroundLoginTask Ich mache nichts Außergewöhnliches, außer um eine Nullprüfung beim Anruf hinzuzufügen ProgressDialogEntlassung:

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

Hier Activity ist nicht sichtbar und daher wurde der Fortschrittsdialog bereits von der abgewiesen onPause() Methode.

Als nächstes in meinem Elternteil Activity Klasse, ich erstelle globale statische Handles zu meinem AsyncTask Klasse und mein ProgressDialog (das AsyncTask, verschachtelt, kann auf diese Variablen zugreifen):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Dies dient zwei Zwecken: Erstens erlaubt es meine Activity immer auf die zugreifen AsyncTask Objekt auch aus einer neuen postrotten Aktivität. Zweitens erlaubt es meins BackgroundLoginTask Zugriff auf die ProgressDialog Auch nach einem Drehen.

Als nächstes füge ich das hinzu zu onPause(), was dazu führt, dass der Fortschrittsdialog verschwindet, wenn unsere Activity verlässt den Vordergrund (verhindern Sie, dass diese hässliche "Kraft schließen" Crash abgeschlossen):

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

Schließlich habe ich Folgendes in meinem onResume() Methode:

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

Dies erlaubt das Dialog nach dem wieder erscheinen Activity wird neu erstellt.

Hier ist die gesamte Klasse:

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

    }
}

Ich bin keineswegs ein erfahrener Android -Entwickler. Sie können gerne kommentieren.

Verschieben Sie die lange Aufgabe in eine separate Klasse. Implementieren Sie es als Subjekt-Observer-Muster. Immer wenn die Aktivität erstellt wird, registrieren Sie sich und schließen beim Schließen der Aufgabenklasse. Aufgabenklasse kann Asynctask verwenden.

Der Trick besteht darin, den Dialog innerhalb von Asynctask während der ONPREEXECUTE/onPostExecute wie gewohnt anzuzeigen, jedoch im Falle einer Orientierungsveränderung eine neue Instanz des Dialogs in der Aktivität zu erstellen/anzeigen und seine Referenz auf die Aufgabe weitergeben.

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

Ich habe es so gemacht:

    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 {

                }
            }
        }

    }

Sie können auch versuchen, mich wissen zu lassen, dass es für Sie funktioniert oder nicht

Wenn Sie einen Hintergrund erstellen Service Das macht das ganze schwere Heben (TCP -Anforderungen/Antwort, Unmarshalling), die View und Activity kann zerstört und neu erstellt werden, ohne ein undesendes Fenster zu entfernen oder Daten zu verlieren. Dies ermöglicht das von Android empfohlene Verhalten, das danach ist Zerstören Sie eine Aktivität bei jeder Konfigurationsänderung (zB für jede Orientierungsänderung).

Es ist etwas komplexer, aber es ist der beste Weg, um Serveranforderungen, Daten vor/Nachbearbeitung usw. aufzurufen.

Sie können sogar Ihre verwenden Service Um jede Anfrage an einen Server zu stapfen, macht es so einfach und effizient, diese Dinge zu behandeln.

Der Dev Guide hat eine vollständige Kapitel über Services.

Ich habe eine Implementierung, die es ermöglicht, die Aktivität bei einer Bildschirmausrichtung zu zerstören, zerstört aber den Dialog in der nachgebildeten Aktivität erfolgreich. ich benutze ...NonConfigurationInstance Um die Hintergrundaufgabe an die nachgebildete Aktivität zu verbinden. Das normale Android -Framework verarbeitet den Nachbau des Dialogs selbst, nichts wird dort geändert.

Ich habe Asynctask unterklassifiziert und ein Feld für die "Besitz" -Aktivität und eine Methode zur Aktualisierung dieses Eigentümers hinzugefügt.

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

  ...
}

In meiner Aktivitätsklasse habe ich ein Feld hinzugefügt backgroundTask In Bezug auf den Hintergrund "im Besitz" des Hintergrunds und ich aktualisiere dieses Feld mithilfe onRetainNonConfigurationInstance und 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;
  }
  ...
}

Vorschläge zur weiteren Verbesserung:

  • Löschen die backgroundTask Die Referenz in der Aktivität nach der Aufgabe ist abgeschlossen, um einen Speicher oder andere damit verbundene Ressourcen freizugeben.
  • Löschen die ownerActivity Referenz im Hintergrund, bevor die Aktivität zerstört wird, falls sie nicht sofort neu erstellt wird.
  • Ein ... kreieren BackgroundTask Schnittstelle und/oder Sammlung, damit verschiedene Arten von Aufgaben aus derselben Besitzaktivität ausgeführt werden.

Wenn Sie zwei Layouts beibehalten, sollte der gesamte UI -Thread beendet werden.

Wenn Sie Asyntask verwenden, können Sie problemlos anrufen .cancel() Methode im Inneren onDestroy() Methode der aktuellen Aktivität.

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

Für Asynctask lesen hier.

Aktualisieren:Bedingung hinzugefügt, um den Status zu überprüfen, da er nur storniert werden kann, wenn er sich im laufenden Status befindet. Beachten Sie auch, dass der Asynctask nur einmal ausgeführt werden kann.

Versucht zu implementieren JFELECTRON's Lösung, weil es ein "ist"Rock-Solid-Lösung für diese Probleme, die mit der "Android-Art" der Dinge entsprechen"Aber es dauerte einige Zeit, um nachzuschlagen und alle genannten Elemente zusammenzustellen. Am Ende etwas anderes und ich denke, eleganter, hier in ihrer Gesamtheit veröffentlichte Lösung.

Verwendet einen von einer Aktivität abgefeuerten Inspektservice, um die lang laufende Aufgabe in einem separaten Thread auszuführen. Der Service feuert die Absichten der Sticky Broadcast in die Aktivität zurück, die den Dialog aktualisiert. Die Aktivität verwendet ShowDialog (), OnCreateTialog () und OnPreparedialog (), um die Notwendigkeit zu beseitigen, anhaltende Daten im Anwendungsobjekt oder im SavedInstancestate -Bündel übergeben zu haben. Dies sollte funktionieren, egal wie Ihre Bewerbung unterbrochen wird.

Aktivitätsklasse:

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

InVentservice -Klasse:

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

Manifestierte Dateieinträge:

Vor dem Antragsabschnitt:

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

Innerhalb von Anwendungsabschnitt

service android:name=".MyService"

Dies ist meine vorgeschlagene Lösung:

  • Bewegen Sie den Asynctask oder den Faden in ein zurückhaltendes Fragment, wie erläutert hier. Ich glaube, es ist eine gute Praxis, alle Netzwerkanrufe in Fragmente zu verschieben. Wenn Sie bereits Fragmente verwenden, könnte einer von ihnen für die Anrufe verantwortlich gemacht werden. Andernfalls können Sie ein Fragment nur für die Anfrage erstellen, wie der verknüpfte Artikel vorschlägt.
  • Das Fragment verwendet eine Listener -Schnittstelle, um die Aufgabenabschluss/den Aufgabe zu signalisieren. Sie müssen sich dort keine Sorgen um Orientierungsänderungen machen. Das Fragment wird immer die richtige Verbindung zum aktuellen Aktivität haben und der Fortschrittsdialog kann sicher wieder aufgenommen werden.
  • Machen Sie Ihren Fortschrittsdialog zu einem Mitglied Ihrer Klasse. Tatsächlich sollten Sie das für alle Dialoge tun. In der OnPause -Methode sollten Sie sie entlassen. Andernfalls werden Sie ein Fenster für die Konfigurationsänderung durchlaufen. Der geschäftige Zustand sollte vom Fragment aufbewahrt werden. Wenn das Fragment an die Aktivität angeschlossen ist, können Sie den Fortschrittsdialog erneut aufrufen, wenn der Anruf noch ausgeführt wird. EIN void showProgressDialog() Die Methode kann zu diesem Zweck zur Schnittstelle zwischen Fragment-Activity-Hörer hinzugefügt werden.

Ich sah mich der gleichen Situation aus. Was ich tat, war nur eine Instanz für meinen Fortschrittsdialog in der gesamten Anwendung.

Zuerst habe ich eine Dialogsingleton -Klasse erstellt, um nur eine Instanz zu erhalten (Singleton -Muster)

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

Wie ich in dieser Klasse zeige, habe ich den Fortschrittsdialog als Attribut. Jedes Mal, wenn ich einen Fortschrittsdialog zeigen muss, erhalte ich die eindeutige Instanz und erstelle einen neuen FortschrittsDialog.

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

Wenn ich mit der Hintergrundaufgabe fertig bin, rufe ich erneut die eindeutige Instanz an und lehne seinen Dialog entlassen.

DialogSingleton.GetInstance().DialogDismiss(this);

Ich speichere den Status des Hintergrundaufgabens in meinen gemeinsamen Einstellungen. Wenn ich den Bildschirm rotiere, frage ich, ob ich eine Aufgabe für diese Aktivität habe: (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).

Wenn ich eine Hintergrundaufgabe ausführe:

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

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

Wenn ich eine Hintergrundaufgabe ausführt:

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

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

Ich hoffe, es hilft.

Dies ist eine sehr alte Frage, die aus irgendeinem Grund in der Seitenleiste aufgetaucht ist.

Wenn die Hintergrundaufgabe nur überleben muss, während die Aktivität im Vordergrund steht, besteht die "neue" Lösung darin, den Hintergrund -Thread zu hosten (oder vorzugsweise, AsyncTask) in einem zurückgehaltenes Fragment, wie in diesem beschrieben Entwicklerleitfaden und zahlreiche Q & AS AS.

Ein zurückgehaltenes Fragment überlebt, wenn die Aktivität für eine Konfigurationsänderung zerstört wird, aber jedoch nicht Wenn die Aktivität im Hintergrund oder im Rückenstapel zerstört wird. Daher sollte die Hintergrundaufgabe weiterhin unterbrochen werden, wenn isChangingConfigurations() ist falsch in onPause().

Ich bin ein Frischer in Android und ich habe es versucht und es hat funktioniert.

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

Ich habe alles versucht. Tage zu experimentieren. Ich wollte die Aktivität nicht vom Drehen blockieren. Mein Szenario war:

  1. Ein Fortschrittsdialog, das dem Benutzer dynamische Informationen zeigt. ZB: "Verbindung zum Server herstellen ...", "Daten herunterladen ...", usw.
  2. Ein Thread, der das schwere Sachen macht und den Dialog aktualisiert
  3. Aktualisieren der Benutzeroberfläche mit den Ergebnissen am Ende.

Das Problem war, dass beim Drehen des Bildschirms jede Lösung des Buches fehlgeschlagen war. Auch mit der Asynctask -Klasse, die die richtige Android -Art des Umgangs mit diesen Situationen ist. Beim Drehen des Bildschirms ist der aktuelle Kontext, mit dem der Startfaden arbeitet, verschwunden und mit dem angezeigten Dialog durcheinander. Das Problem war immer der Dialog, unabhängig davon, wie viele Tricks ich dem Code hinzugefügt habe (neue Kontexte an laufende Threads übergeben, Thread -Zustände durch Rotationen usw. erhalten). Die Codekomplexität am Ende war immer riesig und es gab immer etwas, das schief gehen konnte.

Die einzige Lösung, die für mich funktioniert hat, war der Aktivitäts-/Dialog -Trick. Es ist einfach und genial und alles rotationsnachweis:

  1. Anstatt einen Dialog zu erstellen und zu fragen, um es anzuzeigen, erstellen Sie eine Aktivität, die im Manifest mit Android: thema = "@Android: style/thema.dialog" festgelegt wurde. Also sieht es einfach aus wie ein Dialog.

  2. Ersetzen Sie ShowDialog (dialog_id) durch startActivityForresult (yourArctivityDialog, yourCode);

  3. Verwenden Sie OnactivityResult in der Anrufaktivität, um die Ergebnisse aus dem ausführenden Thread (sogar der Fehler) zu erhalten und die Benutzeroberfläche zu aktualisieren.

  4. Verwenden Sie auf Ihrem 'ActivityDialog' Threads oder Asynctask, um lange Aufgaben und OnretainnonConfigurationInstance auszuführen, um beim Drehen des Bildschirms "Dialog" zu speichern.

Das ist schnell und funktioniert gut. Ich verwende immer noch Dialoge für andere Aufgaben und das Asynctask für etwas, das auf dem Bildschirm keinen konstanten Dialogfeld erfordert. Aber mit diesem Szenario entscheide ich mich immer für das Aktivitäts-/Dialogmuster.

Und ich habe es nicht versucht, aber es ist sogar möglich, diesen Aktivität/diesen Dialog vom Drehen zu blockieren, wenn der Faden ausgeführt wird, um die Dinge zu beschleunigen und gleichzeitig die Aufrufaktivität zu drehen.

Heutzutage gibt es eine viel deutlichere Möglichkeit, diese Art von Problemen zu behandeln. Der typische Ansatz ist:

1. Stellen Sie sicher, dass Ihre Daten ordnungsgemäß von der Benutzeroberfläche getrennt sind:

Alles, was ein Hintergrundprozess ist, sollte beibehalten sein Fragment (Stellen Sie dies mit Fragment.setRetainInstance(). Dies wird zu Ihrer "persistenten Datenspeicherung", bei der alles, was Sie beibehalten möchten, aufbewahrt wird. Nach dem Ereignis zur Veränderung des Orientierung Fragment wird in seinem ursprünglichen Zustand immer noch durch a zugänglich sein FragmentManager.findFragmentByTag() Rufen Sie an (Wenn Sie es erstellen, sollten Sie ihm ein Tag geben keine ID da es nicht an a gebunden ist View).

Siehe das Umgang mit Laufzeitänderungen Entwickelter Leitfaden, um Informationen dazu richtig zu erledigen und warum dies die beste Option ist.

2. Stellen Sie sicher, dass Sie zwischen den Hintergrundprozessen und Ihrer Benutzeroberfläche korrekt und sicher anpassen:

Du musst umkehren Ihr Verknüpfungsprozess. Im Moment verbindet sich Ihr Hintergrundprozess an a View - Stattdessen deine View sollte sich an den Hintergrundprozess anbringen. Es macht mehr Sinn, oder? Das ViewDie Aktion hängt vom Hintergrundprozess ab, während der Hintergrundprozess nicht von der abhängig ist View.Dieis bedeutet, den Link zu einem Standard zu ändern Listener Schnittstelle. Sagen Sie Ihren Prozess (unabhängig davon, ob es ein ist - ob es ein ist AsyncTask, Runnable oder was auch immer) definiert a OnProcessFinishedListener, Wenn der Vorgang erledigt ist, sollte es diesen Hörer nennen, wenn er existiert.

Dies Antworten ist eine schöne, prägnante Beschreibung, wie man benutzerdefinierte Zuhörer macht.

3. Verknüpfen Sie Ihre Benutzeroberfläche mit in den Datenprozess, wenn die Benutzeroberfläche erstellt wird (einschließlich Änderungen der Orientierung):

Jetzt müssen Sie sich Sorgen machen, dass Sie die Hintergrundaufgabe mit dem aktuellen Strom mit der Vernichtung der Hintergrundversorgung zusammenfassen müssen View Struktur ist. Wenn Sie mit Ihren Orientierungsänderungen umgehen richtig (nicht der configChanges Hack Leute empfehlen immer), dann deine Dialog wird vom System nachgebildet. Dies ist wichtig, was bedeutet, dass sich bei der Orientierungsänderung alle Ihre ändern DialogDie Lebenszyklusmethoden werden zurückgerufen. Also in einer dieser Methoden (onCreateDialog ist normalerweise ein guter Ort), Sie können einen Anruf wie folgt machen:

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

Siehe das Fragment -Lebenszyklus Um zu entscheiden, wo der Hörer am besten in Ihre individuelle Implementierung passt.

Dies ist ein allgemeiner Ansatz, um eine robuste und vollständige Lösung für das in dieser Frage gestellte generische Problem zu bieten. In dieser Antwort fehlen wahrscheinlich ein paar kleinere Teile, je nach Ihrem individuellen Szenario. Dies ist jedoch im Allgemeinen der korrektste Ansatz für die ordnungsgemäße Handhabung von Ereignissen zur Ausrichtung.

Ich habe eine Lösung gefunden und leichter, um Fäden zu verarbeiten, wenn sich die Orientierungsänderung ändert. Sie können einfach einen statischen Hinweis auf Ihre Aktivität/Ihr Fragment behalten und überprüfen, ob es null ist, bevor Sie auf die Benutzeroberfläche wirken. Ich schlage vor, auch einen Versuch zu fangen:

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


}

Wenn Sie mit der Erkennung von Orientierungsveränderung Ereignisse eines Dialogs zu kämpfen haben Unabhängig von einer Aktivitätsreferenz, Diese Methode funktioniert aufregend gut. Ich benutze dies, weil ich meine eigene Dialogklasse habe, die in mehreren verschiedenen Aktivitäten angezeigt werden kann, sodass ich nicht immer weiß, in welcher Aktivität sie gezeigt wird. Mit dieser Methode müssen Sie das Androidmanifest nicht ändern, machen Sie sich Sorgen um Aktivitätsreferenzen. Und Sie brauchen keinen benutzerdefinierten Dialog (wie ich). Sie müssen jedoch eine benutzerdefinierte Inhaltsansicht benötigen, damit Sie die Ausrichtungsänderungen mit dieser bestimmten Ansicht erkennen können. Hier ist mein Beispiel:

Konfiguration

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

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

        //DO SOMETHING HERE!! :D
    }
}

Implementierung 1 - Dialog

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

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

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

This is my solution when I faced it: ProgressDialog is not a Fragment child, so my custom class "ProgressDialogFragment" can extend DialogFragment instead in order to keep the dialog shown for configuration changes.

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

The challenge was to retain the dialog title & message while screen rotation as they reset to the default empty string, although the dialog still shown

There are 2 approaches to solve this:

First approach: Make the activity that utilizes the dialog to retain state during config change in manifest file:

android:configChanges="orientation|screenSize|keyboardHidden"

This approach is not preferred by Google.

Second approach: on the activity's onCreate() method, you need to retain your DialogFragment by rebuilding the ProgressDialogFragment again with the title & message as follows if the savedInstanceState is not null:

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

Seems far too 'quick and dirty' to be true so please point out the flaws but what I found worked was...

Within the onPostExecute method of my AsyncTask, I simply wrapped the '.dismiss' for the progress dialog in a try/catch block (with an empty catch) and then simply ignored the exception that was raised. Seems wrong to do but appears there are no ill effects (at least for what I am doing subsequently which is to start another activity passing in the result of my long running query as an Extra)

The simplest and most flexible solution is to use an AsyncTask with a static reference to ProgressBar. This provides an encapsulated and thus reusable solution to orientation change problems. This solution has served me well for varying asyncronous tasks including internet downloads, communicating with Services, and filesystem scans. The solution has been well tested on multiple android versions and phone models. A complete demo can be found here with specific interest in DownloadFile.java

I present the following as a concept example

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

Usage in an Android Activity is 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();
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top