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


524

Mein Programm führt einige Netzwerkaktivitäten in einem Hintergrundthread durch. Vor dem Start wird ein Fortschrittsdialog angezeigt. Der Dialog wird auf dem Handler geschlossen. Dies alles funktioniert einwandfrei, außer wenn sich die Bildschirmausrichtung ändert, während der Dialog geöffnet ist (und der Hintergrund-Thread ausgeführt wird). Zu diesem Zeitpunkt stürzt die App entweder ab oder blockiert sie oder gerät in eine seltsame Phase, in der die App erst funktioniert, wenn alle Threads beendet wurden.

Wie kann ich mit der Änderung der Bildschirmausrichtung ordnungsgemäß umgehen?

Der folgende Beispielcode entspricht in etwa dem, was mein reales 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 nur einen sofortigen Absturz. Der Hintergrund-Thread 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.


1
In Anbetracht der Antworten, die Sie erhalten haben, sollten Sie die akzeptierte Antwort zugunsten der Besten ändern, nicht wahr?
rds

Siehe auch eine frühere Frage stackoverflow.com/questions/456211/…
rds

3
Alle haben eine wirklich gute Erklärung und mögliche Lösungen für dieses Problem. Gehen Sie durch http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Ich muss wissen, ob dies geholfen hat.
Arcamax

2
In diesem Blog-Beitrag finden Sie eine ziemlich vollständige Erklärung, wie asynchrone Hintergrundaufgaben über Bildschirmausrichtungen hinweg beibehalten werden können . Hör zu!
Adrian Monk

Setzen Sie einfach android: configChanges = "Orientierung | Bildschirmgröße" auf Aktivität im Manifest. Es stoppt Android, um seine Aktivität wiederherzustellen
Jawad Zeb

Antworten:


155

Wenn Sie die Ausrichtung ändern, erstellt Android eine neue Ansicht. Wahrscheinlich kommt es zu Abstürzen, weil Ihr Hintergrund-Thread versucht, den Status des alten zu ändern. (Möglicherweise liegt auch ein Problem vor, da sich Ihr Hintergrund-Thread nicht im UI-Thread befindet.)

Ich würde vorschlagen, diesen mHandler flüchtig zu machen und ihn zu aktualisieren, wenn sich die Ausrichtung ändert.


14
Möglicherweise haben Sie den Grund für den Absturz ermittelt. Ich habe den Absturz beseitigt, aber ich habe immer noch nicht herausgefunden, wie ich die Benutzeroberfläche auf zuverlässige Weise in den Zustand zurückversetzen kann, in dem sie sich vor der Änderung der Ausrichtung befand. Aber Ihre Antwort hat mich vorwärts gebracht und sie als Antwort vergeben.
Heikki Toivonen

4
Sie sollten einen onStart in Ihrer Aktivität erhalten, wenn sich die Ausrichtung ändert. Im Wesentlichen müssen Sie die Ansicht unter Verwendung der alten Daten neu konfigurieren. Daher würde ich vorschlagen, numerische Status-udpates über den Fortschrittsbalken anzufordern und eine neue Ansicht neu zu erstellen, wenn Sie diesen neuen "onStart" erhalten. Ich kann mich nicht ohne weiteres daran erinnern, ob Sie auch eine neue Aktivität erhalten, aber ein bisschen Nachschlagen in der Dokumentation sollte helfen.
Haseman

6
Nachdem ich kürzlich damit gespielt habe, kann ich Ihnen mitteilen, dass Sie eine neue Aktivität erhalten, wenn Ihre App die Ausrichtung ändert. (Sie erhalten auch eine neue Ansicht.) Wenn Sie versuchen, die alte Ansicht zu aktualisieren, wird eine Ausnahme angezeigt, da die alte Ansicht einen ungültigen Anwendungskontext aufweist (Ihre alte Aktivität). Sie können dies umgehen, indem Sie myActivity.getApplicationContext () übergeben. anstelle eines Zeigers auf die Aktivität selbst.
Haseman

1
Kann jemand die Verwendung / den Nutzen von volatile in diesem Zusammenhang erklären
Jawad Zeb

2
@Nepster Ja, darüber habe ich mich auch gewundert. Es wäre toll, wenn jemand etwas über die Volatilität erklären würde.
RestInPeace

261

Bearbeiten: Google-Ingenieure empfehlen diesen Ansatz nicht, wie von Dianne Hackborn (auch bekannt als hackbod ) in diesem StackOverflow-Beitrag beschrieben . Weitere Informationen finden Sie in diesem Blogbeitrag .


Sie müssen dies zur Aktivitätsdeklaration im Manifest hinzufügen:

android:configChanges="orientation|screenSize"

so sieht es aus

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

Die Sache ist, dass das System die Aktivität zerstört, wenn eine Änderung in der Konfiguration auftritt. Siehe ConfigurationChanges .

Wenn Sie dies in die Konfigurationsdatei einfügen, kann das System Ihre Aktivitäten nicht zerstören. Stattdessen wird die onConfigurationChanged(Configuration)Methode aufgerufen .


