提出变量引发的惨案(ViewTreeObserver 的正确写法)

4,512 阅读2分钟

ViewTreeObserver 正确使用方法

背景

我们知道,在 OnCreate 中获取View宽高的方法之一 ,包括 ViewTreeObserver.addOnGlobalLayoutListener,但是实际的开发中,写法一 出现了Crash

java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again

写法一:
final ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        viewTreeObserver.removeOnGlobalLayoutListener(this);
    }
});

写法二:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
});

为什么这两种写法仅仅是提出了一个变量,就会 Crash呢? 我们接下来进入源码分析

一、Crash 具体在哪里暴露的?

ViewTreeObserver.java

public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
        checkIsAlive();
        if (mOnGlobalLayoutListeners == null) {
            return;
        }
        mOnGlobalLayoutListeners.remove(victim);
    }
 
    private void checkIsAlive() {
        if (!mAlive) {
            throw new IllegalStateException("This ViewTreeObserver is not alive, call "
                    + "getViewTreeObserver() again");
        }
    }

可以看到在调用 removeOnGlobalLayoutListener 中,会去 checkIsAlive ,而当 mAlive 为 flase 则会抛出异常

二、mAlive 是什么?

ViewTreeObserver.java

private boolean mAlive = true;

   
   /**
     * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
     * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
     *
     * @hide
     */
    private void kill() {
        mAlive = false;
    }

mAlive 初始化为true,而当 ViewTreeObserver 调用了 kill() 的时候 , 再调用 ViewTreeObserver 里的其他方法(除了 isAlive() 和 kill())都会抛出异常

三、kill() 被调用的时机?

ViewTreeObserver.java

/**
     * Merges all the listeners registered on the specified observer with the listeners
     * registered on this object. After this method is invoked, the specified observer
     * will return false in {@link #isAlive()} and should not be used anymore.
     *
     * @param observer The ViewTreeObserver whose listeners must be added to this observer
     */
    void merge(ViewTreeObserver observer) {
        if (observer.mOnWindowAttachListeners != null) {
            if (mOnWindowAttachListeners != null) {
                mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
            } else {
                mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
            }
        }

        ...

        observer.kill();
    }

View.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
       ...
    }

可以看到当 View 的 dispatchAttachedToWindow 被调用的时候,也就是 View 被加入到 Window 时,会将 mFloatingTreeObserver merge 到 View 的 mTreeObserver中,然后将 mFloatingTreeObserver 标记为失效

五、mFloatingTreeObserver 是什么?

View.java

public ViewTreeObserver getViewTreeObserver() {
        if (mAttachInfo != null) {
            return mAttachInfo.mTreeObserver;
        }
        if (mFloatingTreeObserver == null) {
            mFloatingTreeObserver = new ViewTreeObserver(mContext);
        }
        return mFloatingTreeObserver;
    }

这里可以看到我们在调用 getViewTreeObserver 的时候。

分两种情况

  1. 当 mAttachInfo 不等于空的时候(也就是 view 已经加载到了 window ,dispatchAttachedToWindow 被调用)直接返回 mAttachInfo 的 mTreeObserver

  2. 当 mAttachInfo 等于空的时候(也就是 view 还没加载到 window中,dispatchAttachedToWindow 没有被调用)会创建一个 mFloatingTreeObserver 作为临时储存Observer信息参数

也就是说,mFloatingTreeObserver 仅仅是一个临时储存参数的变量,当dispatchAttachedToWindow 被调用后,就合并到 mAttachInfo.mTreeObserver 中,然后 mFloatingTreeObserver 就被 kill()

总结

当调用 getViewTreeObserver 的时候,View 还未被加载到 Window 当中(dispatchAttachedToWindow 未被调用),此时提出变量,变量中存储的其实是用作临时存放 Observer 信息的 mFloatingTreeObserver,等到 View 加载到 Window 之后,mFloatingTreeObserver 被 kill(),再使用提出的变量的方法就会Crash

F&Q

F: 为什么 getViewTreeObserver 提成变量就出现了Crash?

见总结

F:是否 getViewTreeObserver 必须不能提成变量?

A: 如果能确认 getViewTreeObserver 的调用时机一定在 dispatchAttachedToWindow 之后,上面两种写法都不会Crash