简述
在android项目开发的时候,我们经常遇到在代码中获取view的宽高,如果在onCreate中直接获取,返回的都是0.然而通过view.post(),就能获取到view的宽高。这是为什么呢?View的绘制要经历onMeasure、onLayout和onDraw三个过程,view的宽高是在onLayout里面确定的,而在onCreate中获取的时候,view还没执行onLayout,所以获取到的宽高都是0.而view.post(),是在view attach到界面后执行的,所以能获取到宽高。
View.post原理
handler.post
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
这是在View.java中的post方法,它的作用是将Runnable加到message queue中,然后在UI线程执行。
该方法中有 if (attachInfo != null)的判断,如果attachInfo不为空,则说明该view已经attach到window,那么它直接调用attachInfo.mHandler.post(action),这个其实调用的是UI线程的handler,跟正常的Handler.post没有区别,都是把message发送到message queue中,然后通过Looper去处理。
那么attachInfo是什么时候初始化的呢?
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
}
正如我们想象的一样,它就是在view attach到window的时候进行初始化赋值的。
attachInfo为空的时候说明它还没有attach到window中,那么post是通过getRunQueue().post(action)来执行的。
getRunQueue().post(action)
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
getRunQueue()方法返回一个HandlerActionQueue对象,它相当于view还没有attach时的messageQueue.下面我们看下HandlerActionQueue。
/**
* Class used to enqueue pending work from Views when no Handler is attached.
*
* @hide Exposed for test framework only.
*/
public class HandlerActionQueue {
// 存放所有runnable,HandlerAction是对runnable的封装对象
private HandlerAction[] mActions;
private int mCount;
// view没有attach到window的时候,View#post调用方法
public void post(Runnable action) {
postDelayed(action, 0);
}
// view没有attach到window的时候,View#postDelayed调用方法
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
// 移除一个runnable任务
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0;
final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) {
if (actions[i].matches(action)) {
// Remove this action by overwriting it within
// this loop or nulling it out later.
continue;
}
if (j != i) {
// At least one previous entry was removed, so
// this one needs to move to the "new" list.
actions[j] = actions[i];
}
j++;
}
// The "new" list only has j entries.
mCount = j;
// Null out any remaining entries.
for (; j < count; j++) {
actions[j] = null;
}
}
}
// 执行所有runnable,接着清空HandlerAction集合
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
public int size() {
return mCount;
}
public Runnable getRunnable(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].action;
}
public long getDelay(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].delay;
}
// 对runnable的封装类
private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}
通过对HandlerActionQueue类的分析,我们知道它的executeActions方法会执行所有的任务。那么是哪里调用它的呢?
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
...代码省略
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...代码省略
}
同样是在dispatchAttachedToWindow方法中,说明它是在view attach到Window后去执行之前添加的任务。
那么问题又来啦,dispatchAttachedToWindow是什么时候执行的呢?虽然我们通过名字就能猜到它的调用时机,但是我们还是要亲眼看到调用的代码。
ViewRootImpl.performTraversals:
private void performTraversals() {
...代码省略
host.dispatchAttachedToWindow(mAttachInfo, 0);
...代码省略
}
总结
当View没有被attach到window的时候,最后runnable的处理不是通过MessageQueue,而是在ViewRootImpl在performTraversals中执行。
实例验证
在Activity的onCreate中验证,代码如下:
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
// 初始化一个Button,之后将attach到window中
final Button button = AppCompatButton(getApplicationContext()){
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.v("test","onLayout");
}
};
// 将button添加到根布局中
ViewGroup rootView = (ViewGroup) getWindow().getDecorView();
rootView.addView(button);
// 使用handler.post获取宽高
new Handler().post(new Runnable() {
@Override
public void run() {
Log.v("test","Handler post,width:"+button.getWidth()+",height:"+button.getHeight());
}
});
// 使用view.post获取宽高
button.post(new Runnable() {
@Override
public void run() {
Log.v("test","View post,width:"+button.getWidth()+",height:"+button.getHeight());
}
});
}
log:
08-09 00:50:36.672 16825-16825/com.example.android.persistence V/test:
Handler post,width:0,height:0 08-09 00:50:36.683
16825-16825/com.example.android.persistence V/test: onLayout 08-09
00:50:36.687 16825-16825/com.example.android.persistence V/test: View
post,width:1080,height:1920
日志说明:
- 使用handler.post的runnable最先执行,此时View还未执行onLayout,无法获取view的宽高。
- 接着view的onLayout方法执行,表示view完成了位置的布置,此时可以获取宽高。
- view.post的runnable最后执行,也就是说view已经layout完成才执行,此时能够获取View的宽高。
参考
1、http://blog.csdn.net/a740169405/article/details/69668957
2、http://www.jianshu.com/p/b1d5e31e2011