java.lang.IllegalArgumentException: представление не привязано к оконному менеджеру

148

У меня есть действие, которое запускает AsyncTask и показывает диалог прогресса на время операции. Деятельность объявлена ​​НЕ воссозданной вращением или скольжением клавиатуры.

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

Как только задача выполнена, я закрываю диалог, но на некоторых телефонах (framework: 1.5, 1.6) выдается такая ошибка:

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)

Мой код:

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

Из того, что я прочитал ( http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html ) и увидел в источниках Android, похоже, что единственно возможная ситуация, чтобы это получить Исключение составляют случаи, когда деятельность была уничтожена. Но, как я уже упоминал, я запрещаю активный отдых для основных событий.

Так что любые предложения очень ценятся.

alex2k8
источник
1
На этот вопрос есть много ответов, если какой-либо из них помог вам, пожалуйста, выберите его в качестве правильного ответа.
Параг Кадам

Ответы:

228

Я тоже иногда получаю эту ошибку когда закрываю диалог и завершаю действие из метода onPostExecute. Я предполагаю, что иногда деятельность завершается до успешного закрытия диалога.

Простое, но эффективное решение, которое работает для меня

@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
источник
44
Простое решение? Да. Эффективное? Возможно в этом случае. Я бы порекомендовал это? НЕТ! Не глотайте ВСЕ исключения, как это! Я бы даже не поймал IllegalArgumentException, но искал бы другое решение.
Симон Форсберг,
6
Потому что, как правило, пустые try-catches - плохая идея ... Хотя иногда это может быть правильным решением.
Томас
3
@Damjan По вашему ответу вы предлагаете поймать тип исключения. Ну, это плохая практика со стороны Google. Вы можете прочитать об этом здесь: не ловите общее исключение .
Янив
17
Я считаю, что это эффективное решение. В общем случае мы не должны этого делать, но поскольку Android Framework не предоставляет нам простой проверки, мы должны использовать необычный способ. Кроме того, если диалоговый вызов isShowing () работает так, как мы ожидаем, такой хак не нужен.
SXC
1
быстрое исправление, пока что-то лучшее не найдено
Rohit Tigga
13

Вот мое «пуленепробиваемое» решение, которое представляет собой сборник всех хороших ответов, которые я нашел по этой теме (благодаря @Damjan и @Kachi). Здесь исключение проглатывается, только если все другие способы обнаружения не увенчались успехом. В моем случае мне нужно закрыть диалоговое окно автоматически, и это единственный способ защитить приложение от сбоев. Я надеюсь, что это поможет вам! Пожалуйста, голосуйте и оставляйте комментарии, если у вас есть замечания или лучшее решение. Спасибо!

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;
        }
    }
Иво Стоянов
источник
1
очень хорошее решение! Настройка dialog = nullне имеет никакого эффекта. И StatusEventDialogдолжен читать как раз Dialog.
hgoebl
1
StatusEventDialog должен быть изменен на диалог.
Sreekanth Karumanaghat
Этот ответ должен быть принят, очень хорошо обработан
синева
Я понимаю, что вы хотите быть "правильным" и использовать try / catch только в том случае, если isDestroyed()он недоступен, но для практических целей не будет ли просто использовать try / catch всегда?
Зунди
11

У меня может быть обходной путь.

У меня возникла та же проблема, когда я загружал много элементов (через файловую систему) в ListViewчерез AsyncTask. Запустил onPreExecute()a ProgressDialog, а затем оба onPostExecute()и onCancelled()(вызывали, когда задача была явно отменена через AsyncTask.cancel()), закрывая его через .cancel().

Получила ту же ошибку "java.lang.IllegalArgumentException: представление не привязано к диспетчеру окон", когда я убивал диалоговое окно в onCancelled()методе AsyncTask(я видел это в отличном приложении Shelves ).

Обходной путь должен был создать публичное поле в, AsyncTaskкоторое содержит ProgressDialog:

public ProgressDialog mDialog;

Затем, onDestroy()когда я отменяю свой AsyncTask, я также могу убить связанный диалог через:

AsyncTask.mDialog.cancel();

Вызов AsyncTask.cancel()DOES срабатывает onCancelled()в AsyncTask, но по какой-то причине к тому времени, когда этот метод вызывается, представление уже уничтожено, и, таким образом, отмена диалога завершается неудачно.

Пол Меннега
источник
Я считаю, что реализация UserTask просто превосходна, как упоминал @Paul. Исходный код находится здесь: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song
В то время как вариант использования может быть найден в том же проекте: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/...
Evi Song
9

Вот правильное решение этой проблемы:

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

Вместо того, чтобы слепо перехватывать все исключения, это решение обращается к корню проблемы: пытается закрыть диалоговое окно, когда действие, использованное для инициализации диалога, уже завершено. Работаю на моем Nexus 4 под управлением KitKat, но должен работать на всех версиях Android.