21
Dies ist definitiv die beste Lösung; da es einfach das Layout dreht (das Verhalten, das Sie an erster Stelle erwarten). Stellen Sie einfach sicher, dass Sie android: configChanges = "orientierung | keyboardHidden" (wegen Telefonen mit
Querformat-

24
Dies scheint das Verhalten zu sein, das ich erwarte. Aus der Dokumentation geht jedoch hervor, dass die Aktivität zerstört wird, "weil sich jede Anwendungsressource, einschließlich Layoutdateien, basierend auf einem beliebigen Konfigurationswert ändern kann. Daher ist die einzige sichere Möglichkeit, eine Konfigurationsänderung zu verarbeiten, das erneute Abrufen aller Ressourcen". Außerdem orientationgibt es noch viele weitere Gründe für eine Änderung der Konfiguration: keyboardHidden(Ich habe die Wiki-Antwort bereits bearbeitet.) uiMode(Zum Beispiel das Ein- und Aussteigen aus dem Automodus; Ändern des Nachtmodus) usw. Ich frage mich jetzt, ob dies tatsächlich der Fall ist eine gute Antwort.
10.

116
Dies ist keine akzeptable Lösung. Es maskiert nur das wahre Problem.
RF43

18
Funktioniert, wird aber von Google nicht empfohlen.
Ed Burnette

21
Bitte folgen Sie diesem Ansatz hier nicht. DDosAttack ist völlig richtig. Stellen Sie sich vor, Sie erstellen einen Fortschrittsdialog für einen Download oder etwas anderes, das lange dauert. Als Benutzer bleiben Sie nicht bei dieser Aktivität und starren sie an. Sie würden zum Startbildschirm oder zu einer anderen App wechseln, z. B. wenn ein Spiel oder ein Anruf eingeht oder etwas anderes ressourcenhungrig ist, das Ihre Aktivität letztendlich zerstört. Und was dann? Sie stehen vor dem gleichen alten Problem, das NICHT mit diesem kleinen Trick gelöst wird. Die Aktivität wird erneut erstellt, wenn der Benutzer zurückkommt.
Tiguchi

68

Ich habe eine solide Lösung für diese Probleme gefunden, die dem „Android Way“ der Dinge entspricht. Ich habe alle meine lang laufenden Operationen mit dem IntentService-Muster.

Das heißt, meine Aktivitäten senden Absichten, der IntentService erledigt die Arbeit, speichert die Daten in der Datenbank und sendet dann klebrige Absichten. Der klebrige Teil ist wichtig, sodass wir auch dann antworten und die Daten aus der aufrufenden Aktivität abrufen können, wenn die Aktivität während der Zeit unterbrochen wurde, nachdem der Benutzer die Arbeit initiiert hat und die Echtzeitübertragung vom IntentService verpasst hat. ProgressDialogs kann mit diesem Muster ganz gut arbeiten onSaveInstanceState().

Grundsätzlich müssen Sie ein Flag speichern, für das ein Fortschrittsdialog im gespeicherten Instanzpaket ausgeführt wird. Unterlassen SieSpeichern Sie das Fortschrittsdialogobjekt , da dadurch die gesamte Aktivität verloren geht. Um das Fortschrittsdialogfeld dauerhaft zu behandeln, speichere ich es als schwache Referenz im Anwendungsobjekt. Bei einer Orientierungsänderung oder etwas anderem, das dazu führt, dass die Aktivität angehalten wird (Telefonanruf, Benutzer trifft nach Hause usw.) und dann fortgesetzt wird, schließe ich den alten Dialog aus und erstelle einen neuen Dialog in der neu erstellten Aktivität.

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, einfügen, um den Fortschritt zu verfolgen. Wenn Sie den Fortschritt wiederherstellen, verwenden Sie diese Informationen, um den Fortschrittsbalken im selben Status wie zuvor erneut zu erzeugen und dann basierend auf dem aktuellen Status zu aktualisieren.

Zusammenfassend lässt sich sagen, onSaveInstanceState()dass Sie durch das Einfügen lang laufender Aufgaben in einen IntentService in Verbindung mit einem umsichtigen Einsatz von Dialoge effizient verfolgen und dann über die gesamten Lebenszyklusereignisse der Aktivität hinweg wiederherstellen können. Relevante Teile des Aktivitätscodes sind unten aufgeführt. Sie benötigen auch Logik in Ihrem BroadcastReceiver, um Sticky-Absichten angemessen zu behandeln, aber das geht über den Rahmen hinaus.

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

scheint eine schöne Lösung
Derekyy

"Ich habe alle meine langjährigen Vorgänge mit dem IntentService-Muster." Dies ist keine perfekte Lösung, da es so ist, als würde man aus einer Kanone in Spatzen und viel Boilerplate-Code schießen. Weitere
Informationen finden

28

Ich habe das gleiche Problem getroffen. Meine Aktivität muss einige Daten von einer URL analysieren und ist langsam. Dazu erstelle ich einen Thread und zeige dann einen Fortschrittsdialog. Ich lasse den Thread eine Nachricht zurück an den UI-Thread senden, Handlerwenn er fertig ist. In Handler.handleMessagebekomme ich das Datenobjekt (jetzt fertig) vom Thread und fülle es in die Benutzeroberfläche. Es ist also Ihrem Beispiel sehr ähnlich.

Nach vielen Versuchen und Irrtümern habe ich anscheinend eine Lösung gefunden. Zumindest kann ich jetzt den Bildschirm 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, ist unten gezeigt. Das Ziel ist es, mein Datenmodell ( mDataObject) zu füllen und es dann in die Benutzeroberfläche einzufügen. Sollte jederzeit ohne Überraschung eine Bildschirmdrehung 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 funktioniert bei mir. 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 gesagt, weiß ich nicht wirklich, ob es irgendwelche Nebenwirkungen gibt.


1
Vielen Dank! Der Hinweis auf onRetainNonConfigurationInstance()und getLastNonConfigurationInstance()half mir, mein Problem zu lösen. Daumen hoch!
Sven

15

Das ursprünglich wahrgenommene Problem war, dass der Code eine Änderung der Bildschirmausrichtung nicht überleben würde. Anscheinend wurde dies "gelöst", indem das Programm die Änderung der Bildschirmausrichtung selbst handhabte, anstatt das UI-Framework dies tun zu lassen (über den Aufruf von onDestroy).

Ich würde sagen, wenn das zugrunde liegende Problem darin besteht, dass das Programm auf Destroy () nicht überlebt, ist die akzeptierte Lösung nur eine Problemumgehung, die das Programm mit ernsthaften anderen Problemen und Schwachstellen belastet. Denken Sie daran, dass das Android-Framework ausdrücklich angibt, dass Ihre Aktivität aufgrund von Umständen, auf die Sie keinen Einfluss haben, fast jederzeit zerstört werden kann. Daher muss Ihre Aktivität in der Lage sein, onDestroy () und nachfolgendes onCreate () aus irgendeinem Grund zu überleben, nicht nur durch eine Änderung der Bildschirmausrichtung.

Wenn Sie Änderungen an der Bildschirmausrichtung selbst akzeptieren möchten, um das Problem des OP zu lösen, müssen Sie sicherstellen, dass andere Ursachen von onDestroy () nicht zu demselben Fehler führen. Schaffst du das? Wenn nicht, würde ich fragen, ob die "akzeptierte" Antwort wirklich sehr gut ist.


14

Meine Lösung bestand darin, die ProgressDialogKlasse zu erweitern, um meine eigene zu bekommen MyProgressDialog.
Ich habe show()und dismiss()Methoden neu definiert , um die Ausrichtung zu sperren, bevor ich sie zeige, Dialogund sie wieder zu entsperren, wenn sie Dialogentlassen wird. Wenn also das Dialogangezeigt wird und sich die Ausrichtung des Geräts ändert, bleibt die Ausrichtung des Bildschirms bisdismiss() zum Aufruf erhalten, und die Bildschirmausrichtung ändert sich entsprechend den 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);
}

}

