java.lang.IllegalArgumentException: exibição não anexada ao gerenciador de janelas

148

Eu tenho uma atividade que inicia o AsyncTask e mostra o diálogo de progresso pela duração da operação. A atividade é declarada NÃO ser recriada por rotação ou slide do teclado.

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

Depois que a tarefa é concluída, eu encerro a caixa de diálogo, mas em alguns telefones (estrutura: 1.5, 1.6) esse erro é gerado:

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)

Meu código é:

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

Pelo que li ( http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html ) e visto nas fontes do Android, parece a única situação possível para obter isso exceção é quando a atividade foi destruída. Mas, como mencionei, proíbo a recreação de atividades para eventos básicos.

Portanto, todas as sugestões são muito apreciadas.

alex2k8
fonte
1
Esta pergunta tem muitas respostas. Se alguma delas o ajudou, selecione-a como a resposta certa.
Parag Kadam

Respostas:

228

Às vezes, eu também recebo esse erro quando descartar a caixa de diálogo e concluir a atividade do método onPostExecute. Acho que às vezes a atividade termina antes que o diálogo seja encerrado com êxito.

Solução simples e eficaz que funciona para mim

@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;
    }  
}
Damjan
fonte
44
Solução simples? Sim. Eficaz? Talvez neste caso. Eu recomendaria? NÃO! Não engula TODAS as exceções assim! Eu nem pegaria o IllegalArgumentException, mas procuraria outra solução.
Simon Forsberg
6
Porque geralmente try-catchs vazios é uma má idéia ... Embora às vezes possa ser a coisa certa a se fazer.
Thomas
3
@ Damjan Pela sua resposta, você sugere o tipo de captura Exception. Bem, essa é uma prática ruim do Google. Você pode ler sobre isso aqui: Não Capture Exceção Genérica .
23413 Yaniv
17
Eu acredito que isso é uma solução eficaz. Em casos gerais, não devemos fazer isso, mas como o Android Framework não fornece nenhuma verificação fácil para nós, temos que usar formas incomuns. Além disso, se a chamada isShowing () de uma caixa de diálogo estiver funcionando como esperado, não precisamos desse tipo de hack.
SXC 21/08/13
1
solução rápida até que algo melhor é encontrado
Rohit Tigga
13

Aqui está minha solução "à prova de balas", que é a compilação de todas as boas respostas que encontrei sobre esse tópico (graças a @Damjan e @Kachi). Aqui a exceção é engolida apenas se todas as outras formas de detecção não tiverem êxito. No meu caso, preciso fechar a caixa de diálogo automaticamente e esta é a única maneira de proteger o aplicativo contra falhas. Espero que ajude você! Por favor, vote e deixe comentários se você tiver comentários ou uma solução melhor. Obrigado!

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;
        }
    }
Ivo Stoyanov
fonte
1
solução muito boa! A configuração dialog = nullnão tem nenhum efeito. E StatusEventDialogdeve ler apenas Dialog.
hgoebl
1
StatusEventDialog deve ser alterado para caixa de diálogo.
Sreekanth Karumanaghat
Esta resposta deve ser a mais aceita, muito bem tratada
blueware
Entendo que você deseja estar "correto" e usar apenas try / catch caso isDestroyed()não esteja disponível, mas por motivos práticos não seria o mesmo usar sempre o try / catch sempre?
zundi
11

Eu posso ter uma solução alternativa.

Estava com o mesmo problema, onde estou carregando muitos itens (via sistema de arquivos) em um ListViewvia an AsyncTask. Teve o onPreExecute()acionamento de um ProgressDialoge, em seguida, ambos onPostExecute()e onCancelled()(chamado quando a tarefa é cancelada explicitamente via AsyncTask.cancel()) fechando-a via .cancel().

Obteve o mesmo erro "java.lang.IllegalArgumentException: exibição não anexada ao gerenciador de janelas" quando estava matando a caixa de diálogo no onCancelled()método do AsyncTask(eu já tinha visto isso no excelente aplicativo Shelves ).

A solução alternativa foi criar um campo público no AsyncTaskque contém o ProgressDialog:

public ProgressDialog mDialog;

Então, onDestroy()quando cancelo o meu AsyncTask, também posso matar a caixa de diálogo associada via:

AsyncTask.mDialog.cancel();

A chamada AsyncTask.cancel()DOES é acionada onCancelled()no AsyncTask, mas, por algum motivo, quando o método é chamado, a Visualização já foi destruída e, portanto, o cancelamento da caixa de diálogo está falhando.

Paul Mennega
fonte
Acho a implementação UserTask excelente como o @Paul mencionou. O código fonte está aqui: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/...
Evi Canção
Embora o caso de uso possa ser encontrado no mesmo projeto: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song
9

Aqui está a solução correta para resolver esse 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;
    }
}

Em vez de capturar todas as exceções às cegas, esta solução aborda a raiz do problema: tentar reduzir o tamanho de um diálogo quando a atividade usada para inicializar o diálogo já tiver sido concluída. Trabalhando no meu Nexus 4 executando o KitKat, mas deve funcionar para todas as versões do Android.

