咱们常常在view初始化的时候想要获取view的大小,好比在Activity的onCreate方法中想要取得view的大小,有不少小伙伴知道能够在View#getViewTreeObserver#addOnGlobalLayoutListener回调中获得结果,取到结果后,咱们通常都会remove掉这个listener,即调用View#getViewTreeObserver#removeOnGlobalLayoutListener或View#getViewTreeObserver#removeGlobalOnLayoutListener两个方法。java
View#getViewTreeObserver每次获得的ViewTreeObserver可能不是同一个实例android
package com.lkh.viewtreeobservertest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
public class MainActivity extends AppCompatActivity {
private final String TAG = this.getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View text = findViewById(R.id.text);
ViewTreeObserver viewTreeObserver1 = text.getViewTreeObserver();
Log.i(TAG, "viewTreeObserver1="+viewTreeObserver1 + " text h="+text.getHeight());
viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserver viewTreeObserver2 = text.getViewTreeObserver();
// viewTreeObserver.removeOnGlobalLayoutListener(this);
Log.i(TAG, "viewTreeObserver2="+ viewTreeObserver2 + " onGlobalLayout text h="+text.getHeight());
}
});
}
}
复制代码
第一次进入Activity时打印的log:bash
05-13 13:46:46.737 29528-29528/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver1=android.view.ViewTreeObserver@9b3ac41 text h=0
05-13 13:46:46.807 29528-29528/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver2=android.view.ViewTreeObserver@370a279 onGlobalLayout text h=57
05-13 13:46:46.827 29528-29528/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver2=android.view.ViewTreeObserver@370a279 onGlobalLayout text h=57
复制代码
咱们看到两次获取到的ViewTreeObserver不同。第一次拿到的是ViewTreeObserver@9b3ac41,在onGlobalLayout回调方法中拿到的是ViewTreeObserver@370a279app
package com.lkh.viewtreeobservertest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
public class MainActivity extends AppCompatActivity {
private final String TAG = this.getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View text = findViewById(R.id.text);
ViewTreeObserver viewTreeObserver1 = text.getViewTreeObserver();
Log.i(TAG, "viewTreeObserver1="+viewTreeObserver1 + " text h="+text.getHeight());
viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserver viewTreeObserver2 = text.getViewTreeObserver();
viewTreeObserver2.removeOnGlobalLayoutListener(this);
Log.i(TAG, "viewTreeObserver2="+ viewTreeObserver2 + " onGlobalLayout text h="+text.getHeight());
}
});
}
}
复制代码
第一次进入Activity时打印的log:ide
05-13 13:50:01.227 29733-29733/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver1=android.view.ViewTreeObserver@9b3ac41 text h=0
05-13 13:50:01.277 29733-29733/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver2=android.view.ViewTreeObserver@370a279 onGlobalLayout text h=57
复制代码
跟场景一基本同样,只是在onGlobalLayout中添加了viewTreeObserver2.removeOnGlobalLayoutListener(this),能够看到移除成功了,onGlobalLayout只执行了一次。oop
package com.lkh.viewtreeobservertest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
public class MainActivity extends AppCompatActivity {
private final String TAG = this.getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View text = findViewById(R.id.text);
final ViewTreeObserver viewTreeObserver1 = text.getViewTreeObserver();
Log.i(TAG, "viewTreeObserver1="+viewTreeObserver1 + " text h="+text.getHeight());
viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
viewTreeObserver1.removeOnGlobalLayoutListener(this);
ViewTreeObserver viewTreeObserver2 = text.getViewTreeObserver();
Log.i(TAG, "viewTreeObserver2="+ viewTreeObserver2 + " onGlobalLayout text h="+text.getHeight());
}
});
}
}
复制代码
第一次进入Activity时打印的log:布局
05-13 13:54:02.157 30070-30070/com.lkh.viewtreeobservertest I/MainActivity: viewTreeObserver1=android.view.ViewTreeObserver@9b3ac41 text h=0
05-13 13:54:02.217 30070-30070/com.lkh.viewtreeobservertest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.lkh.viewtreeobservertest, PID: 30070
java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:887)
at android.view.ViewTreeObserver.removeOnGlobalLayoutListener(ViewTreeObserver.java:599)
at com.lkh.viewtreeobservertest.MainActivity$1.onGlobalLayout(MainActivity.java:23)
at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:982)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2462)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1489)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7472)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:622)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
复制代码
直接removeOnGlobalLayoutListener的时候抛异常了this
查找缘由最好的办法就是直接去看源代码了,咱们直接先看View#getViewTreeObserver的源码(android源码看的是sdk28的,每一个版本可能有不同)spa
View#getViewTreeObservercode
public ViewTreeObserver getViewTreeObserver() {
if (mAttachInfo != null) {
return mAttachInfo.mTreeObserver;
}
if (mFloatingTreeObserver == null) {
mFloatingTreeObserver = new ViewTreeObserver(mContext);
}
return mFloatingTreeObserver;
}
复制代码
由上面两条咱们知道了为何场景一中两次拿到的ViewTreeObserver不一样的缘由。
下面咱们来看下onGlobalLayout调用的时机。
先在ViewTreeObserver找到调用onGlobalLayout地方,ViewTreeObserver中跟本次分析有关的源码以下
ViewTreeObserver#dispatchOnGlobalLayout
public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener);
}
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");
}
}
public final void dispatchOnGlobalLayout() {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
//调用方法在这里
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
复制代码
咱们看到调用onGlobalLayout方法的是dispatchOnGlobalLayout方法,再继续找到调用dispatchOnGlobalLayout方法的地方,是在ViewRootImpl#performTraversals方法中调用,以下:
android.view.ViewRootImpl#performTraversals
private void performTraversals() {
if (mFirst) {
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
}
...
if (...){
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
...
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
}
复制代码
咱们看到这里调用的是mAttachInfo.mTreeObserver里的dispatchOnGlobalLayout,而咱们在Activity#onCreate方法中第一次getViewTreeObserver拿到的是mFloatingTreeObserver,并非mAttachInfo.mTreeObserver,那咱们往mFloatingTreeObserver中addOnGlobalLayoutListener怎么会执行回调呢,咱们在View源码中查看下使用mFloatingTreeObserver的地方,在View#dispatchAttachedToWindow中找到了它的身影,以下:
View#dispatchAttachedToWindow
/** * @param info the {@link android.view.View.AttachInfo} to associated with * this view */
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
...
}
复制代码
原来是在这里把它合并到AttachInfo的mTreeObserver中了,看下ViewTreeObserver#merge方法,以下:
ViewTreeObserver#merge
void merge(ViewTreeObserver observer) {
...
if (observer.mOnGlobalLayoutListeners != null) {
if (mOnGlobalLayoutListeners != null) {
mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
} else {
mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
}
}
...
observer.kill();
}
复制代码
看到了吧,直接addAll了,所有添加到了AttachInfo的mTreeObserver中。那如今还有个问题,怎么保证合并到AttachInfo的mTreeObserver中的执行时机在mAttachInfo.mTreeObserver.dispatchOnGlobalLayout()执行前,其它咱们在上面的源码中已经看到了它们的执行顺序,这里咱们再看一下,以下:
android.view.ViewRootImpl#performTraversals
private void performTraversals() {
if (mFirst) {
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
}
...
if (...){
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
...
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
}
复制代码
咱们看到,顺序依次是dispatchAttachedToWindow、performMeasure、performLayout、dispatchOnGlobalLayout。发现dispatchOnGlobalLayout是在这几个方法的最后面执行,由此能够知道:
先看下log
05-13 13:54:02.217 30070-30070/com.lkh.viewtreeobservertest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.lkh.viewtreeobservertest, PID: 30070
java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:887)
at android.view.ViewTreeObserver.removeOnGlobalLayoutListener(ViewTreeObserver.java:599)
at com.lkh.viewtreeobservertest.MainActivity$1.onGlobalLayout(MainActivity.java:23)
at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:982)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2462)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1489)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7472)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:622)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
复制代码
回顾代码以下:
final ViewTreeObserver viewTreeObserver1 = text.getViewTreeObserver();
viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
viewTreeObserver1.removeOnGlobalLayoutListener(this);
ViewTreeObserver viewTreeObserver2 = text.getViewTreeObserver();
Log.i(TAG, "viewTreeObserver2="+ viewTreeObserver2 + " onGlobalLayout text h="+text.getHeight());
}
});
复制代码
咱们在onGlobalLayout中使用的ViewTreeObserver为第一次获取到的mFloatingTreeObserver,调用removeOnGlobalLayoutListener方法时,会先执行checkIsAlive方法,这里抛异常,mAlive确定为false。
ViewTreeObserver#removeOnGlobalLayoutListener
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");
}
}
复制代码
哪里会把mAlive置为false呢,找到以下代码:
ViewTreeObserver#kill
/** * 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;
}
复制代码
而执行kill方法的地方在哪呢,能够看到前面执行merge方法时,就执行了kill方法,再回顾下:
ViewTreeObserver#merge
void merge(ViewTreeObserver observer) {
...
if (observer.mOnGlobalLayoutListeners != null) {
if (mOnGlobalLayoutListeners != null) {
mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
} else {
mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
}
}
...
observer.kill();
}
复制代码
看到没,最后执行了kill方法,因此场景三会直接抛异常。 场景二中能remove成功的缘由经过上面的分析应该已经很清楚了,就很少说了。
经过上面的代码分析,咱们知道:
综上,使用View#getViewTreeObserver的回调方法必定要当心,颇有可能出现意想不到的现象,慎用。