【问题标题】:Fragment - removeGlobalOnLayoutListener IllegalStateException片段 - removeGlobalOnLayoutListener IllegalStateException
【发布时间】:2012-10-12 22:07:08
【问题描述】:

我正在尝试使用以下ViewTreeObserverFragment 中获取ImageView 的高度和宽度:

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,则会收到未定义的错误。

我做错了什么?

【问题讨论】:

    标签: android android-view


    【解决方案1】:

    试试这个:

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

    【讨论】:

    • 谢谢!现在我也可以使用建议的 removeOnGlobalLayoutListener。
    • 但是我已经看到很多代码,其中观察者存储在局部变量中 - 并且有效。不明白为什么在很多情况下这是有效的,但不是全部?你知道吗??
    • 这两个观察者不一样吗?所以原版仍然会有听众。
    • @Zordid,当视图已经附加到层次结构时,这些情况必须在生命周期的后期,请参阅"But it works sometimes!"
    • removeGlobalOnLayoutListener 已弃用。有什么建议吗?
    【解决方案2】:

    The other solution 可以正常工作,但无法解释为什么会发生这种情况。

    这里有几个问题需要解决:

    GlobalOn VS OnGlobal

    使用 GlobalOn 版本会给你一个弃用警告,但如果你检查源它只是调用 OnGlobal 版本,所以它们是等价的。不同之处在于您只能使用 API 级别 16 中的 OnGlobal 版本,因此如果您针对的是早期版本,则必须使用 GlobalOn 并处理该弃用警告。

    它为什么死了?

    请注意,这个问题是关于onCreateView 中的代码,其中imageViewPicture 尚未附加到视图层次结构中,它只是被膨胀了。如果您快速查看View.getViewTreeObserver(),您会发现它在这种情况下创建了一个“浮动”观察者。然后当视图被放入层次结构dispatchAttachedToWindow 时,它会合并窗口和视图的浮动观察者:

    info.mTreeObserver.merge(mFloatingTreeObserver);
    

    它将所有注册的侦听器移动到窗口的观察者并杀死浮动观察者。

    你获得的原始观察者是浮动的,它已经死了,因此对其调用添加/删除会导致上述异常。

    他们是同一个观察者吗?

    作为 Danyal,我也很困惑为什么 removeGlobalOnLayoutListener 可以在不同的观察者身上工作,但现在对于临时浮动观察者来说很清楚了。随着浮动的合并到窗口的观察者,侦听器被移动到另一个观察者,因此稍后调用View.getViewTreeObserver() 将为您提供一个包含您的侦听器的观察者。新的观察者现在负责处理你的监听器。

    但它有时会起作用!还有另一种解决方案

    至于 Zordid 关于为什么在许多情况下可以保留本地(闭包)变量的评论可以用类似的推理来解释:onCreateView 中刚刚膨胀的视图在它返回后还没有附加一点.您看到的大多数可能在生命周期中onCreateView 之后的方法中。如果观察者相关代码在onViewCreated 中,float 的(OP)解决方案就可以正常工作。每个生命周期方法都有自己的职责,所以我建议像这样拆分代码:

    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 中调用,因此在您与观察者/听众一起玩时,层次结构是活跃的。

    【讨论】:

    • 感谢详细分析,但我很遗憾地告诉您,在 onViewCreated 中注册侦听器并使用本地最终变量 final ViewTreeObserver observer 的建议是错误的。我在 Nexus 6 (Android 5.1) 上对其进行了测试,但我经常遇到观察者不活着的异常。它的唯一工作方式是根据@ρяσѕρєя K 解决方案。
    • @CodePond.org 感谢您指出这一点。当我再次回到开发 Android 时,我会研究一下。只是好奇:您使用的是静态片段还是动态片段?不应该有所作为,但谁知道呢……我也在 4.4.2 上进行了所有测试,我们拭目以待。
    • 优秀的答案。我只想指出一个小错误,为什么这对@NimrodDayan 不起作用。 final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver(); 会将observer 分配给浮动的ViewTreeObserver。在调用dispatchAttachedToWindow 之后,这个浮动观察者将被合并到窗口的ViewTreeObserver 并被杀死,这就是为什么observer 不活跃时出现异常的原因。每次调用imageViewPicture.getViewTreeObserver() 应该是解决方案并返回正确的(合并或浮动)ViewTreeObserver
    【解决方案3】:

    最好尝试检查getViewTreeObserver 是否还活着。我认为下面的代码会起作用。基于https://stackoverflow.com/a/15301092/2914140https://stackoverflow.com/a/26193736/2914140 和其他一些。

    ** 更新**

    看完Remove listener from ViewTreeObserverhttps://stackoverflow.com/a/40013262/2914140,我重写了一下。

    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() ... 调用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-09
      • 1970-01-01
      • 2018-05-23
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      相关资源
      最近更新 更多