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()того, як вас викликали?
Алекс Локвуд

Відповіді:


163

У мене виникла ця проблема, коли під час зміни орієнтації екрана активність закінчилася перед AsyncTask із завершеним діалогом прогресу. Я, здається, вирішив це, встановивши діалогове вікно на нуль, 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 працює в потоці, який переживає перезапуск активності і може містити посилання на вже мертву активність та її перегляди (це фактично витік пам'яті, хоч, мабуть, і короткий термін - залежить від часу, на який потрібно виконати завдання ). Наведене вище рішення працює чудово, якщо ви раді прийняти короткочасну витік пам'яті. Рекомендований підхід - скасувати () будь-які запущені програми AsyncTask, коли Дія призупинена.
Стіві

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
Просто ви знаєте, що можете повернути емулятор, натиснувши клавішу numpad 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. те, що я написав, - щоб показати причину цієї проблеми.
андроїд розробник

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 слід вирішувати кращим чином, а не просто вести себе ненормально.
Лавакуш

-3

коли ви оголошуєте активність у маніфесті, вам потрібен android: configChanges = "орієнтація"

приклад:

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

1
Цей тег потрібен лише в тому випадку, якщо розробник не хоче знищувати та відтворювати діяльність за допомогою системи Android.
Анкіт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.