java.lang.IllegalArgumentException: vista non allegata al gestore finestre


148

Ho un'attività che avvia AsyncTask e mostra la finestra di avanzamento per la durata dell'operazione. L'attività è dichiarata NON essere ricreata mediante rotazione o scorrimento della tastiera.

    <activity android:name=".MyActivity" 
              android:label="@string/app_name"
              android:configChanges="keyboardHidden|orientation"
              >
        <intent-filter>
        </intent-filter>
    </activity>

Una volta completata l'attività, rifiuto la finestra di dialogo, ma su alcuni telefoni (framework: 1.5, 1.6) viene generato tale errore:

java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:356)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:201)
    at android.view.Window$LocalWindowManager.removeView(Window.java:400)
    at android.app.Dialog.dismissDialog(Dialog.java:268)
    at android.app.Dialog.access$000(Dialog.java:69)
    at android.app.Dialog$1.run(Dialog.java:103)
    at android.app.Dialog.dismiss(Dialog.java:252)
    at xxx.onPostExecute(xxx$1.java:xxx)

Il mio codice è:

final Dialog dialog = new AlertDialog.Builder(context)
    .setTitle("Processing...")
    .setCancelable(true)
    .create();

final AsyncTask<MyParams, Object, MyResult> task = new AsyncTask<MyParams, Object, MyResult>() {

    @Override
    protected MyResult doInBackground(MyParams... params) {
        // Long operation goes here
    }

    @Override
    protected void onPostExecute(MyResult result) {
        dialog.dismiss();
        onCompletion(result);
    }
};

task.execute(...);

dialog.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface arg0) {
        task.cancel(false);
    }
});

dialog.show();

Da quello che ho letto ( http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html ) e visto nelle fonti Android, sembra l'unica situazione possibile per ottenerlo l'eccezione è quando l'attività è stata distrutta. Ma come ho già detto, proibisco attività ricreative per eventi di base.

Quindi ogni suggerimento è molto apprezzato.


1
Questa domanda ha molte risposte, se qualcuno di loro ti ha aiutato, per favore selezionalo come la risposta giusta.
Parag Kadam,

Risposte:


228

Anch'io ricevo questo errore a volte quando chiudo la finestra di dialogo e finisco l'attività dal metodo onPostExecute. Immagino che a volte l'attività venga completata prima che il dialogo venga chiuso correttamente.

Soluzione semplice ma efficace che funziona per me

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

44
Soluzione semplice? Sì. Efficace? Forse in questo caso. Lo consiglierei? NO! Non ingoiare TUTTE le eccezioni del genere! Non vorrei nemmeno catturare IllegalArgumentException, ma cercare un'altra soluzione.
Simon Forsberg,

6
Perché di solito i trucchi vuoti sono una cattiva idea ... Anche se a volte può essere la cosa giusta da fare.
Thomas

3
@Damjan In base alla tua risposta, suggerisci il tipo di cattura Eccezione. Bene, questa è una cattiva pratica da parte di Google. Puoi leggerlo qui: Non catturare l'eccezione generica .
Yaniv,

17
Credo che questa sia una soluzione efficace. In generale, non dovremmo farlo, ma poiché Android Framework non fornisce alcun controllo facile per noi, dobbiamo usare un modo insolito. Inoltre, se la chiamata isShowing () di una finestra di dialogo funziona come previsto, non è necessario questo tipo di hack.
SXC,

1
soluzione rapida fino a quando non viene trovato qualcosa di meglio
Rohit Tigga,

13

Ecco la mia soluzione "a prova di proiettile", che è la raccolta di tutte le buone risposte che ho trovato su questo argomento (grazie a @Damjan e @Kachi). Qui l'eccezione viene ingoiata solo se tutti gli altri modi di rilevamento non sono riusciti. Nel mio caso devo chiudere automaticamente la finestra di dialogo e questo è l'unico modo per proteggere l'app da crash. Spero che ti possa aiutare! Per favore, vota e lascia commenti se hai commenti o una soluzione migliore. Grazie!

