Vista Android non collegata al gestore delle finestre


111

Sto riscontrando alcune delle seguenti eccezioni:

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)

L'ho cercato su Google e ho visto che ha qualcosa a che fare con i popup e la rotazione dello schermo, ma non c'è alcun riferimento al mio codice.

Le domande sono:

  1. c'è un modo per scoprire esattamente quando si sta verificando questo problema?
  2. oltre a girare lo schermo, c'è un altro evento o azione che attiva questo errore?
  3. come posso evitare che ciò accada?

1
Verifica se riesci a spiegare come viene descritta l'attività nel file manifest e quale attività si trova sullo schermo quando si verifica l'errore. Vedi se riesci a ridurre il tuo problema in un caso di prova minimo.
Josh Lee,

Forse stai cercando di modificare la tua vista prima che View#onAttachedToWindow()sia stato chiamato?
Alex Lockwood,

Risposte:


163

Ho avuto questo problema in cui su una modifica dell'orientamento dello schermo, l'attività terminava prima dell'AsyncTask con la finestra di dialogo di avanzamento completata. Mi è sembrato di risolvere questo problema impostando la finestra di dialogo su null onPause()e quindi controllandolo nell'AsyncTask prima di chiudere.

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

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

... nel mio AsyncTask:

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

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}

12
@YekhezkelYovel l'AsyncTask è in esecuzione in un thread che sopravvive al riavvio dell'attività e può contenere riferimenti all'attività ora morta e alle sue viste (questa è effettivamente una perdita di memoria, anche se probabilmente a breve termine - dipende da quanto tempo richiede il completamento dell'attività ). La soluzione sopra funziona bene, se sei felice di accettare una perdita di memoria a breve termine. L'approccio consigliato è di cancel () qualsiasi AsyncTask in esecuzione quando l'attività è sospesa.
Stevie

8

Dopo un litigio con questo problema, alla fine riesco a trovare questa soluzione alternativa:

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

A volte, una buona gestione delle eccezioni funziona bene se non esiste una soluzione migliore per questo problema.


5

Se hai un Activityoggetto in giro, puoi usare il isDestroyed()metodo:

Activity activity;

// ...

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

Questo è utile se hai una AsyncTasksottoclasse non anonima che usi in vari posti.


3

Sto usando una classe statica personalizzata che mostra e nasconde una finestra di dialogo. questa classe viene utilizzata anche da altre attività non solo da un'attività. Ora il problema che hai descritto è apparso anche a me e ho passato la notte a trovare una soluzione ..

Finalmente ti presento la soluzione!

se vuoi mostrare o chiudere una finestra di dialogo e non sai quale attività ha avviato la finestra di dialogo per toccarla, il seguente codice è per te ..

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


            }
        } 

 }

1

La soluzione sopra non ha funzionato per me. Quindi quello che ho fatto è stato prendere ProgressDialogcome globale e poi aggiungerlo alla mia attività

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

in modo che, nel caso in cui l'attività venga distrutta, anche il ProgressDialog verrà distrutto.


0

Per la domanda 1):

Considerando che il messaggio di errore non sembra indicare quale riga del codice sta causando il problema, è possibile rintracciarlo utilizzando i punti di interruzione. I punti di interruzione sospendono l'esecuzione del programma quando il programma arriva a specifiche righe di codice. Aggiungendo punti di interruzione a posizioni critiche, è possibile determinare quale riga di codice causa l'arresto anomalo. Ad esempio, se il tuo programma si blocca in una riga setContentView (), potresti inserire un punto di interruzione lì. Quando il programma viene eseguito, si fermerà prima di eseguire quella riga. Se la ripresa provoca l'arresto anomalo del programma prima di raggiungere il punto di interruzione successivo, allora sai che la linea che ha terminato il programma era tra i due punti di interruzione.

