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 的时候。
分两种情况
-
当 mAttachInfo 不等于空的时候(
也就是 view 已经加载到了 window ,dispatchAttachedToWindow 被调用)直接返回 mAttachInfo 的 mTreeObserver -
当 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