Фрагмент - removeGlobalOnLayoutListener IllegalStateException

Я пытаюсь получить высоту и ширину ImageView в Fragment со следующим ViewTreeObserver:

import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;

private ImageView imageViewPicture;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false);
    setHasOptionsMenu(true);

    ...

    final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
    observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () {
        @Override public void onGlobalLayout() {
            observer.removeGlobalOnLayoutListener(this);
        }
    });

    return view;
}

Запуск этого кода приводит к следующему исключению:

10-12 23:45:26.145: E/AndroidRuntime(12592): FATAL EXCEPTION: main
10-12 23:45:26.145: E/AndroidRuntime(12592): java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:509)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.removeGlobalOnLayoutListener(ViewTreeObserver.java:356)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.thimmey.rezepte.AddRecipeActivity_GeneralFragment$1.onGlobalLayout(AddActivity_GeneralFragment.java:83)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:566)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1736)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2644)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.os.Handler.dispatchMessage(Handler.java:99)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.os.Looper.loop(Looper.java:137)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.app.ActivityThread.main(ActivityThread.java:4517)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at java.lang.reflect.Method.invokeNative(Native Method)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at java.lang.reflect.Method.invoke(Method.java:511)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at dalvik.system.NativeStart.main(Native Method)

В документах говорится, что removeGlobalOnLayoutListener устарел, но если я использую removeOnGlobalLayoutListener, как и было предложено, я получаю ошибку undefined.

Что я делаю неправильно?

Ответ 1

попробуйте следующее:

   ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
   observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () {
    @Override
     public void onGlobalLayout() {

       imageViewPicture.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });

Ответ 2

Другое решение будет работать нормально, но это не объясняет, почему это происходит.

Здесь есть несколько проблем:

GlobalOn VS OnGlobal

Использование версии GlobalOn даст вам предупреждение об устаревании, но если вы проверите источник, он просто вызывает версию OnGlobal, поэтому они эквивалентны. Разница в том, что вы можете использовать OnGlobal только с уровня API 16, поэтому, если вы планируете использовать более раннюю версию, вам придется использовать GlobalOn и иметь дело с этим предупреждением об отказе.

Почему он мертв?

Обратите внимание, что этот вопрос касается кода в onCreateView, где imageViewPicture еще не привязано к иерархии представлений, оно только что завышено. Если вы посмотрите на View.getViewTreeObserver(), вы увидите, что в этом случае он создает "плавающий" наблюдатель. Затем, когда представление помещается в иерархию dispatchAttachedToWindow, которая затем объединяет окно и плавающие наблюдатели вида:

info.mTreeObserver.merge(mFloatingTreeObserver);

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

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

Являются ли они теми же наблюдателями?

Как Данял, я также был озадачен тем, почему removeGlobalOnLayoutListener работает на другом наблюдателе, но теперь ясно, что временный плавающий наблюдатель. Когда плавающий объединяется с наблюдателем окна, слушатели переносятся на другого наблюдателя, поэтому вызов View.getViewTreeObserver() позже даст вам наблюдателя, содержащего вашего слушателя. Новый наблюдатель теперь отвечает за обработку вашего слушателя.

Но это работает иногда!, а другое решение

Что касается замечаний Zordid о том, почему во многих случаях это нормально держать в локальной (замыкающей) переменной, можно объяснить аналогичными рассуждениями: просто завышенный вид в onCreateView еще не прикреплен только после его возврата, Большинство из вас, вероятно, видели в методе после onCreateView в жизненном цикле. float (OP) будет работать нормально, если код, связанный с наблюдателем, находился в onViewCreated. У каждого метода жизненного цикла есть свои обязанности, поэтому я бы посоветовал разбить код следующим образом:

private ImageView imageViewPicture;

@Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false);

    // assuming ... includes:
    this.imageViewPicture = view.findViewById(R.id.image);

    return view;
}

@Override public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener () {
        @Override public void onGlobalLayout() {
            observer.removeGlobalOnLayoutListener(this);
        }
    });
}

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

То же самое, вероятно, верно для Activity.setContentView, который сразу же прикрепляет завышенное представление и обычно вызывается в onCreate, поэтому иерархия находится в режиме реального времени к тому времени, когда вы играете с наблюдателями/слушателями.

Ответ 3

Лучше попробуйте проверить, жив ли getViewTreeObserver. Я думаю, что следующий код будет работать. На основе fooobar.com/info/18545/..., fooobar.com/info/18547/... и некоторых других.

** Обновление **

После чтения Удалить прослушиватель из ViewTreeObserver, fooobar.com/info/18402/... Я немного переписал.

public static void captureGlobalLayout(@NonNull final View view,
                                       @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
    ViewTreeObserver vto = view.getViewTreeObserver();
    if (vto.isAlive()) {
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        ViewTreeObserver vto = view.getViewTreeObserver();
                        if (vto.isAlive()) {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                                vto.removeOnGlobalLayoutListener(this);
                            } else {
                                //noinspection deprecation
                                vto.removeGlobalOnLayoutListener(this);
                            }
                            listener.onGlobalLayout();
                        }
                    }
                });
    } else {
        view.post(new Runnable() {
            @Override
            public void run() {
                listener.onGlobalLayout();
            }
        });

    }
}

** Старый ответ **

Странно, но даже если view.getViewTreeObserver().isAlive() истинно, то следующий вызов view.getViewTreeObserver() может снова привести к тому же исключению. Итак, я окружил код с помощью try-catch. Возможно, вы можете пропустить все это и сохранить только блок view.post(...).

if (view.getViewTreeObserver().isAlive()) {
    try {
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (view.getViewTreeObserver().isAlive()) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                }
                yourCode();
            }
        });
    } catch (IllegalStateException e) {
        e.printStackTrace();
        // The same as below branch.
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.post(new Runnable() {
                    @Override
                    public void run() {
                        yourCode();
                    }
                });
            }
        });
    }
} else {
    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Try to wait until the view is ready.
            view.post(new Runnable() {
                @Override
                public void run() {
                    yourCode();
                }
            });
        }
    });
}

Вероятно, view.post(...) достаточно, но я назвал его из фонового потока, поэтому, если вы сделаете то же самое, лучше его вызывать из runOnUiThread(new Runnable() ....