8

Ich hatte das gleiche Problem und habe eine Lösung gefunden, die nicht mit dem ProgressDialog funktioniert, und ich erhalte schnellere Ergebnisse.

Ich habe ein Layout erstellt, das eine ProgressBar 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>

Führen Sie dann in der onCreate-Methode die folgenden Schritte aus

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

Führen Sie dann die lange Aufgabe in einem Thread aus. Wenn dies abgeschlossen ist, lassen Sie Runnable die Inhaltsansicht auf das tatsächliche Layout einstellen, das Sie für diese Aktivität verwenden möchten.

Zum Beispiel:

mHandler.post(new Runnable(){

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

Dies ist, was ich getan habe, und ich habe festgestellt, dass es schneller läuft als das Anzeigen des ProgressDialogs und es ist weniger aufdringlich und sieht meiner Meinung nach besser aus.

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


Diese Lösung ist in einem einfachen Anwendungsfall elegant, weist jedoch Nachteile auf. Sie müssen die vollständige Inhaltsansicht neu erstellen. setContentView(R.layout.my_layout);ist nicht ausreichend; Sie müssen alle Listener einstellen, Daten zurücksetzen usw.
10.

@rds du hast recht. Dies ist wirklich nur eine Lösung für einen einfachen Fall oder wenn Sie in Ihrer onCreate-Methode einige schwere Aufgaben ausführen müssen, bevor Sie Ihre Ansicht anzeigen.
Pzanno

Ich verstehe es nicht ganz. Anstatt die Listener in onCreate () einzustellen, wie wir es normalerweise tun würden, könnten wir sie in run () einrichten. Vermisse ich hier etwas?
Code Poet

7

Ich habe eine Lösung dafür gefunden, die ich anderswo noch nicht gesehen habe. Sie können ein benutzerdefiniertes Anwendungsobjekt verwenden, das weiß, ob Hintergrundaufgaben ausgeführt werden, anstatt dies in der Aktivität zu versuchen, die bei einer Änderung der Ausrichtung zerstört und neu erstellt wird. Ich gebloggt dies hier .


1
Das Erstellen einer benutzerdefinierten Datei Applicationwird normalerweise verwendet, um einen globalen Anwendungsstatus beizubehalten. Ich sage nicht, dass es nicht funktioniert, aber es scheint zu kompliziert. Aus dem Dokument "Es ist normalerweise nicht erforderlich, Anwendung in Unterklassen zu unterteilen.". Ich bevorzuge weitgehend die Antwort von sonxurxo.
rds

7

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

Ich habe eine Anmeldeaktivität mit einer verschachtelten AsyncTaskKlasse namens BackgroundLoginTask.

In meinem BackgroundLoginTaskFall mache ich nichts Außergewöhnliches, außer beim Abrufen des Anrufs einen Null-Check hinzuzufügen ProgressDialog:

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

Dies dient dazu, den Fall zu behandeln, in dem die Hintergrundaufgabe beendet wird, während die Activitynicht sichtbar ist, und daher der Fortschrittsdialog von der onPause()Methode bereits geschlossen wurde.

Als Nächstes Activityerstelle ich in meiner übergeordneten Klasse globale statische Handles für meine AsyncTaskKlasse und meine ProgressDialog(die AsyncTaskverschachtelten können auf diese Variablen zugreifen):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Dies dient zwei Zwecken: Erstens ermöglicht es mir Activity, AsyncTaskauch von einer neuen, nachgedrehten Aktivität aus immer auf das Objekt zuzugreifen . Zweitens ermöglicht es mir, BackgroundLoginTaskauf die zuzugreifen und sie zu schließenProgressDialog auch nach einer Drehung .

Als nächstes füge ich dies hinzu onPause(), wodurch der Fortschrittsdialog verschwindet, wenn wir Activityden Vordergrund verlassen (wodurch dieser hässliche Absturz beim "Schließen erzwingen" verhindert wird):

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

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

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

Dadurch kann das Dialogwieder angezeigt werden, nachdem das Activityneu erstellt wurde.

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 kein erfahrener Android-Entwickler, also zögern Sie nicht zu kommentieren.


1
Interessant! Besonders für diejenigen von uns, die AsyncTask verwenden. Ich habe gerade Ihre Lösung ausprobiert und sie scheint größtenteils zu funktionieren. Es gibt ein Problem: Der ProgressDialog scheint nach einer Rotation etwas früher zu enden, solange der ProgressDialog noch aktiv ist. Ich werde herumspielen, um genau zu sehen, was passiert und wie ich Abhilfe schaffen kann. Aber ich bekomme diese Abstürze nicht mehr!
Scott Biggs

1
Habe eine Lösung gefunden. Es scheint, dass das Problem hier der statische ProgressDialog ist. Wenn Rotationen den ProgressDialog unterbrechen, wird manchmal die Methode .dismiss () aufgerufen, nachdem sie in der neuen Aktivität neu gestartet wurde. Indem wir den ProgressDialog mit jeder Aktivität erstellen, stellen wir sicher, dass dieser neue ProgressDialog nicht zusammen mit der alten Aktivität beendet wird. Ich habe auch sichergestellt, dass der ProgressDialog bei jeder Entlassung auf null gesetzt wird (um die Speicherbereinigung zu unterstützen). Wir haben hier also eine Lösung! Prost an alle, die AsyncTask verwenden!
Scott Biggs

4

Verschieben Sie die lange Aufgabe in eine separate Klasse. Implementieren Sie es als Subjekt-Beobachter-Muster. Immer wenn die Aktivität erstellt wird, registrieren Sie sich und schließen Sie die Registrierung mit der Taskklasse. Die Taskklasse kann AsyncTask verwenden.


1
Ich sehe nicht, wie das helfen würde. Könnten Sie genauer erklären, wie dies die Probleme verhindert, die ich sehe?
Heikki Toivonen

1
Wie Haseman sagte, verhindert es, dass das Backend auf die UI-Elemente zugreift, und wir können die UI vom Backend trennen. Das Backend wird in einem separaten Thread ausgeführt und läuft auch dann weiter, wenn der Bildschirm neu ausgerichtet und die Registrierung für die Statusaktualisierung registriert wurde . Das eigentliche Beispiel, das ich damit gelöst habe, ist, dass ich eine Download-Aufgabe habe. Ich habe diese in einen separaten Thread verschoben. Jedes Mal, wenn der Thread erstellt wird, registriere ich die Registrierung und hebe die Registrierung auf.
Vinay

Ok, ich besuche dieses Problem noch einmal und glaube nicht, dass ich diese Antwort noch vollständig verstehe. Angenommen, wir haben die Hauptaktivität, eine AsyncTask zu starten, um einen lang laufenden Netzwerkvorgang auszuführen, den wir während einer Änderung der Bildschirmausrichtung nicht unterbrechen möchten. Ich sehe nicht, wie die neue Aktivität eine Nachricht an die AsyncTask senden kann, die von der alten Aktivität gestartet wurde. Können Sie ein Codebeispiel geben?
Heikki Toivonen

@ Heikki, liegt meine Implementierung unter dem, was du meinst?
Beetstra

4

Der Trick besteht darin, den Dialog in AsyncTask während onPreExecute / onPostExecute wie gewohnt anzuzeigen / zu schließen. Bei einer Änderung der Ausrichtung wird jedoch eine neue Instanz des Dialogs in der Aktivität erstellt / angezeigt und der Verweis auf die Aufgabe übergeben.

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

4

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, ob es für Sie funktioniert oder nicht


Der onDestroy-Code wird möglicherweise auf der Entwicklerseite überhaupt nicht ausgeführt : "Beachten Sie die Spalte" Killable "in der obigen Tabelle - für die Methoden, die als killable markiert sind, nachdem diese Methode den Prozess zurückgegeben hat, der die Aktivität hostet, die möglicherweise von der beendet wurde System jederzeit ohne eine andere Zeile seines Codes ausgeführt wird "
ilomambo

Ich bin der festen Überzeugung, dass "onRetainNonConfigurationInstance ()" die Methode ist, die für solche Fälle verwendet werden soll ... nyc work
Nitin Bansal

Ich stehe vor einem ähnlichen Problem, da Sachin Gurnani eine statische Deklaration verwendet, um mein Problem zu beheben. stackoverflow.com/questions/12058774/…
Steven Du

2

Wenn Sie einen Hintergrund erstellen Service, der das ganze schwere Heben erledigt (TCP-Anforderungen / Antwort, Unmarshalling), kann das Viewund Activityzerstört und neu erstellt werden, ohne dass Fenster verloren gehen oder Daten verloren gehen. Dies ermöglicht das von Android empfohlene Verhalten, bei dem eine Aktivität bei jeder Konfigurationsänderung zerstört wird (z. B. bei jeder Orientierungsänderung).

Es ist etwas komplexer, aber es ist der beste Weg, um Serveranfragen, Daten vor / nach der Verarbeitung usw. aufzurufen.

Sie können sogar Ihre verwenden Service um jede Anforderung an einen Server in die Warteschlange zu stellen, sodass die Bearbeitung dieser Aufgaben einfach und effizient ist.

Der Dev Guide enthält ein vollständiges Kapitel überServices .


A Serviceist mehr Arbeit als a AsyncTask, kann aber in manchen Situationen ein besserer Ansatz sein. Es ist nicht unbedingt besser, oder? Davon abgesehen verstehe ich nicht, wie dies das Problem löst, ProgressDialogdass aus der Hauptleitung durchgesickert ist Activity. Wo instanziieren Sie das ProgressDialog? Wo entlassen Sie es?
rds

2

Ich habe eine Implementierung, mit der die Aktivität bei einer Änderung der Bildschirmausrichtung zerstört werden kann, der Dialog in der neu erstellten Aktivität jedoch weiterhin erfolgreich zerstört wird. ich benutze...NonConfigurationInstance , um die Hintergrundaufgabe an die neu erstellte Aktivität anzuhängen. Das normale Android-Framework übernimmt die Neuerstellung des Dialogs selbst, dort wird nichts geändert.

Ich habe AsyncTask in eine Unterklasse unterteilt und ein Feld für die Aktivität "Besitzen" sowie eine Methode zum Aktualisieren dieses Besitzers 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, backgroundTaskdas auf die "eigene" Hintergrundaufgabe verweist, und ich aktualisiere dieses Feld mit onRetainNonConfigurationInstanceund 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;
  }
  ...
}