Качи
источник
3
isDestroyedтребуется API 17+
Androiderson
Почему вам нужно установить mProgressDialog на ноль? Это связано с утечкой памяти? Не могли бы вы объяснить?
Паван
@ Паван, это детали реализации с моей стороны. Это не обязательно, просто функция в этом классе работает. После того, как диалог прогресса скрыт, я установил его на ноль. Когда пользователь хочет показать еще одно диалоговое окно прогресса, создается новый экземпляр.
Качи
Определенно! ((Активность) контекст) .isFinishing () требуется, спасибо! :)
Даниэль Кшичковски
5

Я согласен с мнением «Дамжана».
если вы используете много диалогов, следует закрыть все диалоги в onDestroy () или onStop ().
тогда вы сможете уменьшить частоту возникновения исключения «java.lang.IllegalArgumentException: представление, не привязанное к оконному менеджеру».

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



но немного превосходит ...
чтобы было понятнее, вы не можете показывать диалог после вызова onDestroy.
я не использую, как показано ниже. но это понятно.

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


удачи!

Hogun
источник
Я всегда предпочитаю избегать пустых блоков улова. Стоит попробовать, но так как эту ошибку трудно произвести - только время покажет, работает ли она на самом деле. Спасибо, в любом случае.
Дрор Фичман
что такое пустые блоки? я не использую попробовать / поймать. Превышена рабочая переменная mIsDestroyed. но если вы напишете в коде это диалоговое окно после некоторой работы в другом потоке, вам может понадобиться эта переменная. когда другой поток работает, если действие завершено, вы можете посмотреть на это исключение.
Хогун,
У меня была такая же проблема, и когда я добавил @Override public void onPause () {if (dialog! = Null) dialog.dismiss (); super.onPause (); } до настоящего времени у меня нет этой ошибки ... так что я думаю, что это так же, как ваш ответ, и это действительно полезно
Крис Сим
@ ChrisSim привет! onPuase () и onDestroy () - это защита. когда активность включена, диалог закрывается. и когда вы запускаете приложение, диалог не отображается. ты хочешь этого?
Хогун
@Hogun Да, конечно, я имею в виду ту же идею, я закрываю диалог о паузе, а не об уничтожении, потому что он мне нужен во время паузы, а не уничтожения. Во-вторых, я закрываю это только тогда, когда это не нуль. Спасибо, что объяснили это другим.
Крис Сим
4

Использовать это.

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();
Пралабх Джайн
источник
2
Это почти то же самое решение, которое предложил @Damjan.
Юрий
28
Этого недостаточно, IllegalArgumentException все еще происходит с этой проверкой.
Мерфи
Я сделал то же самое решение, но все еще не знаю, является ли оно эффективным. Единственная разница, я вложил два if, чтобы быть уверенным, что вторая часть .isShowing () не будет оценена, если равна нулю.
Ник
2
Этого недостаточно.
Тран
1
@Nick: В таких случаях нет необходимости вкладывать множественные числа if, &&оператор Java имеет ленивую оценку (также называемую коротким замыканием), что означает, что 2-й операнд не вычисляется, если первый операнд оценивается false(что означает результат &&всегда будет в falseлюбом случае, следовательно , «ленивые» оценка). Аналогичным образом ||не будет оцениваться его второй операнд, если первый оценивает true. Примечание: &и |операторы не имеют такого поведения и , таким образом , всегда оценивать оба операнда.
Матиас
3

У меня была такая же проблема, вы можете решить ее:

@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
источник
2

Я думаю, что ваш код правильный, в отличие от другого ответа. onPostExecute будет работать в потоке пользовательского интерфейса. В этом весь смысл AsyncTask - вам не нужно беспокоиться о вызове runOnUiThread или о обработчиках. Кроме того, согласно документации, dismiss () может быть безопасно вызван из любого потока (не уверен, что они сделали это исключением).

Возможно, это проблема синхронизации, когда dialog.dismiss () вызывается после того, как активность больше не отображается?

Как насчет тестирования, что произойдет, если вы закомментируете setOnCancelListener, а затем выйдете из действия во время выполнения фоновой задачи? Затем ваш onPostExecute попытается закрыть уже закрытое диалоговое окно. Если приложение падает, вы можете просто проверить, открыто ли диалоговое окно, прежде чем закрыть его.

У меня точно такая же проблема, поэтому я собираюсь попробовать это в коде.

