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

189

Używam ACRA do zgłaszania awarii aplikacji. Otrzymywałem View not attached to window managerkomunikat o błędzie i myślałem, że to naprawiłem, pakując pDialog.dismiss();instrukcję if:

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

Zmniejszyło to liczbę View not attached to window managerwypadków, które otrzymuję, ale wciąż się pojawiają i nie jestem pewien, jak to rozwiązać.

Komunikat o błędzie:

java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:425)
at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:327)
at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:83)
at android.app.Dialog.dismissDialog(Dialog.java:330)
at android.app.Dialog.dismiss(Dialog.java:312)
at com.package.class$LoadAllProducts.onPostExecute(class.java:624)
at com.package.class$LoadAllProducts.onPostExecute(class.java:1)
at android.os.AsyncTask.finish(AsyncTask.java:631)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:644)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5419)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1046)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:862)
at dalvik.system.NativeStart.main(Native Method)

Fragment kodu:

class LoadAllProducts extends AsyncTask<String, String, String> 
{

    /**
     * Before starting background thread Show Progress Dialog
     * */
    @Override
    protected void onPreExecute() 
    {
        super.onPreExecute();
        pDialog = new ProgressDialog(CLASS.this);
        pDialog.setMessage("Loading. Please wait...");
        pDialog.setIndeterminate(false);
        pDialog.setCancelable(false);
        pDialog.show();
    }

    /**
     * getting All products from url
     * */
    protected String doInBackground(String... args) 
    {
        // Building Parameters
        doMoreStuff("internet");
        return null;
    }


    /**
     * After completing background task Dismiss the progress dialog
     * **/
    protected void onPostExecute(String file_url) 
    {
         // dismiss the dialog after getting all products
         if (pDialog!=null) 
         {
                if (pDialog.isShowing()) 
                {
                    pDialog.dismiss();   //This is line 624!    
                }
         }
         something(note);
    }
}

Oczywisty:

    <activity
        android:name="pagename.CLASS" 
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout"            
        android:label="@string/name" >
    </activity>

Czego brakuje mi, aby powstrzymać katastrofę?

Howli
źródło
1
Czy kiedykolwiek to rozgryzłeś? Mam ten sam problem. Nie mogę tego rozgryźć.
tomjung
Niestety nie. Za chwilę mogę zacząć nagrodę. Powinieneś sprawdzić niektóre inne wątki zajmujące się tym problemem, na wypadek gdyby Ci pomogły.
Howli
Twoje AsyncTaskjest zadeklarowane w środku Activitylub Fragment?
erakitin
Proszę zamieścić funkcje „doMoreStuff ()” i „coś ()”.
szaleństwo
Problem może być spowodowany zbyt dużą pracą głównego wątku, więc spróbuj użyć programów obsługi, aby wyświetlić progressdialog, a jeśli (pDialog! = Null) ta linia nie jest potrzebna, ponieważ samaShows sprawdza, czy okno dialogowe jest w toku, czy nie.
Madhu

Odpowiedzi:

446

Jak odtworzyć błąd:

  1. Włącz tę opcję w urządzeniu: Settings -> Developer Options -> Don't keep Activities.
  2. Naciśnij przycisk Home podczas AsyncTaskwykonywania i ProgressDialogpokazywania.

System operacyjny Android zniszczy działanie, gdy tylko zostanie ukryte. Kiedy onPostExecutezostanie wywołany, Activitybędzie w stanie „wykończenia” i ProgressDialognie będzie do niego przywiązany Activity.

Jak to naprawić:

  1. Sprawdź stan aktywności w swojej onPostExecutemetodzie.
  2. Odrzuć metodę ProgressDialogin onDestroy. W przeciwnym razie android.view.WindowLeakedzostanie zgłoszony wyjątek. Ten wyjątek zwykle pochodzi z okien dialogowych, które są nadal aktywne po zakończeniu działania.

Wypróbuj ten naprawiony kod:

public class YourActivity extends Activity {

    private void showProgressDialog() {
        if (pDialog == null) {
            pDialog = new ProgressDialog(StartActivity.this);
            pDialog.setMessage("Loading. Please wait...");
            pDialog.setIndeterminate(false);
            pDialog.setCancelable(false);
        }
        pDialog.show();
    }

    private void dismissProgressDialog() {
        if (pDialog != null && pDialog.isShowing()) {
            pDialog.dismiss();
        }
    }