Verbesserungsvorschläge:

  • Löschen Sie die backgroundTaskReferenz in der Aktivität, nachdem die Aufgabe beendet wurde, um Speicher oder andere damit verbundene Ressourcen freizugeben.
  • Löschen Sie die ownerActivity Referenz in der Hintergrundaufgabe, bevor die Aktivität zerstört wird, falls sie nicht sofort neu erstellt wird.
  • Erstellen Sie eine BackgroundTaskSchnittstelle und / oder Sammlung, damit verschiedene Arten von Aufgaben von derselben Eigentümeraktivität ausgeführt werden können.

2

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

Wenn Sie AsynTask verwenden, können Sie die .cancel()Methode innerhalb der onDestroy()Methode der aktuellen Aktivität problemlos aufrufen .

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

Weitere Informationen zu AsyncTask finden Sie im Abschnitt "Abbrechen einer Aufgabe" hier .

Update: Bedingung zur Überprüfung des Status hinzugefügt, da diese nur abgebrochen werden kann, wenn sie sich im laufenden Zustand befindet. Beachten Sie auch, dass die AsyncTask nur einmal ausgeführt werden kann.


2

Versucht, die Lösung von jfelectron zu implementieren , weil es sich um eine " Lösung für diese Probleme handelt, die der" Android- entspricht. Es dauerte jedoch einige Zeit, bis alle genannten Elemente und zusammengestellt wurden. Am Ende war diese etwas andere, und ich denke elegantere Lösung hier in ihrer Gesamtheit veröffentlicht.