public void dismissWithCheck(Dialog dialog) {
        if (dialog != null) {
            if (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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isFinishing() && !((Activity) context).isDestroyed()) {
                            dismissWithTryCatch(dialog);
                        }
                    } else {

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

    public void dismissWithTryCatch(Dialog dialog) {
        try {
            dialog.dismiss();
        } catch (final IllegalArgumentException e) {
            // Do nothing.
        } catch (final Exception e) {
            // Do nothing.
        } finally {
            dialog = null;
        }
    }

1
ottima soluzione! L'impostazione dialog = nullnon ha alcun effetto. E StatusEventDialogdovrebbe leggere solo Dialog.
hgoebl,

1
StatusEventDialog dovrebbe essere modificato in dialog.
Sreekanth Karumanaghat,

Questa risposta dovrebbe essere quella accettata, gestita molto bene
programma

Capisco che vuoi essere "corretto" e utilizzare solo try / catch nel caso in cui isDestroyed()non sia disponibile, ma per scopi pratici non sarebbe lo stesso usare solo try / catch?
zundi,

11

Potrei avere una soluzione alternativa.

Stava avendo lo stesso problema, in cui sto caricando molti elementi (tramite il file system) in un ListViewtramite un AsyncTask. Ho dovuto onPreExecute()accendere un ProgressDialog, e poi entrambi onPostExecute()e onCancelled()(chiamato quando l'attività viene annullata esplicitamente tramite AsyncTask.cancel()) chiudendolo tramite .cancel().

Ho avuto lo stesso errore "java.lang.IllegalArgumentException: vista non allegata al gestore di finestre" quando stavo uccidendo la finestra di dialogo nel onCancelled()metodo del AsyncTask(l'avevo visto fatto nell'eccellente app Shelves ).

La soluzione alternativa era quella di creare un campo pubblico nel AsyncTaskche contenga ProgressDialog:

public ProgressDialog mDialog;

Quindi, onDestroy()quando annullo il mio AsyncTask, posso anche uccidere la finestra di dialogo associata tramite:

AsyncTask.mDialog.cancel();

Chiamando AsyncTask.cancel()DOES si attiva onCancelled()in AsyncTask, ma per qualche motivo quando viene chiamato quel metodo, la vista è già stata distrutta e quindi annullare la finestra di dialogo non riesce.


Trovo che l'implementazione di UserTask sia eccellente come menzionato da @Paul. Il codice sorgente è qui: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song

Mentre il caso d'uso può essere trovato nello stesso progetto: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song

9

Ecco la soluzione corretta per risolvere questo problema:

public void hideProgress() {
    if(mProgressDialog != null) {
        if(mProgressDialog.isShowing()) { //check if dialog is showing.

            //get the Context object that was used to great the dialog
            Context context = ((ContextWrapper)mProgressDialog.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) { 
                if(!((Activity)context).isFinishing() && !((Activity)context).isDestroyed()) 
                    mProgressDialog.dismiss();
            } else //if the Context used wasnt an Activity, then dismiss it too
                mProgressDialog.dismiss();
        }
        mProgressDialog = null;
    }
}

Invece di cogliere ciecamente tutte le eccezioni, questa soluzione affronta la radice del problema: cercare di ridimensionare una finestra di dialogo quando l'attività utilizzata per inizializzare la finestra di dialogo è già stata completata. Funziona sul mio Nexus 4 con KitKat, ma dovrebbe funzionare con tutte le versioni di Android.


3
isDestroyedrichiede API 17+
Androiderson

Perché è necessario impostare mProgressDialog su null? È legato alla perdita di memoria? Potresti spiegare per favore?
Pawan,

@Pawan, è un dettaglio di implementazione da parte mia. Non è richiesto, è solo il modo in cui funziona la funzione in questa classe. Dopo che una finestra di dialogo di avanzamento è nascosta, l'ho impostata su null. Quando un utente desidera mostrare un'altra finestra di dialogo sull'avanzamento, viene istanziata una nuova istanza.
Kachi,

Sicuramente! ((Activity) context) .isFinishing () è richiesto, grazie! :)
Daniel Krzyczkowski il

5

concordo con l'opinione di "Damjan".
se usi molte finestre di dialogo, dovresti chiudere tutte le finestre di dialogo in onDestroy () o onStop ().
allora potresti essere in grado di ridurre la frequenza 'si verifica l'eccezione' java.lang.IllegalArgumentException: vista non allegata al gestore finestre '.

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mDialog.dismiss();
    super.onDestroy();
}



ma poco superiore ...
per renderlo più chiaro, si impedisce di mostrare qualsiasi finestra dopo OnDestroy chiamato.
non uso come di seguito. ma è chiaro.

private boolean mIsDestroyed = false;

private void showDialog() {
    closeDialog();

    if (mIsDestroyed) {
        Log.d(TAG, "called onDestroy() already.");
        return;
    }

    mDialog = new AlertDialog(this)
        .setTitle("title")
        .setMessage("This is DialogTest")
        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        })
        .create();
    mDialog.show();
}