    @Override
    protected void onDestroy() {
        dismissProgressDialog();
        super.onDestroy();
    }

    class LoadAllProducts extends AsyncTask<String, String, String> {

        // Before starting background thread Show Progress Dialog
        @Override
        protected void onPreExecute() {
            showProgressDialog();
        }

        //getting All products from url
        protected String doInBackground(String... args) {
            doMoreStuff("internet");
            return null;
        }

        // After completing background task Dismiss the progress dialog
        protected void onPostExecute(String file_url) {
            if (YourActivity.this.isDestroyed()) { // or call isFinishing() if min sdk version < 17
                return;
            }
            dismissProgressDialog();
            something(note);
        }
    }
}
erakityna
źródło
9
Świetna odpowiedź! BTW, isShowing () nie jest konieczne, ponieważ metodaISSISS () nic nie zrobi, jeśli isShowing () == false. Kod źródłowy
Peter Zhao
3
Jakie są korzyści z korzystania isDestroyed()przez isFinishing()wszystkich interfejsów API dla tego konkretnego celu?
Alexander Abakumov,
@AlexanderAbakumov: Z tego, co rozumiem, isFinishing()nie ma gwarancji true, że działanie zostanie zniszczone przez system, zobacz dokumentację Działania .
Markus Penguin
1
@MarkusPenguin: Right. Ale w tym przypadku, jeśli ktoś spróbuje skorzystać z porady z komentarza autora // or call isFinishing() if min sdk version < 17, spotka go ten sam wyjątek. Potrzebujemy więc innego rozwiązania niż ta odpowiedź dla aplikacji działających na API <17.
Alexander Abakumov
@AlexanderAbakumov Mam ten sam problem, ale dokończenie nie działa dla mnie, zadziałało zapisanie Słabego Odniesienia do mojej aktywności w AsyncCallback, a następnie:myActivityWeakReference.get() != null && !myActivityWeakReference.get().isFinishing()
rusito23,
35

Problemem może być to, Activityże były finishedlub były progress of finishing.

Dodaj czek isFinishingi odrzuć okno dialogowe tylko wtedy, gdy jestfalse

if (!YourActivity.this.isFinishing() && pDialog != null) {
    pDialog.dismiss();
}

isFinishing: sprawdź, czy czynność jest w trakcie kończenia, albo dlatego, że wywołałeś , albo finishktoś inny poprosił o jej zakończenie.

Libin
źródło
1
Po zaznaczeniu wyjątek jest nadal zgłaszany
Marcos Vasconcelos
My ProgressDialog należy do klasy, która nie jest zajęciem, więc nie mogę użyć czeku isFinishing @Libin
Maniraj
11

Do Dialogtworzenia w a Fragmentużywam następującego kodu:

ProgressDialog myDialog = new ProgressDialog(getActivity());
myDialog.setOwnerActivity(getActivity());
...
Activity activity = myDialog.getOwnerActivity();
if( activity!=null && !activity.isFinishing()) {
    myDialog.dismiss();
}

Używam tego wzorca, aby poradzić sobie ze sprawą, kiedy Fragmentmożna odłączyć a od Activity.

Teng-pao Yu
źródło
8

Zobacz, jak działa Kodeks tutaj:

Po wywołaniu zadania asynchronicznego zadanie asynchroniczne działa w tle. to jest pożądane. Teraz to zadanie asynchroniczne ma okno dialogowe postępu dołączone do działania, jeśli zapytasz, jak wyświetlić kod:

pDialog = new ProgressDialog(CLASS.this);

Mijasz Class.this argument jako kontekst do argumentu. Tak więc okno dialogowe Postęp jest nadal dołączone do działania.

Rozważmy teraz scenariusz: jeśli spróbujemy zakończyć działanie za pomocą metody finish (), gdy zadanie asynchroniczne jest w toku, to jest to miejsce, w którym próbujesz uzyskać dostęp do zasobu dołączonego do działania, tj. progress bar Kiedy działanie już nie jest tam.

Stąd otrzymujesz:

java.lang.IllegalArgumentException: View not attached to the window manager

Rozwiązanie tego:

1) Upewnij się, że okno dialogowe zostało zamknięte lub anulowane przed zakończeniem działania.

2) Zakończ działanie, dopiero po zamknięciu okna dialogowego, to znaczy, że zadanie asynchroniczne zostało zakończone.