L'aggiunta di punti di interruzione è facile se stai usando Eclipse. Fare clic con il pulsante destro del mouse sul margine appena a sinistra del codice e selezionare "Attiva / disattiva punto di interruzione". È quindi necessario eseguire l'applicazione in modalità debug, il pulsante che sembra un insetto verde accanto al normale pulsante di esecuzione. Quando il programma raggiunge un punto di interruzione, Eclipse passerà alla prospettiva di debug e ti mostrerà la linea in cui sta aspettando. Per riavviare il programma, cerca il pulsante "Riprendi", che sembra un normale "Riproduci" ma con una barra verticale a sinistra del triangolo.

Puoi anche riempire la tua applicazione con Log.d ("La mia applicazione", "Alcune informazioni qui che ti dicono dove si trova la riga di registro"), che poi pubblica i messaggi nella finestra LogCat di Eclipse. Se non riesci a trovare quella finestra, aprila con Finestra -> Mostra vista -> Altro ... -> Android -> LogCat.

Spero che aiuti!


7
Il problema si sta verificando sui telefoni dei client, quindi non ho un'opzione per eseguire il debug. Inoltre il problema si verifica sempre, a volte, quindi non so come rintracciarlo
Daniel Benedykt

Puoi ancora ricevere messaggi di debug da un telefono, se riesci a metterne le mani su uno. Nel menu -> impostazioni -> applicazioni -> sviluppo, c'è un'opzione per il debug USB. Se abilitato, puoi collegare il telefono e LogCat cattura tutte le normali linee di debug. Oltre a questo ... beh ... l'intermittenza potrebbe essere spiegata da questo a seconda dello stato del programma. Suppongo che dovresti scherzare usando il programma per vedere se puoi ricreare il problema.
Steve Haley

4
Come ho detto prima, il problema si sta verificando su un telefono client e non ho accesso ad esso. Il cliente potrebbe trovarsi in un altro continente :)
Daniel Benedykt

Ci sono app sul mercato che ti invieranno via email il loro logcat.
Tim Green

1
Solo così sai che puoi ruotare l'emulatore premendo il tasto del tastierino numerico 9
Rob

0

Ho aggiunto quanto segue al file manifest per quell'attività

android:configChanges="keyboardHidden|orientation|screenLayout"

19
Questa non è una soluzione: il sistema potrebbe distruggere la tua attività anche per altri motivi.
Roel

1
Puoi spiegare la tua risposta per favore?
Leebeedev

0

secondo il codice del windowManager (link qui ), ciò si verifica quando la vista che si sta tentando di aggiornare (che probabilmente appartiene a una finestra di dialogo, ma non necessaria) non è più attaccata alla radice reale delle finestre.

come altri hanno suggerito, dovresti controllare lo stato dell'attività prima di eseguire operazioni speciali sui tuoi dialoghi.

ecco il codice pertinente, che è la causa del problema (copiato dal codice sorgente di Android):

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

il codice che ho scritto qui è quello che fa Google. quello che ho scritto è per mostrare la causa di questo problema.
sviluppatore Android

0

Il mio problema è stato risolto bloccando la rotazione dello schermo sul mio Android, l'app che mi stava causando un problema ora funziona perfettamente


0

Un'altra opzione è non avviare l'attività asincrona fino a quando la finestra di dialogo non è collegata alla finestra sovrascrivendo onAttachedToWindow () nella finestra di dialogo, in questo modo è sempre ignorabile.


0

O semplicemente puoi aggiungere

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

che renderà il ProgressDialogper non cancellabile


-2

Perché non provare a catturare, in questo modo:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }

IllegalStateOfException Dovrebbe essere trattato in un modo migliore, piuttosto solo per comportarsi in modo anomalo.
Lavakush

-3

quando dichiari attività nel manifest hai bisogno di android: configChanges = "orientamento"

esempio:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>

1
Questo tag è richiesto solo se lo sviluppatore non vuole distruggere e ricreare l'attività dal sistema Android.
Ankit
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.