Verwendet einen IntentService, der von einer Aktivität ausgelöst wurde, um die lange laufende Aufgabe in einem separaten Thread auszuführen. Der Dienst gibt klebrige Broadcast-Absichten an die Aktivität zurück, die den Dialog aktualisiert. Die Aktivität verwendet showDialog (), onCreateDialog () und onPrepareDialog (), um zu vermeiden, dass persistente Daten im Anwendungsobjekt oder im Paket "savedInstanceState" übergeben werden müssen. Dies sollte unabhängig davon funktionieren, wie Ihre Anwendung 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);
}}

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

Manifest-Dateieinträge:

vor dem Bewerbungsabschnitt:

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

innerhalb des Anwendungsbereichs

service android:name=".MyService"

2

Dies ist meine vorgeschlagene Lösung:

  • Verschieben Sie die AsyncTask oder den Thread in ein beibehaltenes Fragment, wie hier erläutert . Ich halte es für eine gute Praxis, alle Netzwerkanrufe in Fragmente zu verschieben. Wenn Sie bereits Fragmente verwenden, kann eines davon für die Aufrufe verantwortlich gemacht werden. Andernfalls können Sie ein Fragment nur für die Ausführung der Anforderung erstellen, wie im verlinkten Artikel vorgeschlagen.
  • Das Fragment verwendet eine Listener-Schnittstelle, um den Abschluss / Fehler der Aufgabe zu signalisieren. Sie müssen sich dort nicht um Orientierungsänderungen kümmern. Das Fragment hat immer den richtigen Link zur aktuellen Aktivität und der Fortschrittsdialog kann sicher fortgesetzt werden.
  • Machen Sie Ihren Fortschrittsdialog zu einem Mitglied Ihrer Klasse. Tatsächlich sollten Sie dies für alle Dialoge tun. In der onPause-Methode sollten Sie sie schließen, da sonst ein Fenster für die Konfigurationsänderung angezeigt wird. Der Besetztzustand sollte vom Fragment beibehalten werden. Wenn das Fragment an die Aktivität angehängt ist, können Sie den Fortschrittsdialog erneut aufrufen, wenn der Aufruf noch ausgeführt wird. Zu void showProgressDialog()diesem Zweck kann der Fragment-Activity-Listener-Schnittstelle eine Methode hinzugefügt werden.

