Widok androida nie jest dołączony do menedżera okien


111

Mam kilka z następujących wyjątków:

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)

Wyszukałem go w Google i zobaczyłem, że ma to coś wspólnego z wyskakującymi okienkami i obracaniem ekranu, ale nie ma odniesienia do mojego kodu.

Oto pytania:

  1. czy istnieje sposób, aby dowiedzieć się dokładnie, kiedy występuje ten problem?
  2. poza obróceniem ekranu, czy jest inne zdarzenie lub czynność, która wyzwala ten błąd?
  3. jak temu zapobiec?

1
Sprawdź, czy możesz wyjaśnić, w jaki sposób działanie jest opisane w manifeście i jakie działanie jest widoczne na ekranie, gdy wystąpi błąd. Sprawdź, czy możesz odłożyć swój problem do minimalnego testu.
Josh Lee,

Może próbujesz zmodyfikować swój widok, zanim View#onAttachedToWindow()został wywołany?
Alex Lockwood

Odpowiedzi:


163

Miałem ten problem, w którym przy zmianie orientacji ekranu czynność kończyła się przed AsyncTask z zakończeniem okna dialogowego postępu. onPause()Wydawałem się rozwiązać ten problem, ustawiając okno dialogowe na wartość null, a następnie sprawdzając to w AsyncTask przed zamknięciem.

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

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

... w moim 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 AsyncTask działa w wątku, który przetrwa ponowne uruchomienie aktywności i może zawierać odwołania do martwego działania i jego widoków (jest to efektywny wyciek pamięci, choć prawdopodobnie krótkotrwały - zależy od tego, ile czasu zajmie wykonanie zadania ). Powyższe rozwiązanie działa dobrze, jeśli akceptujesz krótkotrwały wyciek pamięci. Zalecanym podejściem jest anulowanie () wszystkich uruchomionych AsyncTask, gdy działanie jest wstrzymane.
Stevie

8

Po walce z tym problemem w końcu znalazłem to obejście:

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

Czasami dobra obsługa wyjątków działa dobrze, jeśli nie było lepszego rozwiązania tego problemu.


5

Jeśli masz jakiś Activityprzedmiot w pobliżu, możesz użyć isDestroyed()metody:

Activity activity;

// ...

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

Jest to miłe, jeśli masz nieanonimową AsyncTaskpodklasę, której używasz w różnych miejscach.


3

Używam niestandardowej klasy statycznej, która sprawia, że ​​pokazuje i ukrywa okno dialogowe. ta klasa jest wykorzystywana także do innych czynności, a nie tylko do jednej czynności. Teraz problem, który opisałeś, pojawił się również i zostałem na noc, aby znaleźć rozwiązanie.

Na koniec przedstawiam Ci rozwiązanie!

jeśli chcesz pokazać lub zamknąć okno dialogowe i nie wiesz, która czynność zainicjowała okno dialogowe w celu jego dotknięcia, poniższy kod jest dla Ciebie.

 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

Powyższe rozwiązanie nie zadziałało. Więc zrobiłem to ProgressDialogglobalnie, a następnie dodałem to do mojej działalności

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

tak więc w przypadku zniszczenia aktywności, ProgressDialog również zostanie zniszczony.


0

Pytanie 1):

Biorąc pod uwagę, że komunikat o błędzie nie wydaje się wskazywać, która linia kodu powoduje problem, możesz go wyśledzić za pomocą punktów przerwania. Punkty przerwania wstrzymują wykonywanie programu, gdy program dotrze do określonych wierszy kodu. Dodając punkty przerwania do krytycznych lokalizacji, możesz określić, która linia kodu powoduje awarię. Na przykład, jeśli twój program ulega awarii w linii setContentView (), możesz umieścić tam punkt przerwania. Gdy program zostanie uruchomiony, zatrzyma się przed uruchomieniem tej linii. Jeśli następnie wznowienie spowoduje awarię programu przed osiągnięciem następnego punktu przerwania, wtedy wiadomo, że wiersz, który go zabił, znajdował się między dwoma punktami przerwania.