Kachi
fonte
3
isDestroyedrequer API 17+
Androiderson 03/04
Por que você precisa definir mProgressDialog como null? Isso está relacionado ao vazamento de memória? Você poderia explicar?
Pawan
@Pawan, é um detalhe de implementação do meu lado. Não é necessário, é apenas o modo como a função nesta classe funciona. Depois que uma caixa de diálogo de progresso está oculta, eu a defino como nula. Quando um usuário deseja mostrar outra caixa de diálogo de progresso, uma nova instância é instanciada.
Kachi
Definitivamente! ((Activity) context) .isFinishing () é obrigatório, obrigado! :)
Daniel Krzyczkowski
5

eu concordo com uma opinião de 'Damjan'.
se você usar muitas caixas de diálogo, feche todas as caixas de diálogo em onDestroy () ou onStop ().
poderá reduzir a frequência 'java.lang.IllegalArgumentException: ocorre a exceção da exibição não anexada ao gerenciador de janelas'.

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



mas pouco excede ...
para tornar mais claro, você evita exibir qualquer caixa de diálogo após a ligação de OnDestroy.
Eu não uso como abaixo. mas está claro.

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


boa sorte!

Hogun
fonte
Eu sempre vou preferir evitar blocos vazios de captura. vale a pena tentar, mas como esse erro é difícil de produzir - apenas o tempo dirá se está realmente funcionando. obrigado de qualquer maneira.
Dror Fichman
o que são blocos de captura vazios? eu não uso try / catch. uma variável mIsDestroyed é excedida em funcionamento. mas se você escrever para codificar essa caixa de diálogo depois de trabalhar em outro encadeamento, poderá precisar dessa variável. quando outro encadeamento estiver funcionando, se a atividade estiver concluída, você poderá observar esta exceção.
Hogun 27/03
Eu tive o mesmo problema e quando adicionei @Override public void onPause () {if (dialog! = Null) dialog.dismiss (); super.onPause (); } Até ao presente Eu não tenho esse erro ... então eu acho que é o mesmo como a sua resposta e sua realmente útil
Chris Sim
@ChrisSim hello! onPuase () e onDestroy () são defference. quando a atividade está onPuase, o diálogo é fechado. e quando você executa o aplicativo, a caixa de diálogo não é mostrada. você quer isso?
Hogun
@ Hogun Sim, é claro, quero dizer a mesma idéia, estou fechando a caixa de diálogo em pausa em vez de destruir, porque eu preciso disso em pausa e não em destruição. Em segundo lugar, eu a fecho exatamente quando não é nulo. Obrigado por explicar isso para outras pessoas.
Chris Sim
4

Usa isto.

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();
Pralabh Jain
fonte
2
É quase a mesma solução que a @Damjan propôs.
Yury
28
Isso não é suficiente, a IllegalArgumentException ainda acontece com essa verificação.
Murphy #
Eu fiz a mesma solução, mas ainda não sei se é eficaz. Única diferença: aninhei dois if's para garantir que a segunda parte .isShowing () não será avaliada se for nula.
22413 Nick
2
Isto não é suficiente.
Trant
1
@ Nick: Não há necessidade de aninhar múltiplos ifem casos como este, o &&operador do Java tem uma avaliação lenta (também chamada de curto-circuito), o que significa que o segundo operando não é avaliado se o primeiro avaliar false(o que significa o resultado de o &&será sempre falsede qualquer maneira, portanto avaliação "preguiçoso"). Da mesma forma ||, não avaliará seu segundo operando se o primeiro avaliar true. Nota: os operadores &e |não têm esse comportamento e, portanto, sempre avaliam os dois operandos.
Matthias
3

Eu tive o mesmo problema, você pode resolvê-lo:

@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;
    }  
}
spacebiker
fonte
2

Acho que seu código está correto, diferente da outra resposta sugerida. onPostExecute será executado no thread da interface do usuário. Esse é o ponto principal do AsyncTask - você não precisa se preocupar em chamar runOnUiThread ou lidar com manipuladores. Além disso, de acordo com os documentos, pode-se chamar de forma segura () a partir de qualquer encadeamento (sem ter certeza de que eles fizeram disso a exceção).

Talvez seja um problema de tempo em que dialog.dismiss () está sendo chamado depois que a atividade não é mais exibida?

Que tal testar o que acontece se você comentar o setOnCancelListener e sair da atividade enquanto a tarefa em segundo plano estiver em execução? Em seguida, seu onPostExecute tentará descartar uma caixa de diálogo já descartada. Se o aplicativo travar, você provavelmente pode apenas verificar se a caixa de diálogo está aberta antes de descartá-la.

Estou tendo exatamente o mesmo problema, então vou testá-lo em código.