Perfekte Lösung, aber verstehe nicht, warum diese Antwort von anderen überschattet wird !!!
Blackkara

2

Ich sah mich der gleichen Situation gegenüber. Ich habe nur eine Instanz für meinen Fortschrittsdialog in der gesamten Anwendung erhalten.

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 anzeigen 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 die eindeutige Instanz erneut auf und schließe ihren Dialog.

DialogSingleton.GetInstance().DialogDismiss(this);

Ich speichere den Status der Hintergrundaufgabe in meinen freigegebenen Einstellungen. Wenn ich den Bildschirm drehe, frage ich, ob für diese Aktivität eine Aufgabe ausgeführt wird: (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 starte:

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

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

Wenn ich mit dem Ausführen einer Hintergrundaufgabe fertig bin:

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

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

Ich hoffe, es hilft.


2

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

Wenn die Hintergrundaufgabe nur überleben muss, während sich die Aktivität im Vordergrund befindet, besteht die "neue" Lösung darin, den Hintergrundthread (oder vorzugsweise AsyncTask) in einem beibehaltenen Fragment zu hosten , wie in diesem Entwicklerhandbuch und zahlreichen Fragen und Antworten beschrieben .

Ein beibehaltenes Fragment bleibt erhalten, wenn die Aktivität für eine Konfigurationsänderung zerstört wird, jedoch nicht, wenn die Aktivität im Hintergrund oder im Backstack zerstört wird. Daher sollte die Hintergrundaufgabe immer noch unterbrochen werden, wenn sie isChangingConfigurations()falsch ist onPause().


2

Ich bin ein Neuling 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);
        }
}

1

Ich habe ALLES versucht. Verbrachte Tage damit zu experimentieren. Ich wollte nicht verhindern, dass sich die Aktivität dreht. Mein Szenario war:

  1. Ein Fortschrittsdialog mit dynamischen Informationen für den Benutzer. Beispiel: "Verbindung zum Server herstellen ...", "Daten herunterladen ..." usw.
  2. Ein Thread, der die schweren Dinge erledigt 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 im Buch fehlschlug. Selbst mit der AsyncTask-Klasse, die die richtige Android-Methode ist, um mit diesen Situationen umzugehen. Beim Drehen des Bildschirms ist der aktuelle Kontext, mit dem der Startthread arbeitet, verschwunden und der angezeigte Dialog wird durcheinander gebracht. Das Problem war immer der Dialog, egal wie viele Tricks ich dem Code hinzugefügt habe (Übergeben neuer Kontexte an laufende Threads, Beibehalten von Thread-Zuständen durch Rotationen usw.). 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- / Dialogtrick. Es ist einfach und genial und alles rotationssicher:

  1. Anstatt einen Dialog zu erstellen und ihn anzuzeigen, erstellen Sie eine Aktivität, die im Manifest mit android: theme = "@ android: style / Theme.Dialog" festgelegt wurde. Es sieht also nur wie ein Dialog aus.

  2. Ersetzen Sie showDialog (DIALOG_ID) durch startActivityForResult (yourActivityDialog, yourCode).

  3. Verwenden Sie onActivityResult in der aufrufenden Aktivität, um die Ergebnisse des ausführenden Threads (auch die Fehler) abzurufen und die Benutzeroberfläche zu aktualisieren.

  4. Verwenden Sie in Ihrem 'ActivityDialog' Threads oder AsyncTask, um lange Aufgaben auszuführen, und onRetainNonConfigurationInstance, um den Status "dialog" beim Drehen des Bildschirms zu speichern.

Das ist schnell und funktioniert gut. Ich verwende immer noch Dialoge für andere Aufgaben und die AsyncTask für etwas, das keinen konstanten Dialog auf dem Bildschirm erfordert. Aber in diesem Szenario gehe ich immer zum Aktivitäts- / Dialogmuster.