Dodawanie punktów przerwania jest łatwe, jeśli używasz Eclipse. Kliknij prawym przyciskiem myszy margines po lewej stronie kodu i wybierz opcję „Przełącz punkt przerwania”. Następnie musisz uruchomić aplikację w trybie debugowania, który to przycisk wygląda jak zielony owad obok normalnego przycisku uruchamiania. Gdy program osiągnie punkt przerwania, Eclipse przełączy się na perspektywę debugowania i pokaże linię, w której oczekuje. Aby ponownie uruchomić program, poszukaj przycisku „Wznów”, który wygląda jak zwykły przycisk „Odtwórz”, ale z pionowym paskiem po lewej stronie trójkąta.

Możesz również wypełnić swoją aplikację wpisem Log.d („Moja aplikacja”, „Tutaj znajdują się informacje, które wskazują, gdzie znajduje się wiersz dziennika”), który następnie wysyła wiadomości w oknie LogCat Eclipse. Jeśli nie możesz znaleźć tego okna, otwórz je za pomocą Window -> Show View -> Other ... -> Android -> LogCat.

Mam nadzieję, że to pomoże!


7
Problem występuje na telefonach klientów, więc nie mam opcji debugowania. Również problem występuje cały czas, po prostu się zdarza, więc nie wiem, jak go śledzić
Daniel Benedykt

Nadal możesz otrzymywać wiadomości debugowania z telefonu, jeśli możesz go zdobyć. W menu -> ustawienia -> aplikacje -> programowanie jest opcja debugowania USB. Jeśli jest włączona, możesz następnie podłączyć telefon, a LogCat przechwytuje wszystkie normalne linie debugowania. Poza tym ... cóż ... przerywanie można by tym wyjaśnić w zależności od stanu programu. Przypuszczam, że musiałbyś bawić się programem, aby zobaczyć, czy możesz odtworzyć problem.
Steve Haley

4
Jak powiedziałem wcześniej, problem występuje na telefonie klienta i nie mam do niego dostępu. Klient może być na innym kontynencie :)
Daniel Benedykt

Na rynku są aplikacje, które wyślą Ci swój logcat.
Tim Green

1
Pamiętaj, że możesz obrócić emulator, naciskając klawisz numeryczny 9
Rob

0

Dodałem do manifestu dla tego działania następujący tekst

android:configChanges="keyboardHidden|orientation|screenLayout"

19
To nie jest rozwiązanie: system może zniszczyć Twoją aktywność również z innych powodów.
Roel

1
Czy mógłbyś wyjaśnić swoją odpowiedź?
Leebeedev

0

zgodnie z kodem menedżera okien (link tutaj ), dzieje się tak, gdy widok, który próbujesz zaktualizować (który prawdopodobnie należy do okna dialogowego, ale nie jest konieczny) nie jest już dołączony do rzeczywistego katalogu głównego okien.

jak sugerowali inni, przed wykonaniem specjalnych operacji na oknach dialogowych należy sprawdzić stan działania.

oto odpowiadający kod, który jest przyczyną problemu (skopiowany z kodu źródłowego Androida):

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

kod, który tutaj napisałem, jest tym, co robi Google. to, co napisałem, ma na celu pokazanie przyczyny tego problemu.
programista Androida

0

Mój problem został rozwiązany przez uhlockowanie obracania ekranu na moim Androidzie, aplikacja, która sprawiała mi problem, teraz działa idealnie


0

Inną opcją jest nie uruchamianie zadania asynchronicznego, dopóki okno dialogowe nie zostanie dołączone do okna przez nadpisanie onAttachedToWindow () w oknie dialogowym, w ten sposób jest ono zawsze dopuszczalne.


0

Lub po prostu możesz dodać

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

co sprawi, że ProgressDialognie będzie można anulować


-2

Dlaczego nie spróbować złapać, w ten sposób:

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

IllegalStateOfException Powinien być traktowany w lepszy sposób, a nie tylko po to, aby zachowywać się nienormalnie.
Lavakush

-3

kiedy deklarujesz aktywność w manifeście, potrzebujesz android: configChanges = "orientacja"

przykład:

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

1
Ten tag jest wymagany tylko wtedy, gdy programista nie chce niszczyć i odtwarzać aktywności przez system Android.
Ankit
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.