Android-Ansicht nicht an Fenstermanager angehängt

111

Ich habe einige der folgenden Ausnahmen:

java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:191)
at android.view.Window$LocalWindowManager.updateViewLayout(Window.java:428)
at android.app.Dialog.onWindowAttributesChanged(Dialog.java:596)
at android.view.Window.setDefaultWindowFormat(Window.java:1013)
at com.android.internal.policy.impl.PhoneWindow.access$700(PhoneWindow.java:86)
at com.android.internal.policy.impl.PhoneWindow$DecorView.drawableChanged(PhoneWindow.java:1951)
at com.android.internal.policy.impl.PhoneWindow$DecorView.fitSystemWindows(PhoneWindow.java:1889)
at android.view.ViewRoot.performTraversals(ViewRoot.java:727)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1633)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4338)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
at dalvik.system.NativeStart.main(Native Method)

Ich habe es gegoogelt und festgestellt, dass es etwas mit Popups und dem Drehen des Bildschirms zu tun hat, aber es gibt keinen Verweis auf meinen Code.

Die Fragen sind:

  1. Gibt es eine Möglichkeit, genau herauszufinden, wann dieses Problem auftritt?
  2. Gibt es außer dem Drehen des Bildschirms ein anderes Ereignis oder eine andere Aktion, die diesen Fehler auslöst?
  3. Wie verhindere ich dies?
Daniel Benedykt
quelle
1
Überprüfen Sie, ob Sie erklären können, wie die Aktivität im Manifest beschrieben wird und welche Aktivität auf dem Bildschirm angezeigt wird, wenn der Fehler auftritt. Sehen Sie, ob Sie Ihr Problem in einen minimalen Testfall verwandeln können.
Josh Lee
Vielleicht versuchen Sie, Ihre Ansicht zu ändern, bevor View#onAttachedToWindow()sie aufgerufen wurde?
Alex Lockwood

Antworten:

163

Ich hatte dieses Problem, bei dem bei einer Änderung der Bildschirmausrichtung die Aktivität vor der AsyncTask beendet wurde und der Fortschrittsdialog abgeschlossen wurde. Ich schien dies zu beheben, indem ich den Dialog auf null setzte onPause()und dies dann in der AsyncTask überprüfte, bevor ich ihn schloss.

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

    if ((mDialog != null) && mDialog.isShowing())
        mDialog.dismiss();
    mDialog = null;
}

... in meiner AsyncTask:

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...",
            true);
}

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}
Johnnyblizzard
quelle
12
@YekhezkelYovel Die AsyncTask wird in einem Thread ausgeführt, der den Neustart der Aktivität überlebt und möglicherweise Verweise auf die jetzt tote Aktivität und ihre Ansichten enthält (dies ist praktisch ein Speicherverlust, wenn auch wahrscheinlich ein kurzfristiger - hängt davon ab, wie lange die Aufgabe dauert ). Die obige Lösung funktioniert einwandfrei, wenn Sie gerne ein Kurzzeitgedächtnisleck akzeptieren. Der empfohlene Ansatz besteht darin, alle laufenden AsyncTask-Aktionen abzubrechen (), wenn die Aktivität angehalten wird.
Stevie
8

Nach einem Kampf mit diesem Problem habe ich endlich diese Problemumgehung:

/**
 * Dismiss {@link ProgressDialog} with check for nullability and SDK version
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissProgressDialog(ProgressDialog dialog) {
    if (dialog != null && dialog.isShowing()) {

            //get the Context object that was used to great the dialog
            Context context = ((ContextWrapper) dialog.getContext()).getBaseContext();

            // if the Context used here was an activity AND it hasn't been finished or destroyed
            // then dismiss it
            if (context instanceof Activity) {

                // Api >=17
                if (!((Activity) context).isFinishing() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isDestroyed()) {
                            dismissWithExceptionHandling(dialog);
                        }
                    } else { 
                        // Api < 17. Unfortunately cannot check for isDestroyed()
                        dismissWithExceptionHandling(dialog);
                    }
                }
            } else
                // if the Context used wasn't an Activity, then dismiss it too
                dismissWithExceptionHandling(dialog);
        }
        dialog = null;
    }
}

/**
 * Dismiss {@link ProgressDialog} with try catch
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissWithExceptionHandling(ProgressDialog dialog) {
    try {
        dialog.dismiss();
    } catch (final IllegalArgumentException e) {
        // Do nothing.
    } catch (final Exception e) {
        // Do nothing.
    } finally {
        dialog = null;
    }
}

Manchmal funktioniert eine gute Ausnahmebehandlung gut, wenn es keine bessere Lösung für dieses Problem gibt.

Blueware
quelle
5

Wenn ein ActivityObjekt herumhängt, können Sie die folgende isDestroyed()Methode verwenden:

Activity activity;

// ...

if (!activity.isDestroyed()) {
    // ...
}

Dies ist hilfreich, wenn Sie eine nicht anonyme AsyncTaskUnterklasse haben, die Sie an verschiedenen Orten verwenden.

shawkinaw
quelle
3

Ich verwende eine benutzerdefinierte statische Klasse, die einen Dialog ein- und ausblendet. Diese Klasse wird auch von anderen Aktivitäten verwendet, nicht nur von einer Aktivität. Nun erschien mir auch das von Ihnen beschriebene Problem und ich blieb über Nacht, um eine Lösung zu finden.

Zum Schluss präsentiere ich Ihnen die Lösung!

Wenn Sie einen Dialog anzeigen oder schließen möchten und nicht wissen, welche Aktivität den Dialog initiiert hat, um ihn zu berühren, ist der folgende Code für Sie.

 static class CustomDialog{

     public static void initDialog(){
         ...
         //init code
         ...
     }

      public static void showDialog(){
         ...
         //init code for show dialog
         ...
     }

     /****This is your Dismiss dialog code :D*******/
     public static void dismissProgressDialog(Context context) {                
            //Can't touch other View of other Activiy..
            //http://stackoverflow.com/questions/23458162/dismiss-progress-dialog-in-another-activity-android
            if ( (progressdialog != null) && progressdialog.isShowing()) {

                //is it the same context from the caller ?
                Log.w("ProgressDIalog dismiss", "the dialog is from"+progressdialog.getContext());

                Class caller_context= context.getClass();
                Activity call_Act = (Activity)context;
                Class progress_context= progressdialog.getContext().getClass();

                Boolean is_act= ( (progressdialog.getContext()) instanceof  Activity )?true:false;
                Boolean is_ctw= ( (progressdialog.getContext()) instanceof  ContextThemeWrapper )?true:false;

                if (is_ctw) {
                    ContextThemeWrapper cthw=(ContextThemeWrapper) progressdialog.getContext();
                    Boolean is_same_acivity_with_Caller= ((Activity)(cthw).getBaseContext() ==  call_Act )?true:false;

                    if (is_same_acivity_with_Caller){
                        progressdialog.dismiss();
                        progressdialog = null;
                    }
                    else {
                        Log.e("ProgressDIalog dismiss", "the dialog is NOT from the same context! Can't touch.."+((Activity)(cthw).getBaseContext()).getClass());
                        progressdialog = null;
                    }
                }


            }
        } 

 }
aimiliano
quelle
1

Die obige Lösung hat bei mir nicht funktioniert. Also habe ich ProgressDialogals global genommen und dies dann zu meiner Aktivität hinzugefügt

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

Wenn also die Aktivität zerstört wird, wird auch der ProgressDialog zerstört.

Kishan Solanki
quelle
0

Zu Frage 1):

Da die Fehlermeldung nicht zu sagen scheint, welche Zeile Ihres Codes das Problem verursacht, können Sie sie mithilfe von Haltepunkten aufspüren. Haltepunkte unterbrechen die Ausführung des Programms, wenn das Programm bestimmte Codezeilen erreicht. Durch Hinzufügen von Haltepunkten an kritischen Stellen können Sie bestimmen, welche Codezeile den Absturz verursacht. Wenn Ihr Programm beispielsweise in einer Zeile setContentView () abstürzt, können Sie dort einen Haltepunkt setzen. Wenn das Programm ausgeführt wird, wird es angehalten, bevor diese Zeile ausgeführt wird. Wenn das Programm nach dem Fortsetzen vor dem Erreichen des nächsten Haltepunkts abstürzt, wissen Sie, dass sich die Linie, die das Programm beendet hat, zwischen den beiden Haltepunkten befand.

Das Hinzufügen von Haltepunkten ist einfach, wenn Sie Eclipse verwenden. Klicken Sie mit der rechten Maustaste in den Rand links neben Ihrem Code und wählen Sie "Haltepunkt umschalten". Anschließend müssen Sie Ihre Anwendung im Debug-Modus ausführen, der Schaltfläche, die wie ein grünes Insekt neben der normalen Ausführungsschaltfläche aussieht. Wenn das Programm einen Haltepunkt erreicht, wechselt Eclipse in die Debug-Perspektive und zeigt Ihnen die Zeile an, auf die es wartet. Um das Programm erneut zu starten, suchen Sie nach der Schaltfläche "Fortsetzen", die wie eine normale "Wiedergabe" aussieht, jedoch einen vertikalen Balken links vom Dreieck aufweist.