Kailas
źródło
1
Odnośnie # 2: nie masz pełnej kontroli nad tym, kiedy zakończyć Activity; Android może to zakończyć w dowolnym momencie. Więc # 2 nie ma sensu.
Alexander Abakumov
7

Oparty na odpowiedzi @erakitin, ale również kompatybilny z wersjami Androida <poziom API 17. Niestety Activity.isDestroyed () jest obsługiwany tylko od poziomu API 17, więc jeśli kierujesz na starszy poziom interfejsu API, taki jak ja, musisz sprawdź to sam. Po tym nie mam View not attached to window managerwyjątku.

Przykładowy kod

public class MainActivity extends Activity {
    private TestAsyncTask mAsyncTask;
    private ProgressDialog mProgressDialog;
    private boolean mIsDestroyed;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (condition) {
            mAsyncTask = new TestAsyncTask();
            mAsyncTask.execute();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mAsyncTask != null && mAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
            Toast.makeText(this, "Still loading", Toast.LENGTH_LONG).show();
            return;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mIsDestroyed = true;

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

    public class TestAsyncTask extends AsyncTask<Void, Void, AsyncResult> {    
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mProgressDialog = ProgressDialog.show(MainActivity.this, "Please wait", "doing stuff..");
        }

        @Override
        protected AsyncResult doInBackground(Void... arg0) {
            // Do long running background stuff
            return null;
        }

        @Override
        protected void onPostExecute(AsyncResult result) {
            // Use MainActivity.this.isDestroyed() when targeting API level 17 or higher
            if (mIsDestroyed)// Activity not there anymore
                return;

            mProgressDialog.dismiss();
            // Handle rest onPostExecute
        }
    }
}
Kapé
źródło
4
@Override
public void onPause() {
    super.onPause();

    if(pDialog != null)
        pDialog .dismiss();
    pDialog = null;
}

skieruj to .

Meghna
źródło
3

Zastąp onConfigurationChanged i odrzuć okno dialogowe postępu. Jeśli okno dialogowe postępu zostanie utworzone w trybie pionowym, a odrzucone w układzie poziomym, wyświetli się błąd Widok niepowiązany z menedżerem okien.

Zatrzymaj także pasek postępu i zatrzymaj zadanie asynchroniczne w metodach onPause (), onBackPressed i onDestroy.

if(asyncTaskObj !=null && asyncTaskObj.getStatus().equals(AsyncTask.Status.RUNNING)){

    asyncTaskObj.cancel(true);

}
Pratapi Hemant Patel
źródło
3

Zastąp onDestroy działania i zamknij okno dialogowe i unieważnij

protected void onDestroy ()
    {
        if(mProgressDialog != null)
            if(mProgressDialog.isShowing())
                mProgressDialog.dismiss();
        mProgressDialog= null;
    }
surya
źródło
3

Po pierwsze, przyczyną awarii jest indeks decorView o wartości -1, możemy to wiedzieć z kodu źródłowego Androida, jest fragment kodu:

klasa: android.view.WindowManagerGlobal

plik: WindowManagerGlobal.java

private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
//here, view is decorView,comment by OF
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }

więc otrzymujemy rozdzielczość śledzenia, po prostu oceniamy indeks decorView, jeśli jest większy niż 0, kontynuujemy lub po prostu wracamy i rezygnujemy z odrzucenia, kodujemy w następujący sposób:

