Android View não anexado ao gerenciador de janelas

111

Estou tendo algumas das seguintes exceções:

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)

Eu pesquisei no Google e vi que tem algo a ver com pop-ups e virar a tela, mas não há referência ao meu código.

As perguntas são:

  1. existe uma maneira de saber exatamente quando esse problema está acontecendo?
  2. além de virar a tela, há outro evento ou ação que desencadeia esse erro?
  3. como faço para evitar que isso aconteça?
Daniel Benedykt
fonte
1
Veja se você consegue explicar como a atividade é descrita no manifesto e qual atividade está na tela quando o erro ocorre. Veja se você consegue separar seu problema em um caso de teste mínimo.
Josh Lee
Talvez você esteja tentando modificar sua visão antes de View#onAttachedToWindow()ter sido chamado?
Alex Lockwood

Respostas:

163

Tive esse problema em que, em uma mudança de orientação da tela, a atividade terminava antes de AsyncTask com a caixa de diálogo de progresso concluída. onPause()Parece que resolvi isso definindo a caixa de diálogo como nula e verificando isso na AsyncTask antes de dispensar.

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

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

... em minha AsyncTask:

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

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}
johnnyblizzard
fonte
12
@YekhezkelYovel o AsyncTask está sendo executado em um thread que sobrevive ao reinício da atividade e pode conter referências à atividade agora morta e suas visualizações (isso é efetivamente um vazamento de memória, embora provavelmente de curto prazo - depende de quanto tempo a tarefa leva para ser concluída ) A solução acima funciona bem, se você estiver satisfeito em aceitar um vazamento de memória de curto prazo. A abordagem recomendada é cancelar () qualquer AsyncTask em execução quando a atividade for pausada.
Stevie
8

Depois de lutar contra esse problema, finalmente acabo com esta solução 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;
    }
}

Às vezes, um bom tratamento de exceções funciona bem se não houver uma solução melhor para esse problema.

blueware
fonte
5

Se você tiver um Activityobjeto por perto, pode usar o isDestroyed()método:

Activity activity;

// ...

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

Isso é bom se você tiver uma AsyncTasksubclasse não anônima que você usa em vários lugares.

Shawkinaw
fonte
3

Estou usando uma classe estática personalizada que cria e oculta uma caixa de diálogo. esta classe está sendo usada por outras atividades também, não apenas uma atividade. Agora, o problema que você descreveu também apareceu para mim e eu passei a noite para encontrar uma solução

Finalmente apresento a solução!

se você deseja mostrar ou descartar uma caixa de diálogo e não sabe qual atividade iniciou a caixa de diálogo para tocá-la, o código a seguir é para você.

 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
fonte
1

A solução acima não funcionou para mim. Então, o que fiz foi tomar ProgressDialogglobalmente e adicionar isso à minha atividade

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

de modo que, caso a atividade seja destruída, o ProgressDialog também será destruído.

Kishan Solanki
fonte
0

Para a pergunta 1):

Considerando que a mensagem de erro não parece dizer qual linha do seu código está causando o problema, você pode rastreá-lo usando pontos de interrupção. Os pontos de interrupção pausam a execução do programa quando o programa chega a linhas de código específicas. Adicionando pontos de interrupção a locais críticos, você pode determinar qual linha de código causa o travamento. Por exemplo, se seu programa está travando em uma linha setContentView (), você pode colocar um ponto de interrupção lá. Quando o programa for executado, ele fará uma pausa antes de executar essa linha. Se a continuação fizer com que o programa trave antes de atingir o próximo ponto de interrupção, você saberá que a linha que matou o programa estava entre os dois pontos de interrupção.

Adicionar pontos de interrupção é fácil se você estiver usando o Eclipse. Clique com o botão direito na margem à esquerda do código e selecione "Alternar ponto de interrupção". Em seguida, você precisa executar seu aplicativo no modo de depuração, o botão que se parece com um inseto verde ao lado do botão de execução normal. Quando o programa atinge um ponto de interrupção, o Eclipse muda para a perspectiva de depuração e mostra a linha que está esperando. Para iniciar o programa em execução novamente, procure o botão 'Continuar', que se parece com um 'Play' normal, mas com uma barra vertical à esquerda do triângulo.

Você também pode preencher seu aplicativo com Log.d ("Meu aplicativo", "Algumas informações aqui que indicam onde está a linha de log"), que então posta mensagens na janela LogCat do Eclipse. Se você não conseguir encontrar essa janela, abra-a com Window -> Show View -> Other ... -> Android -> LogCat.

Espero que ajude!

Steve Haley
fonte
7
O problema está acontecendo nos telefones dos clientes, então não tenho a opção de depurar. Além disso, o problema está acontecendo o tempo todo, apenas acontecendo às vezes, então não sei como rastreá-lo
Daniel Benedykt
Você ainda pode receber mensagens de depuração de um telefone, se conseguir colocar as mãos em um. No menu -> configurações -> aplicativos -> desenvolvimento, há uma opção para Depuração USB. Se ativado, você pode conectar o telefone e o LogCat captura todas as linhas de depuração normais. Fora isso ... bem ... a intermitência pode ser explicada por isso, dependendo do estado do programa. Suponho que você teria que usar o programa para ver se poderia recriar o problema.
Steve Haley,
4
Como eu disse antes, o problema está acontecendo no telefone de um cliente e não tenho acesso a ele. O cliente pode estar em outro continente :)
Daniel Benedykt
Existem aplicativos no mercado que enviarão seu logcat por e-mail.
Tim Green
1
Só para você saber que pode girar o emulador pressionando a tecla do teclado numérico 9
Rob
0

Eu adicionei o seguinte ao manifesto dessa atividade

android:configChanges="keyboardHidden|orientation|screenLayout"
Rickey
fonte
19
Isso não é uma solução: o sistema pode destruir sua atividade por outros motivos também.
Roel de
1
Você poderia explicar sua resposta, por favor?
Leebeedev
0

de acordo com o código do windowManager (link aqui ), isso ocorre quando a visualização que você está tentando atualizar (que provavelmente pertence a uma caixa de diálogo, mas não é necessária) não está mais anexada à raiz real das janelas.

como outros sugeriram, você deve verificar o status da atividade antes de realizar operações especiais em seus diálogos.

aqui está o código relevante, que é a causa do problema (copiado do código-fonte do 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;
        }
    }
desenvolvedor android
fonte
o código que escrevi aqui é o que o Google faz. o que escrevi é para mostrar a causa desse problema.
desenvolvedor Android de
0

Meu problema foi resolvido bloqueando a rotação da tela no meu Android, o aplicativo que estava me causando um problema agora funciona perfeitamente

Roger
fonte
0

Outra opção é não iniciar a tarefa assíncrona até que a caixa de diálogo seja anexada à janela, substituindo onAttachedToWindow () na caixa de diálogo, dessa forma sempre será descartável.

Nick Palmer
fonte
0

Ou simplesmente você pode adicionar

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

o que tornará o ProgressDialognão cancelável

Sarith Vasu
fonte
-2

Por que não tentar pegar, assim:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }
Heasen
fonte
IllegalStateOfException Deve ser tratada de uma maneira melhor, em vez de apenas se comportar de forma anormal.
Lavakush de
-3

quando você declara atividade no manifesto, você precisa do android: configChanges = "orientação"

exemplo:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>
Cristian
fonte
1
Esta tag é necessária apenas se o desenvolvedor não quiser destruir e recriar a atividade pelo sistema Android.
Ankit