Sie können Ihre Anwendung auch mit Log.d ("Meine Anwendung", "Einige Informationen hier, aus denen hervorgeht, wo sich die Protokollzeile befindet") füllen, das dann Nachrichten im LogCat-Fenster von Eclipse veröffentlicht. Wenn Sie dieses Fenster nicht finden können, öffnen Sie es mit Fenster -> Ansicht anzeigen -> Andere ... -> Android -> LogCat.

Hoffentlich hilft das!

Steve Haley
quelle
7
Das Problem tritt auf den Telefonen des Clients auf, daher habe ich keine Option zum Debuggen. Auch das Problem tritt die ganze Zeit auf, nur manchmal, also weiß ich nicht, wie ich es verfolgen soll
Daniel Benedykt
Sie können weiterhin Debugging-Nachrichten von einem Telefon erhalten, wenn Sie eines in die Hände bekommen. Unter Menü -> Einstellungen -> Anwendungen -> Entwicklung gibt es eine Option für das USB-Debugging. Wenn aktiviert, können Sie das Telefon anschließen und LogCat fängt alle normalen Debugging-Zeilen ab. Davon abgesehen ... nun ... könnte die Intermittenz je nach Programmstatus dadurch erklärt werden. Ich nehme an, Sie müssten mit dem Programm herumspielen, um zu sehen, ob Sie das Problem neu erstellen können.
Steve Haley
4
Wie ich bereits sagte, tritt das Problem auf einem Client-Telefon auf und ich habe keinen Zugriff darauf. Der Kunde kann auf einem anderen Kontinent sein :)
Daniel Benedykt
Es gibt Apps auf dem Markt, die Ihnen ihre Logcat per E-Mail senden.
Tim Green
1
Nur damit Sie wissen, dass Sie den Emulator drehen können, indem Sie die Zifferntaste 9
Rob
0

Ich habe dem Manifest für diese Aktivität Folgendes hinzugefügt

android:configChanges="keyboardHidden|orientation|screenLayout"
Rickey
quelle
19
Dies ist keine Lösung: Das System kann Ihre Aktivität auch aus anderen Gründen zerstören.
Roel
1
Könnten Sie bitte Ihre Antwort erklären?
Leebeedev
0

Gemäß dem Code des windowManager (Link hier ) tritt dies auf, wenn die Ansicht, die Sie aktualisieren möchten ( die wahrscheinlich zu einem Dialogfeld gehört, aber nicht erforderlich ist), nicht mehr an das eigentliche Stammverzeichnis der Fenster angehängt ist.

Wie andere vorgeschlagen haben, sollten Sie den Status der Aktivität überprüfen, bevor Sie spezielle Vorgänge in Ihren Dialogen ausführen.

Hier ist der relavante Code, der die Ursache für das Problem ist (kopiert aus dem Android-Quellcode):

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams
            = (WindowManager.LayoutParams)params;

    view.setLayoutParams(wparams);

    synchronized (this) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots[index];
        mParams[index] = wparams;
        root.setLayoutParams(wparams, false);
    }
}

private int findViewLocked(View view, boolean required) {
        synchronized (this) {
            final int count = mViews != null ? mViews.length : 0;
            for (int i=0; i<count; i++) {
                if (mViews[i] == view) {
                    return i;
                }
            }
            if (required) {
                throw new IllegalArgumentException(
                        "View not attached to window manager");
            }
            return -1;
        }
    }
Android-Entwickler
quelle
Der Code, den ich hier geschrieben habe, ist der, den Google macht. Was ich geschrieben habe, ist, um die Ursache für dieses Problem aufzuzeigen.
Android-Entwickler
0

Mein Problem wurde gelöst, indem die Bildschirmdrehung auf meinem Android-Gerät blockiert wurde. Die App, die mir ein Problem verursachte, funktioniert jetzt einwandfrei

Roger
quelle
0

Eine andere Option besteht darin, die asynchrone Aufgabe erst zu starten, wenn der Dialog an das Fenster angehängt ist, indem onAttachedToWindow () im Dialog überschrieben wird. Auf diese Weise kann er immer geschlossen werden.

Nick Palmer
quelle
0

Oder einfach können Sie hinzufügen

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...", true, false);
}

das macht das ProgressDialognicht stornierbar

Sarith Vasu
quelle
-2

Warum nicht versuchen, so zu fangen:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }
Heasen
quelle
IllegalStateOfException Sollte besser behandelt werden, anstatt sich nur abnormal zu verhalten.
Lavakush
-3

Wenn Sie Aktivität im Manifest deklarieren, benötigen Sie Android: configChanges = "Orientierung"

Beispiel:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>
Cristian
quelle
1
Dieses Tag ist nur erforderlich, wenn der Entwickler die Aktivität des Android-Systems nicht zerstören und neu erstellen möchte.
Ankit