private void closeDialog() {
    if (mDialog != null) {
        mDialog.dismiss();
    }
}

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mIsDestroyed = true;
    closeDialog();
    super.onDestroy();
}


in bocca al lupo!


Preferirò sempre evitare blocchi di cattura vuoti. vale la pena provare, ma poiché questo errore è difficile da produrre, solo il tempo dirà se funziona davvero. grazie comunque.
Dror Fichman,

che cosa sono i blocchi di cattura vuoti? non uso try / catch. una variabile mIsDestroyed è stata superata funzionando. ma se scrivi nel codice che mostra la finestra di dialogo dopo aver lavorato in un altro thread, potresti aver bisogno di questa variabile. quando funziona un altro thread, se l'attività è terminata, puoi vedere questa eccezione.
Hogun,

Ho avuto lo stesso prob e quando ho aggiunto @Override public void onPause () {if (dialog! = Null) dialog.dismiss (); super.onPause (); } fino ad oggi non ho questo errore ... quindi penso che sia uguale alla tua risposta ed è davvero utile
Chris Sim

@ChrisSim ciao! onPuase () e onDestroy () è defference. quando Activity è onPuase, la finestra di dialogo è chiusa. e quando si esegue l'app, la finestra di dialogo non viene visualizzata. lo vuoi?
Hogun,

@Hogun Sì, certo, intendo la stessa idea, sto chiudendo il dialogo in pausa invece che in distruzione perché ne ho bisogno in pausa e non in distruzione. In secondo luogo, lo chiudo proprio quando non è nullo. Grazie per averlo spiegato agli altri.
Chris Sim,

4

Usa questo.

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();

2
È quasi la stessa soluzione proposta da @Damjan.
Yury,

28
Questo non è abbastanza, IllegalArgumentException si verifica ancora con questo controllo.
Murphy,

Ho fatto la stessa soluzione, ma ancora non so se sia efficace. L'unica differenza, ho annidato due if per essere sicuro che la seconda parte .isShowing () non verrà valutata se è nulla.
Nick,

2
Questo non è sufficiente.
trante,

1
@Nick: non è necessario nidificare multipli ifin casi come questo, l' &&operatore Java ha una valutazione pigra (anche chiamata cortocircuito), il che significa che il secondo operando non viene valutato se il primo valuta false(il che significa che il risultato di il &&sarà sempre falsecomunque, quindi valutazione "pigro"). Allo stesso modo ||non valuterà il suo secondo operando se il primo lo valuta true. Nota: gli operatori &e |non hanno questo comportamento e quindi valutano sempre entrambi gli operandi.
Matthias,

3

Ho avuto lo stesso problema, puoi risolverlo:

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

2

