Android View не привязан к оконному менеджеру

111

У меня есть некоторые из следующих исключений:

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)

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

Вопросы следующие:

  1. есть ли способ узнать, когда именно возникает эта проблема?
  2. Есть ли другое событие или действие, вызывающее эту ошибку, кроме поворота экрана?
  3. как мне предотвратить это?
Даниэль Бенедикт
источник
1
Посмотрите, можете ли вы объяснить, как действие описано в манифесте и какое действие отображается на экране при возникновении ошибки. Посмотрите, сможете ли вы избавиться от своей проблемы в минимальном тестовом сценарии.
Джош Ли,
Может быть, вы пытаетесь изменить свое представление до того, View#onAttachedToWindow()как был вызван?
Alex Lockwood

Ответы:

163

У меня была эта проблема, когда при изменении ориентации экрана действие завершалось до AsyncTask с завершением диалогового окна прогресса. Я, кажется, решил это, установив диалоговое окно на null, onPause()а затем проверив это в AsyncTask перед тем, как закрыть.

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

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

... в моей 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 работает в потоке, который переживает перезапуск активности и может содержать ссылки на теперь мертвую Activity и ее представления (это фактически утечка памяти, хотя, вероятно, краткосрочная - зависит от того, сколько времени займет выполнение задачи ). Приведенное выше решение отлично работает, если вы счастливы принять краткосрочную утечку памяти. Рекомендуемый подход - отменить () любую запущенную AsyncTask, когда Activity приостановлено.
Стиви,
8

После борьбы с этой проблемой я наконец нашел обходной путь:

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

Иногда хорошая обработка исключений работает хорошо, если для этой проблемы не было лучшего решения.

голубая посуда
источник
5

Если у вас есть какой-то Activityобъект, вы можете использовать isDestroyed()метод:

Activity activity;

// ...

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

Это хорошо, если у вас есть неанонимный AsyncTaskподкласс, который вы используете в разных местах.

Shawkinaw
источник
3

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

Наконец, я представляю вам решение!

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

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

Вышеупомянутое решение не сработало для меня. Итак, я взял ProgressDialogглобально, а затем добавил это в свою деятельность

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

так что в случае, если активность будет уничтожена, ProgressDialog также будет уничтожен.

Кишан Соланки
источник
0

По вопросу 1):

Учитывая, что сообщение об ошибке, похоже, не говорит, какая строка вашего кода вызывает проблему, вы можете отследить ее, используя точки останова. Точки останова приостанавливают выполнение программы, когда программа переходит к определенной строке кода. Добавляя точки останова в критические места, вы можете определить, какая строка кода вызывает сбой. Например, если ваша программа дает сбой на строке setContentView (), вы можете поставить там точку останова. Когда программа запускается, она приостанавливается перед запуском этой строки. Если затем возобновление приводит к сбою программы до достижения следующей точки останова, значит, вы знаете, что строка, завершившая программу, находилась между двумя точками останова.

Добавить точки останова легко, если вы используете Eclipse. Щелкните правой кнопкой мыши поле слева от кода и выберите «Переключить точку останова». Затем вам нужно запустить приложение в режиме отладки, кнопка, которая выглядит как зеленое насекомое рядом с кнопкой обычного запуска. Когда программа достигает точки останова, Eclipse переключится на перспективу отладки и покажет вам строку, в которой она ожидает. Чтобы снова запустить программу, найдите кнопку «Возобновить», которая выглядит как обычная кнопка «Воспроизвести», но с вертикальной полосой слева от треугольника.

Вы также можете заполнить свое приложение Log.d («Мое приложение», «Здесь некоторая информация, которая сообщает вам, где находится строка журнала»), который затем отправляет сообщения в окно LogCat Eclipse. Если вы не можете найти это окно, откройте его с помощью Window -> Show View -> Other ... -> Android -> LogCat.

Надеюсь, это поможет!

Стив Хейли
источник
7
Проблема возникает на телефонах клиентов, поэтому у меня нет возможности отладить ее. Кроме того, проблема возникает постоянно, иногда случается, поэтому не знаю, как ее отследить
Даниэль Бенедикт
Вы по-прежнему можете получать отладочные сообщения с телефона, если сможете его достать. В меню -> настройки -> приложения -> разработка есть опция USB-отладки. Если этот параметр включен, вы можете подключить телефон, и LogCat перехватит все обычные строки отладки. Помимо этого ... ну ... прерывистость может быть объяснена этим в зависимости от состояния программы. Я полагаю, вам придется повозиться с программой, чтобы посмотреть, сможете ли вы воссоздать проблему.
Стив Хейли,
4
Как я уже сказал, проблема возникает на клиентском телефоне, и у меня нет к нему доступа. Заказчик может быть на другом континенте :)
Даниэль Бенедикт
На рынке есть приложения, которые отправят вам свой журнал по электронной почте.
Тим Грин
1
Просто чтобы вы знали, что вы можете повернуть эмулятор, нажав клавишу цифровой клавиатуры 9
Роб
0

Я добавил в манифест этого действия следующее:

android:configChanges="keyboardHidden|orientation|screenLayout"
Рики
источник
19
Это не решение: система может разрушить вашу деятельность и по другим причинам.
Roel
1
Не могли бы вы объяснить свой ответ?
Либидев
0

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

как предлагали другие, вы должны проверить статус активности перед выполнением специальных операций с вашими диалоговыми окнами.

вот соответствующий код, который является причиной проблемы (скопирован из исходного кода 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;
        }
    }
разработчик Android
источник
код, который я здесь написал, - это то, что делает Google. Я написал, чтобы показать причину этой проблемы.
разработчик Android
0

Моя проблема была решена путем блокировки поворота экрана на моем Android, приложение, которое вызывало у меня проблему, теперь работает отлично

Роджер
источник
0

Другой вариант - не запускать асинхронную задачу до тех пор, пока диалоговое окно не будет прикреплено к окну, переопределив onAttachedToWindow () в диалоговом окне, таким образом, это всегда будет запрещено.

Ник Палмер
источник
0

Или просто вы можете добавить

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

что сделает ProgressDialogневозможным отмену

Сарит Васу
источник
-2

Почему бы не попробовать поймать вот так:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }
Heasen
источник
IllegalStateOfException Следует обрабатывать лучше, а не просто вести себя ненормально.
Lavakush
-3

когда вы объявляете активность в манифесте, вам нужен android: configChanges = "Ориентация"

пример:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>
Cristian
источник
1
Этот тег требуется только в том случае, если разработчик не хочет уничтожать и воссоздавать активность системой Android.
Ankit