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подкласс, который вы используете в разных местах.


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


            }
        } 

 }

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

код, который я здесь написал, - это то, что делает 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);
        }
    }

IllegalStateOfException Следует обрабатывать лучше, а не просто вести себя ненормально.
Lavakush

-3

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

пример:

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

1
Этот тег требуется только в том случае, если разработчик не хочет уничтожать и воссоздавать активность системой Android.
Ankit
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.