Брэндон О'Рурк
источник
Я также изучил код dimiss (), и действительно, его можно безопасно вызывать из любого потока. Кстати, у меня проблема с тестированием, так как эта проблема возникает на телефонах пользователей, и я так и не смог воспроизвести сам :-( Так что пытался выяснить, анализируя код ... По времени. Я думал об этом, но не могу представить себе ситуацию, когда действие может быть закрыто перед диалогом. Если нажать НАЗАД, то сначала будет отменено диалоговое окно. А автоматическое воссоздание активности запрещено файлом манифеста, но может быть, оно все же может быть воссоздано каким-либо образом? я знаю, если вы найдете что-нибудь!
alex2k8
2

Алекс,

Я могу ошибаться, но я подозреваю, что у нескольких телефонов «в дикой природе» есть ошибка, которая заставляет их переключать ориентацию в приложениях, помеченных как статически ориентированные. Это происходит довольно часто на моем личном телефоне и на многих тестовых телефонах, которые использует наша группа (включая droid, n1, g1, hero). Обычно приложение, помеченное как статически ориентированное (возможно, вертикально), выкладывается на секунду или две, используя горизонтальную ориентацию, а затем немедленно переключается обратно. Конечным результатом является то, что даже если вы не хотите, чтобы ваше приложение меняло ориентацию, вы должны быть готовы к этому. Я не знаю, при каких условиях это поведение может быть воспроизведено, я не знаю, относится ли оно к какой-либо версии Android. Все, что я знаю, это то, что я видел это много раз :(

Я бы порекомендовал использовать решение, представленное в опубликованной вами ссылке, которое предлагает переопределить метод Activity onCreateDialog и позволить ОС Android управлять жизненным циклом ваших диалогов. Мне кажется, что даже если вы не хотите, чтобы ваша деятельность меняла ориентацию, она куда-то меняет ориентацию. Вы можете попытаться отследить метод, который всегда будет препятствовать переключению ориентации, но я пытаюсь сказать вам, что лично я не верю, что есть надежный способ, который работает на всех современных телефонах Android на рынке.

Hamy
источник
1
Вы можете удерживать ваше устройство от смены ориентации, но есть множество других изменений конфигурации, которые разрушают / воссоздают вашу активность - обычным является перемещение клавиатуры внутрь или наружу.
MaximumGoat
2

То, что для меня работало большую часть времени, это проверить, не заканчивается ли действие.

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}
Эрве Чт
источник
2

Деятельность объявлена ​​НЕ воссозданной вращением или скольжением клавиатуры.

Просто получил ту же проблему. Исправлено для уровня API 13 или выше.
Из документов Android:

Примечание. Если ваше приложение предназначено для уровня API 13 или выше (как заявлено атрибутами minSdkVersion и targetSdkVersion), вам также следует объявить конфигурацию «screenSize», поскольку она также изменяется, когда устройство переключается между книжной и альбомной ориентациями.

Итак, я изменил свой манифест на это:

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

И теперь работает нормально. Активность не воссоздается, когда я поворачиваю телефон, диалоговое окно прогресса и вид остаются прежними. Нет ошибки для меня.

Шунт
источник
это не решение. например, у меня есть и Adview активность от admob, и она должна воссоздать активность для изменения размера.
Батмачи
2

Прежде всего, делайте обработку ошибок там, где вы пытаетесь закрыть диалог.

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

Если это не помогло, тогда отклоните его в методе действия onStop ().

 @Override
    protected void onStop() {
        super.onStop();
        if ((progressDialog != null) && progressDialog.isShowing()) {
            progressDialog.dismiss();
            progressDialog = null;
        }
    }
Амит Патель
источник
1

У меня была такая же проблема при использовании кнопки для синхронизации списка с сервера: 1) я нажимаю кнопку 2) появляется диалоговое окно прогресса при загрузке списка с сервера 3) я переключаю устройство в другую ориентацию 4) java.lang .IllegalArgumentException: представление не присоединено к оконному менеджеру в postExecute () AsyncTask во время progress.dismiss ().

Когда я попытался исправить это, я понял, что даже если проблема не возникает, в моем списке отображаются не все элементы.

Я решил, что AsyncTask должен завершить (и закрыть диалоговое окно) до уничтожения beign действия, поэтому я сделал объект asynctask атрибутом и переопределил метод onDestroy ().

Если асинхронная задача занимает много времени, пользователь, возможно, почувствует, что устройство работает медленно, но я думаю, что он платит за попытку изменить ориентацию устройства, пока отображается диалоговое окно прогресса. И даже если это занимает некоторое время, приложение не падает.

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
источник
1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}
vikseln
источник
3
В то время как этот фрагмент кода может ответить на вопрос, предоставляя некоторое объяснение того, как он решает проблему, поможет будущим посетителям сайта понять ваш ответ
RobV
0

Может ниже код работает для вас, он работает для меня прекрасно:

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

Не определяйте свое представление глобально, если вы вызываете его из фоновой службы.

Шашват Гупта
источник