try {
            Class<?> windowMgrGloable = Class.forName("android.view.WindowManagerGlobal");
            try {
                Method mtdGetIntance = windowMgrGloable.getDeclaredMethod("getInstance");
                mtdGetIntance.setAccessible(true);
                try {
                    Object windownGlobal = mtdGetIntance.invoke(null,null);
                    try {
                        Field mViewField = windowMgrGloable.getDeclaredField("mViews");
                        mViewField.setAccessible(true);
                        ArrayList<View> mViews = (ArrayList<View>) mViewField.get(windownGlobal);
                        int decorViewIndex = mViews.indexOf(pd.getWindow().getDecorView());
                        Log.i(TAG,"check index:"+decorViewIndex);
                        if (decorViewIndex < 0) {
                            return;
                        }
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (pd.isShowing()) {
            pd.dismiss();
        }
aolphn
źródło
2

Pomiń tę dismiss()metodę:

@Override
public void dismiss() {
    Window window = getWindow();
    if (window == null) {
        return;
    }
    View decor = window.getDecorView();
    if (decor != null && decor.getParent() != null) {
        super.dismiss();
    }
}

Aby odtworzyć problem, zakończ działanie przed zamknięciem okna dialogowego.

piorun
źródło
1

najlepsze rozwiązanie. Sprawdź pierwszy kontekst jest kontekstem działania lub kontekstem aplikacji, jeśli kontekst działania to tylko sprawdzenie działania jest zakończone, a następnie nie zadzwoń dialog.show()lubdialog.dismiss();

Zobacz przykładowy kod poniżej ... mam nadzieję, że będzie pomocny!

Wyświetl okno dialogowe

if (context instanceof Activity) {
   if (!((Activity) context).isFinishing())
     dialog.show();
}

Zamknij okno dialogowe

if (context instanceof Activity) {
       if (!((Activity) context).isFinishing())
         dialog.dismiss();
    }

Jeśli chcesz dodać więcej czeków, dodaj dialog.isShowing()lub dialog !-nullużyj &&warunku.

Yogesh Rathi
źródło
0

Przyczyną tego problemu jest zakończenie aktywności przed wywołaniem funkcji zwalniania. Podejmij wyjątek i sprawdź dokładnie dziennik ADB.

/**
     * After completing background task Dismiss the progress dialog
     * **/
    protected void onPostExecute(String file_url) {
    try {
         if (pDialog!=null) {
            pDialog.dismiss();   //This is line 624!    
         }
    } catch (Exception e) {
        // do nothing
    }
     something(note);
}
Rajiv Manivannan
źródło
0

Mam sposób na odtworzenie tego wyjątku.

Używam 2 AsyncTask. Jeden wykonuje długie zadanie, a drugi wykonuje krótkie. Po zakończeniu krótkiego zadania zadzwoń finish(). Po zakończeniu długiego zadania i wywołaniu Dialog.dismiss()następuje awaria.

Oto mój przykładowy kod:

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");

        new AsyncTask<Void, Void, Void>(){
            @Override
            protected void onPreExecute() {
                mProgressDialog = ProgressDialog.show(MainActivity.this, "", "plz wait...", true);
            }

            @Override
            protected Void doInBackground(Void... nothing) {
                try {
                    Log.d(TAG, "long thread doInBackground");
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                Log.d(TAG, "long thread onPostExecute");
                if (mProgressDialog != null && mProgressDialog.isShowing()) {
                    mProgressDialog.dismiss();
                    mProgressDialog = null;
                }
                Log.d(TAG, "long thread onPostExecute call dismiss");
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

        new AsyncTask<Void, Void, Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Log.d(TAG, "short thread doInBackground");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                super.onPostExecute(aVoid);
                Log.d(TAG, "short thread onPostExecute");
                finish();
                Log.d(TAG, "short thread onPostExecute call finish");
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

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

Możesz spróbować i dowiedzieć się, jak najlepiej rozwiązać ten problem. Z moich badań wynika, że ​​istnieją co najmniej 4 sposoby, aby to naprawić:

  1. Odpowiedź @ erakitin: zadzwoń isFinishing() celu sprawdzenia stanu aktywności
  2. @ Odpowiedź Kapé: ustaw flagę, aby sprawdzić stan działania
  3. Użyj try / catch, aby sobie z tym poradzić.
  4. Zadzwoń AsyncTask.cancel(false)w onDestroy(). Zapobiegnie to wykonaniu asynctask, onPostExecute()ale onCancelled()zamiast niego.
    Uwaga: onPostExecute()nadal będzie działać, nawet jeśli zadzwonisz AsyncTask.cancel(false)na starszy system operacyjny Android, taki jak Android 2.XX

Możesz wybrać najlepszy dla siebie.

Yueh-Ming Chien
źródło
0

Być może inicjujesz pDialog globalnie, a następnie usuń go i zainicjalizuj lokalnie swój widok lub okno dialogowe. Mam ten sam problem, zrobiłem to i mój problem został rozwiązany. Mam nadzieję, że to zadziała u ciebie.

Shashwat Gupta
źródło
0

zamknęliśmy również nasze okno dialogowe dotyczące onPausemetody lub onDestroymetody

@Override
protected void onPause() {
    super.onPause();
    dialog.dismiss();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    dialog.dismiss();
}
tarun bhola
źródło