Ich habe es nicht ausprobiert, aber es ist sogar möglich, das Drehen dieser Aktivität / dieses Dialogfelds zu blockieren, wenn der Thread ausgeführt wird, wodurch die Dinge beschleunigt werden und die aufrufende Aktivität gedreht werden kann.


Nizza , außer dass Parameter müssen durch eine übergeben werden Intent, die restriktiver sind als die ist Objectdurch erlaubtAsyncTask
rds

@ Rui Auch ich habe diese Methode im letzten Jahr verwendet. Mir ist jetzt allerdings aufgefallen, dass dies auch falsch ist. Warum sollte Google überhaupt einen Dialog führen, wenn dies der Weg ist, um dieses Problem zu beheben? Das Problem, das ich sehe, ist, dass, wenn Sie ActivityB (Theme.Dialog) von ActivityA aus öffnen, ActivityA auf dem Activity Stack nach unten verschoben wird und daher vom Betriebssystem bei Bedarf als tötungsbereit markiert wird. Wenn Sie also einen lang laufenden Prozess haben und eine Art Faux-Fortschrittsdialog anzeigen und dieser zu lange gedauert hat und der Speicher knapp geworden ist ... ActivityA wird beendet und es gibt nichts, was nach Abschluss des Fortschritts ebenfalls zurückkehren könnte.
RF43

1

Heutzutage gibt es eine viel klarere Möglichkeit, mit solchen Problemen umzugehen. Der typische Ansatz ist:

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

Alles, was ein Hintergrundprozess ist, sollte gespeichert bleiben Fragment(setzen Sie dies mit Fragment.setRetainInstance(). Dies wird zu Ihrem 'dauerhaften Datenspeicher', in dem alle Daten gespeichert werden, die Sie behalten möchten. Nach dem Ereignis der Orientierungsänderung ist das FragmentOriginal weiterhin verfügbar Status durch einen FragmentManager.findFragmentByTag()Anruf (wenn Sie es erstellen, sollten Sie ihm ein Tag geben, keine ID, da es nicht an a angehängt ist View).

Siehe die Handhabung Runtime Änderungen entwickelten Reiseführer für Informationen über diese richtig zu tun und warum es die beste Option.

2. Stellen Sie sicher, dass Sie eine korrekte und sichere Schnittstelle zwischen den Hintergrundprozessen und Ihrer Benutzeroberfläche herstellen:

Sie müssen Ihren Verknüpfungsprozess umkehren . Im Moment hängt sich Ihr Hintergrundprozess an a an View- stattdessen Viewsollten Sie sich an den Hintergrundprozess anhängen. Es macht mehr Sinn, oder? Die ViewAktion des ist abhängig vom Hintergrundprozess, während der Hintergrundprozess nicht vom Hintergrundprozess abhängt. ViewDies bedeutet, dass die Verknüpfung zu einer Standardschnittstelle geändert Listenerwird. Sagen Sie Ihren Prozess (was auch immer Klasse ist es - ob es eine ist AsyncTask, Runnableoder was auch immer) definiert ein OnProcessFinishedListener, wenn der Prozess es nennen soll , dass die Zuhörer gemacht wird , wenn es vorhanden ist .

Diese Antwort ist eine schöne, kurze Beschreibung, wie man benutzerdefinierte Listener erstellt.

3. Verknüpfen Sie Ihre Benutzeroberfläche bei jeder Erstellung der Benutzeroberfläche mit dem Datenprozess (einschließlich Änderungen der Ausrichtung):

Jetzt müssen Sie sich darum kümmern, die Hintergrundaufgabe mit Ihrer aktuellen ViewStruktur zu verbinden. Wenn Sie Ihre Orientierungsänderungen richtig handhaben (nicht die configChangesHack-Leute, die immer empfohlen werden), werden Sie Dialogvom System neu erstellt. Dies ist wichtig. Dies bedeutet, dass bei der Orientierungsänderung alle DialogLebenszyklusmethoden Ihres Systems abgerufen werden. Bei jeder dieser Methoden ( onCreateDialognormalerweise ein guter Ort) können Sie einen Aufruf wie den folgenden ausführen:

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

Im Fragment-Lebenszyklus können Sie entscheiden, wo die Einstellung des Listeners am besten zu Ihrer individuellen Implementierung passt.

Dies ist ein allgemeiner Ansatz, um eine robuste und vollständige Lösung für das in dieser Frage gestellte generische Problem bereitzustellen. Abhängig von Ihrem individuellen Szenario fehlen in dieser Antwort wahrscheinlich einige kleinere Teile. Dies ist jedoch im Allgemeinen der korrekteste Ansatz, um Orientierungsänderungsereignisse richtig zu behandeln.


1

Ich habe eine einfachere Lösung gefunden, um mit Fäden umzugehen, wenn sich die Ausrichtung ändert. Sie können einfach einen statischen Verweis auf Ihre Aktivität / Ihr Fragment behalten und überprüfen, ob er null ist, bevor Sie auf die Benutzeroberfläche einwirken. Ich schlage vor, auch einen Versuch zu verwenden:

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


}

1