Brandon O'Rourke
fonte
Também procurei no código dimiss () e, de fato, ele pode ser chamado com segurança a partir de qualquer thread. BTW, eu tenho um problema com os testes, pois esse problema ocorre nos telefones dos usuários e nunca fui capaz de reproduzir sozinho :-( Então, tentando descobrir analisando o código ... De acordo com o tempo. Estava pensando nisso, mas não consigo imaginar uma situação em que a Atividade possa ser encerrada antes do Dialog. Se BACK for pressionado, o cancelará primeiro. E a recreação automática da atividade é proibida pelo arquivo de manifesto, mas pode ser que ainda possa ser recriada de alguma maneira? me saber se você encontrar alguma coisa!
alex2k8
2

alex

Eu posso estar errado aqui, mas suspeito que vários telefones "em estado selvagem" têm um bug que os leva a mudar de orientação em aplicativos marcados como estaticamente orientados. Isso acontece bastante no meu telefone pessoal e em muitos dos telefones de teste que nosso grupo usa (incluindo droid, n1, g1, hero). Normalmente, um aplicativo marcado como estaticamente orientado (talvez verticalmente) se expõe por um segundo ou dois usando uma orientação horizontal e depois retorna imediatamente. O resultado final é que, mesmo que você não queira que seu aplicativo mude de orientação, você deve estar preparado para isso. Não sei em que condições exatas esse comportamento pode ser reproduzido, não sei se é específico para uma versão do Android. Tudo o que sei é que já vi isso acontecer muitas vezes :(

Eu recomendaria usar a solução fornecida no link que você postou, que sugere substituir o método Activity onCreateDialog e permitir que o sistema operacional Android gerencie o ciclo de vida dos seus Diálogos. Parece-me que, embora você não queira que sua atividade mude de orientação, ela está mudando de orientação em algum lugar. Você pode tentar rastrear um método que sempre impeça a troca de orientação, mas estou tentando lhe dizer que pessoalmente não acredito que exista uma maneira infalível de funcionar em todos os telefones Android atuais do mercado.

Hamy
fonte
1
Você pode impedir que seu dispositivo mude de orientação, mas existem várias outras alterações na configuração que destroem / recriam sua Atividade - uma comum é deslizar ou desabilitar um teclado.
MaximumGoat 26/07/12
2

O que funcionou para mim na maioria das vezes é verificar se a Atividade não está terminando.

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}
Herve Qui
fonte
2

A atividade é declarada NÃO ser recriada por rotação ou slide do teclado.

Só tenho o mesmo problema. Correção para API nível 13 ou mais alto.
Dos documentos do Android:

Nota: Se o seu aplicativo tiver como alvo a API nível 13 ou superior (conforme declarado pelos atributos minSdkVersion e targetSdkVersion), você também deverá declarar a configuração "screenSize", pois ela também muda quando um dispositivo alterna entre as orientações retrato e paisagem.

Então, eu mudei meu manifesto para isso:

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

E agora funciona bem. A atividade não é recriada quando eu giro o telefone, a caixa de diálogo de progresso e a exibição permanecem as mesmas. Nenhum erro para mim.

Shunt
fonte
isso não é uma solução. por exemplo, eu tenho uma atividade do Adview da admob e ela deve recriar a atividade para as alterações de tamanho.
batmaci
2

Antes de tudo, faça o tratamento de erros sempre que estiver tentando descartar a caixa de diálogo.

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

Se isso não corrigir, descarte-o no método onStop () da atividade.

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

Eu tive o mesmo problema ao usar um botão para sincronizar uma lista do servidor: 1) clico no botão 2) Uma caixa de diálogo de progresso é exibida durante o download da lista do servidor 3) mudo o dispositivo para outra orientação 4) java.lang .IllegalArgumentException: exibição não anexada ao gerenciador de janelas em postExecute () do AsyncTask durante progress.dismiss ().

Ao tentar a correção, percebi que, mesmo que o problema não ocorra, minha lista não estava mostrando todos os itens.

Imaginei que o que eu queria era que o AsyncTask terminasse (e descartasse a caixa de diálogo) antes que a atividade fosse destruída, então fiz do objeto asynctask um atributo e substitui o método onDestroy ().

Se o assíncrono demorar muito tempo, talvez o usuário sinta que o dispositivo está lento, mas acho que esse é o preço que ele paga por tentar mudar a orientação do dispositivo enquanto a caixa de diálogo de progresso é exibida. E mesmo que demore algum tempo, o aplicativo não falha.

private AsyncTask<Boolean, Void, Boolean> atask;

@Override
protected void onDestroy() {
    if (atask!=null)
        try {
            atask.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    super.onDestroy();
}
shadowglas
fonte
1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}
vikseln
fonte
3
Embora esse trecho de código possa responder à pergunta, fornecer algumas explicações sobre como ele resolve o problema ajudará futuros visitantes do site a entender sua resposta
RobV
0

Abaixo, o código funciona para você, funciona perfeitamente para mim:

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

Não defina sua visão globalmente se você a chamar do serviço em segundo plano.

Shashwat Gupta
fonte