Vue non associée au crash du gestionnaire de fenêtres


195

J'utilise ACRA pour signaler les plantages d'applications. Je recevais un View not attached to window managermessage d'erreur et je pensais l'avoir corrigé en enveloppant le pDialog.dismiss();dans une instruction if:

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

Cela a réduit le nombre d' View not attached to window manageraccidents que je reçois, mais j'en reçois toujours et je ne sais pas comment le résoudre.

Message d'erreur:

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)

Extrait de code:

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

Manifeste:

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

Que me manque-t-il pour empêcher ce crash de se produire?


1
Avez-vous déjà compris celui-ci? J'ai le même problème. Je n'arrive pas à comprendre.
tomjung

Malheureusement non. Je pourrais commencer une prime dans un instant. Vous devriez vérifier certains des autres fils traitant de ce problème au cas où ils vous aideraient.
Howli

Votre AsyncTaskest déclaré à l'intérieur Activityou Fragment?
erakitin

Veuillez poster les fonctions "doMoreStuff ()" et "something ()".
berserk

Le problème peut être dû à un travail excessif du thread principal, essayez donc d'utiliser des gestionnaires pour afficher la boîte de dialogue de progression, et si (pDialog! = Null) cette ligne n'est pas nécessaire, car isShowing vérifie lui-même si la boîte de dialogue est en cours ou non.
Madhu

Réponses:


457

Comment reproduire le bogue:

  1. Activez cette option sur votre appareil: Settings -> Developer Options -> Don't keep Activities.
  2. Appuyez sur le bouton Accueil pendant l' AsyncTaskexécution de et que ProgressDialogs'affiche.

Le système d'exploitation Android détruira une activité dès qu'elle sera masquée. Quand onPostExecuteest appelé, le Activitytestament sera en état de "finition" et le ProgressDialogsera non attaché Activity.

Comment le réparer:

  1. Vérifiez l'état de l'activité dans votre onPostExecuteméthode.
  2. Ignorez la méthode ProgressDialogin onDestroy. Sinon, une android.view.WindowLeakedexception sera levée. Cette exception provient généralement des boîtes de dialogue qui sont toujours actives lorsque l'activité se termine.

Essayez ce code fixe:

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

13
Très bonne réponse! BTW, isShowing () n'est pas nécessaire car licencier () ne fera rien si isShowing () == false. Code source
Peter Zhao

3
Quel est l'avantage d'utiliser isDestroyed()over isFinishing()sur toutes les API à cette fin spécifique?
Alexander Abakumov

@AlexanderAbakumov: D'après ce que je comprends, ce isFinishing()n'est pas garanti truesi l'activité est détruite par le système, voir la documentation sur les activités .
Markus Penguin

1
@MarkusPenguin: C'est vrai. Mais, dans ce cas, si l'on a tenté d'utiliser un conseil du commentaire de l'auteur // or call isFinishing() if min sdk version < 17, il se heurte à la même exception. Nous avons donc besoin d'une solution différente de celle-ci pour les applications exécutées sur API <17.
Alexander Abakumov

@AlexanderAbakumov J'ai le même problème, mais isFinishing ne fonctionne pas pour moi, cela a fonctionné en sauvant une référence faible à mon activité dans AsyncCallback, puis:myActivityWeakReference.get() != null && !myActivityWeakReference.get().isFinishing()
rusito23

37

Le problème pourrait être que le Activityfichier a été finishedou est entré progress of finishing.

Ajoutez une coche isFinishinget fermez la boîte de dialogue uniquement lorsquefalse

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

isFinishing: vérifiez si cette activité est en cours de fin, soit parce que vous l'avez appelée, soit parce que finishquelqu'un d'autre a demandé qu'elle se termine.


1
Avec le chèque, l'exception est toujours lancée
Marcos Vasconcelos

Mon ProgressDialog est dans une classe qui n'est pas une activité, je ne peux donc pas utiliser le contrôle isFinishing @Libin
Maniraj

12

Pour Dialogcréé en a Fragment, j'utilise le code suivant:

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

J'utilise ce modèle pour traiter le cas où un Fragmentpeut être détaché du Activity.


8

Voyez comment le code fonctionne ici:

Après avoir appelé la tâche Async, la tâche async s'exécute en arrière-plan. c'est souhaitable. Maintenant, cette tâche Async a une boîte de dialogue de progression qui est attachée à l'activité, si vous demandez comment voir le code:

pDialog = new ProgressDialog(CLASS.this);

Vous transmettez le Class.thiscontexte à l'argument. Ainsi, la boîte de dialogue Progression est toujours associée à l'activité.

Considérons maintenant le scénario: si nous essayons de terminer l'activité en utilisant la méthode finish (), alors que la tâche asynchrone est en cours, c'est le point où vous essayez d'accéder à la ressource attachée à l'activité, c'est-à-dire le progress bar moment où l'activité n'est plus Là.