Wenn Sie Probleme haben , Orientierungsänderungsereignisse eines Dialogfelds zu erkennen, UNABHÄNGIG VON EINER AKTIVITÄTSREFERENZ , funktioniert diese Methode aufregend gut. Ich verwende dies, weil ich eine eigene Dialogklasse habe, die in mehreren verschiedenen Aktivitäten angezeigt werden kann, sodass ich nicht immer weiß, in welcher Aktivität sie angezeigt wird. Mit dieser Methode müssen Sie das AndroidManifest nicht ändern. Sorgen Sie sich um Aktivitätsreferenzen. und Sie brauchen keinen benutzerdefinierten Dialog (wie ich). Sie benötigen jedoch eine benutzerdefinierte Inhaltsansicht, damit Sie die Orientierungsänderungen mithilfe dieser bestimmten Ansicht erkennen können. Hier ist mein Beispiel:

Installieren

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

1

Dies ist meine Lösung, als ich damit konfrontiert wurde: Es ProgressDialogist kein untergeordnetes FragmentElement, daher kann meine benutzerdefinierte Klasse " ProgressDialogFragment" DialogFragmentstattdessen erweitert werden, um den für Konfigurationsänderungen angezeigten Dialog beizubehalten.

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

Die Herausforderung bestand darin, den Dialogtitel und die Meldung während der Bildschirmdrehung beizubehalten, während sie auf die leere Standardzeichenfolge zurückgesetzt wurden, obwohl der Dialog weiterhin angezeigt wurde

Es gibt zwei Lösungsansätze:

Erster Ansatz: Führen Sie die Aktivität aus, die den Dialog verwendet, um den Status während der Konfigurationsänderung in der Manifestdatei beizubehalten:

android:configChanges="orientation|screenSize|keyboardHidden"

Dieser Ansatz wird von Google nicht bevorzugt.

Zweiter Ansatz: Bei der onCreate()Methode der Aktivität müssen Sie Ihre beibehalten, DialogFragmentindem Sie die ProgressDialogFragmentmit dem Titel und der Nachricht wie folgt neu erstellen, wenn die savedInstanceStatenicht null ist:

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

0

Scheint viel zu "schnell und schmutzig" zu sein, um wahr zu sein. Bitte weisen Sie auf die Mängel hin, aber ich fand, dass es funktioniert hat ...

Innerhalb der onPostExecute-Methode meiner AsyncTask habe ich einfach die '.dismiss' für den Fortschrittsdialog in einen try / catch-Block (mit einem leeren catch) eingeschlossen und dann die ausgelöste Ausnahme einfach ignoriert. Scheint falsch zu sein, aber es scheint, dass es keine negativen Auswirkungen gibt (zumindest für das, was ich später mache, nämlich eine andere Aktivität zu starten, die als Ergebnis meiner langjährigen Abfrage als Extra übergeben wird).


Wollen Sie damit sagen, dass tatsächlich kein Speicherverlust vorliegt, wenn die Android-Plattformen mitteilen, dass das Fenster verloren gegangen ist?
rds

Mein Fortschrittsdialog ist eine Mitgliedsvariable der Aktivitätsklasse, daher habe ich angenommen, dass bei Zerstörung und Neuerstellung der Aktivität Müll gesammelt wird und kein Leck vorliegt. Liege ich falsch?
Simon

Ja, ich denke es ist falsch. Wie Sie sagen, Activityhat der einen Verweis auf die Dialog. Wenn die Konfiguration geändert wird, wird die erste Activityzerstört, was bedeutet, dass alle Felder auf gesetzt sind null. Das Low-Level WindowManagerhat aber auch einen Hinweis auf das Dialog(da es noch nicht entlassen wurde). Das neue Activityversucht, ein neues Dialog(in preExecute()) zu erstellen, und der Fenstermanager löst eine schwerwiegende Ausnahme aus, die Sie daran hindert. In der Tat, wenn es dies tut, würde es keine Möglichkeit geben, das zu zerstören, Dialogwodurch ein Verweis auf die Initiale beibehalten wird Activity. Habe ich recht?
rds

0

Die einfachste und flexibelste Lösung ist die Verwendung einer AsyncTask mit einem statischen Verweis auf ProgressBar . Dies bietet eine gekapselte und somit wiederverwendbare Lösung für Orientierungsänderungsprobleme. Diese Lösung hat mir gute Dienste für verschiedene asynchrone Aufgaben geleistet, einschließlich Internet-Downloads, Kommunikation mit Diensten und Dateisystem-Scans. Die Lösung wurde auf mehreren Android-Versionen und Telefonmodellen gut getestet. Eine vollständige Demo finden Sie hier mit besonderem Interesse an DownloadFile.java

Ich präsentiere das Folgende als Konzeptbeispiel

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

Die Verwendung in einer Android-Aktivität ist einfach

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

0

Wenn Sie die Ausrichtung ändern, beendet Android diese Aktivität und erstellt eine neue Aktivität. Ich empfehle die Nachrüstung mit Rx Java. welcher Griff stürzt automatisch ab.

Verwenden Sie diese Methode beim Nachrüsten.

.subscribeOn (Schedulers.io ()) .observeOn (AndroidSchedulers.mainThread ())

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.