Penso che il tuo codice sia corretto a differenza dell'altra risposta suggerita. onPostExecute verrà eseguito sul thread dell'interfaccia utente. Questo è il punto centrale di AsyncTask: non devi preoccuparti di chiamare runOnUiThread o trattare con i gestori. Inoltre, secondo i documenti, dismiss () può essere tranquillamente richiamato da qualsiasi thread (non sono sicuro che abbiano reso questa un'eccezione).

Forse è un problema di temporizzazione in cui dialog.dismiss () viene chiamato dopo che l'attività non viene più visualizzata?

Che ne dite di testare cosa succede se si commenta setOnCancelListener e quindi si chiude l'attività mentre l'attività in background è in esecuzione? Quindi onPostExecute proverà a chiudere una finestra di dialogo già ignorata. Se l'app si arresta in modo anomalo, probabilmente puoi semplicemente verificare se la finestra di dialogo è aperta prima di eliminarla.

Ho lo stesso identico problema, quindi lo proverò nel codice.


Ho anche esaminato il codice dimiss (), e in effetti, può essere tranquillamente chiamato da qualsiasi thread. A proposito, ho un problema con i test, poiché questo problema si verifica sui telefoni degli utenti e non sono mai stato in grado di riprodurre da solo :-( Quindi, cercando di capire analizzando il codice ... Secondo i tempi. Stavo pensando a questo, ma non riesco a immaginare una situazione in cui l'attività possa essere chiusa prima della finestra di dialogo. Se si preme INDIETRO, la finestra di dialogo verrà annullata per prima. E la ricreazione automatica dell'attività è vietata dal file manifest, ma può essere ancora può essere ricreata in qualche modo? so se trovi qualcosa!
alex2k8

2

alex,

Potrei sbagliarmi qui, ma sospetto che più telefoni "in the wild" abbiano un bug che li induca a cambiare orientamento su applicazioni contrassegnate come orientate staticamente. Questo succede un po 'sul mio telefono personale e su molti dei telefoni di prova che il nostro gruppo utilizza (inclusi droid, n1, g1, hero). In genere un'app contrassegnata come orientata staticamente (forse in verticale) si stenderà per un secondo o due usando un orientamento orizzontale, quindi tornerà immediatamente indietro. Il risultato finale è che anche se non vuoi che la tua app cambi orientamento, devi essere pronto a farlo. Non so in quali condizioni esatte sia possibile riprodurre questo comportamento, non so se sia specifico per una versione di Android. Tutto quello che so è che l'ho visto accadere molte volte :(

Ti consiglio di utilizzare la soluzione fornita nel link che hai pubblicato che suggerisce di ignorare il metodo Activity onCreateDialog e di consentire al sistema operativo Android di gestire il ciclo di vita dei tuoi dialoghi. Mi sembra che anche se non vuoi che la tua attività cambi orientamento, sta cambiando orientamento da qualche parte. Puoi provare a rintracciare un metodo che impedirà sempre il cambio di orientamento, ma sto cercando di dirti che personalmente non credo che ci sia un modo infallibile che funziona su tutti gli attuali telefoni Android sul mercato.


1
Puoi impedire al tuo dispositivo di cambiare orientamento, ma ci sono un sacco di altre modifiche alla configurazione che distruggono / ricreano la tua attività: una comune fa scorrere dentro o fuori la tastiera.
MaximumGoat

2

Ciò che ha funzionato per me il più delle volte è verificare se l'attività non sta finendo.

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}

2

L'attività è dichiarata NON essere ricreata mediante rotazione o scorrimento della tastiera.

Ho appena avuto lo stesso problema. Correzione per livello API 13 o superiore.
Dai documenti Android:

Nota: se l'applicazione è indirizzata al livello API 13 o superiore (come dichiarato dagli attributi minSdkVersion e targetSdkVersion), è necessario dichiarare anche la configurazione "screenSize", poiché cambia anche quando un dispositivo passa da orientamento verticale a orizzontale.

Quindi ho cambiato il mio manifest in questo:

<activity
        android:name="MyActivity"
        android:configChanges="orientation|screenSize"
        android:label="MyActivityName" >
</activity>

E ora funziona benissimo. L'attività non viene ricreata quando ruoto il telefono, la finestra di dialogo di avanzamento e la visualizzazione rimangono invariati. Nessun errore per me.


questa non è una soluzione. ad esempio ho un'attività Adview da AdMob e dovrebbe ricreare l'attività per le modifiche alle dimensioni.
Batmaci,

2

Prima di tutto fai la gestione degli errori ovunque tu stia provando a chiudere la finestra di dialogo.

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

Se il problema persiste, ignoralo nel metodo onStop () dell'attività.

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

1

Ho avuto lo stesso problema durante l'utilizzo di un pulsante per sincronizzare un elenco dal server: 1) faccio clic sul pulsante 2) Viene visualizzata una finestra di dialogo durante il download dell'elenco dal server 3) Ruoto il dispositivo su un altro orientamento 4) java.lang .IllegalArgumentException: vista non allegata al gestore finestre su postExecute () di AsyncTask durante progress.dismiss ().