Par conséquent, vous obtenez:

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

Solution à ceci:

1) Assurez-vous que la boîte de dialogue est fermée ou annulée avant la fin de l'activité.

2) Terminez l'activité, uniquement après la fermeture de la boîte de dialogue, c'est-à-dire que la tâche asynchrone est terminée.


3
Concernant le n ° 2: vous n'avez pas le contrôle total sur le moment de terminer Activity; Android peut le terminer à tout moment. Donc, # 2 n'a pas de sens.
Alexander Abakumov

7

Basé sur la réponse @erakitin, mais également compatible pour les versions d'Android <API niveau 17. Malheureusement, Activity.isDestroyed () n'est pris en charge qu'à partir du niveau API 17, donc si vous ciblez un niveau d'API plus ancien comme moi, vous devrez vérifiez-le vous-même. Je n'ai pas eu l' View not attached to window managerexception après ça.

Exemple de code

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

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

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

renvoyer ceci .


3

Remplacez onConfigurationChanged et fermez la boîte de dialogue de progression. Si la boîte de dialogue de progression est créée en portrait et disparaît en paysage, elle lancera une erreur Vue non attachée au gestionnaire de fenêtres.

Arrêtez également la barre de progression et arrêtez la tâche asynchrone dans les méthodes onPause (), onBackPressed et onDestroy.

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

    asyncTaskObj.cancel(true);

}

3

Remplacer onDestroy de l'activité et ignorer votre boîte de dialogue et la rendre nulle

protected void onDestroy ()
    {
        if(mProgressDialog != null)
            if(mProgressDialog.isShowing())
                mProgressDialog.dismiss();
        mProgressDialog= null;
    }

3

Tout d'abord, la raison du crash est que l'index de decorView est -1, nous pouvons le savoir à partir du code source Android, il y a un extrait de code:

classe: android.view.WindowManagerGlobal

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

nous obtenons donc une résolution de suivi, il suffit de juger l'index de decorView, s'il est supérieur à 0, continuez ou retournez simplement et abandonnez le code comme suit:

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

Il s'agit d'un piratage dangereux reposant sur de nombreux détails d'implémentation internes.
hqzxzwb

2

Remplacez la dismiss()méthode comme ceci:

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

Pour reproduire le problème, terminez simplement l'activité avant de fermer la boîte de dialogue.


1

meilleure solution. Vérifier que le premier contexte est le contexte de l'activité ou le contexte de l'application si le contexte de l'activité, puis uniquement vérifier que l'activité est terminée ou non, puis appeler dialog.show()oudialog.dismiss();

Voir l'exemple de code ci-dessous ... j'espère qu'il vous sera utile!

Afficher la boîte de dialogue

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

Fermer la boîte de dialogue

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

Si vous souhaitez ajouter plus de contrôles, ajoutez dialog.isShowing()ou dialog !-nullutilisez une &&condition.


0

Ce problème est dû au fait que votre activité est terminée avant que la fonction de rejet ne soit appelée. Gérez l'exception et vérifiez votre journal ADB pour la raison exacte.

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

0

J'ai eu un moyen de reproduire cette exception.

J'utilise 2 AsyncTask. L'un effectue une tâche longue et l'autre une tâche courte. Une fois la tâche courte terminée, appelez finish(). Lorsque la tâche longue est terminée et appelée Dialog.dismiss(), elle se bloque.

Voici mon exemple de code:

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

Vous pouvez essayer ceci et découvrir quelle est la meilleure façon de résoudre ce problème. D'après mon étude, il existe au moins 4 façons de résoudre ce problème:

  1. Réponse de @ erakitin: appel isFinishing()pour vérifier l'état de l'activité
  2. Réponse de @ Kapé: définir un indicateur pour vérifier l'état de l'activité
  3. Utilisez try / catch pour le gérer.
  4. Appel AsyncTask.cancel(false)à onDestroy(). Cela empêchera l'exécution de l'asynctask onPostExecute()mais s'exécutera à la onCancelled()place.
    Remarque: onPostExecute()exécutera toujours même si vous appelez AsyncTask.cancel(false)sur un ancien système d'exploitation Android, comme Android 2.XX

Vous pouvez choisir le meilleur pour vous.


0

Peut-être initialisez-vous pDialog globalement, puis supprimez-le et initialisez votre vue ou votre boîte de dialogue localement.J'ai le même problème, j'ai fait cela et mon problème est résolu. J'espère que cela fonctionnera pour vous.


0

nous avons également ignoré notre dialogue sur la onPauseméthode ou la onDestroyméthode

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

@Override
protected void onDestroy() {
    super.onDestroy();
    dialog.dismiss();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.