Mentre provavo la correzione, ho capito che anche se il problema non si verifica, il mio elenco non mostrava tutti gli elementi.

Ho pensato che quello che volevo fosse che AsyncTask finisse (e chiudesse la finestra di dialogo) prima che l'attività venisse distrutta, così ho fatto dell'oggetto asynctask un attributo e ho ignorato il metodo onDestroy ().

Se l'asynctask impiega molto tempo, forse l'utente sentirà che il dispositivo è lento, ma penso che sia il prezzo che paga per provare a cambiare l'orientamento del dispositivo mentre viene visualizzata la finestra di dialogo di avanzamento. E anche se ci vuole un po 'di tempo, l'app non si arresta in modo anomalo.

private AsyncTask<Boolean, Void, Boolean> atask;

@Override
protected void onDestroy() {
    if (atask!=null)
        try {
            atask.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    super.onDestroy();
}

1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}

3
Sebbene questo frammento di codice possa rispondere alla domanda fornendo alcune spiegazioni su come risolve il problema, i futuri visitatori del sito
potranno

0

Potrebbe sotto il codice funziona per te, funziona perfettamente per me:

private void viewDialog() {
    try {
        Intent vpnIntent = new Intent(context, UtilityVpnService.class);
        context.startService(vpnIntent);
        final View Dialogview = View.inflate(getBaseContext(), R.layout.alert_open_internet, null);
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DIM_BEHIND,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
        windowManager.addView(Dialogview, params);

        Button btn_cancel = (Button) Dialogview.findViewById(R.id.btn_canceldialog_internetblocked);
        Button btn_okay = (Button) Dialogview.findViewById(R.id.btn_openmainactivity);
        RelativeLayout relativeLayout = (RelativeLayout) Dialogview.findViewById(R.id.rellayout_dialog);

            btn_cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                if (Dialogview != null) {
//                                ( (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                    windowManager.removeView(Dialogview);
                                }
                            } catch (final IllegalArgumentException e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } catch (final Exception e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } finally {
                                try {
                                    if (windowManager != null && Dialogview != null) {
//                                    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                        windowManager.removeView(Dialogview);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                            //    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
//                        windowManager.removeView(Dialogview);


                        }
                    });
                }
            });
            btn_okay.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            //        ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                            try {
                                if (windowManager != null && Dialogview != null)
                                    windowManager.removeView(Dialogview);
                                Intent intent = new Intent(getBaseContext(), SplashActivity.class);
                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);


                                context.startActivity(intent);
                            } catch (Exception e) {
                                windowManager.removeView(Dialogview);
                                e.printStackTrace();
                            }
                        }
                    });
                }
            });
        } catch (Exception e) {
            //` windowManager.removeView(Dialogview);
            e.printStackTrace();
        }
    }

Non definire la vista a livello globale se la si chiama dal servizio in background.

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.