探究View.post获取View宽高问题
来源:互联网 发布:张晓东 大数据 编辑:程序博客网 时间:2024/06/07 16:46
1 提出问题:
执行View.post()的时候,此时View是开始被measure?还是在measure之前执行?如果在measure之前执行,
而post又没有进行延时,那么这个runnable又是如何被放到测量之后进行的呢?
首先跟踪post源码:
/** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> * * @param action The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. * * @see #postDelayed * @see #removeCallbacks */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); ① } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); ② return true; }
分析:
如果mAttachInfo不为Null的时候,会执行1;否则执行2;
所以现在需要知道View中mAttachInfo是否为空,
View中能给mAttachInfo赋值的地方只有一处,在dispatchAttachedToWindow()方法里赋值。
/** * @param info the {@link android.view.View.AttachInfo} to associated with * this view */ void dispatchAttachedToWindow(AttachInfo info, int visibility) { //system.out.println("Attached! " + this); 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; } if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) { mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); ② ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // 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. for (OnAttachStateChangeListener listener : listeners) { listener.onViewAttachedToWindow(this); } } int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(vis); } if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) { // If nobody has evaluated the drawable state yet, then do it now. refreshDrawableState(); } needGlobalAttributesupdate(false); }
可以分析知道onCrate方法先于onAttached执行,而赋值在onAttached之前,那么onCreate的时候mAttachInfo为空。
这个时候会执行ViewRootImpl.getRunQueue.post()这行代码。
所以在onCreate执行view.post的方法时,那些Runnable并没有马上被执行,而是保存到RunQueue中。那么它在什么什么时候执行,
static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; }
下面继续分析:
执行的接口就是:RunQueue.executeActions(),其内部也是调用handler.RunQueue.executeActions()这个接口:
/** * The run queue is used to enqueue pending work from Views when no Handler is * attached. The work is executed during the next call to performTraversals on * the thread. * @hide */ static final class RunQueue { private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); void post(Runnable action) { postDelayed(action, 0); } void postDelayed(Runnable action, long delayMillis) { HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; handlerAction.delay = delayMillis; synchronized (mActions) { mActions.add(handlerAction); } } void removeCallbacks(Runnable action) { final HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; synchronized (mActions) { final ArrayList<HandlerAction> actions = mActions; while (actions.remove(handlerAction)) { // Keep going } } } void executeActions(Handler handler) { synchronized (mActions) { final ArrayList<HandlerAction> actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { final HandlerAction handlerAction = actions.get(i); handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } } private static class HandlerAction { Runnable action; long delay; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HandlerAction that = (HandlerAction) o; return !(action != null ? !action.equals(that.action) : that.action != null); } @Override public int hashCode() { int result = action != null ? action.hashCode() : 0; result = 31 * result + (int) (delay ^ (delay >>> 32)); return result; } } }
在ViewRootImpl里只有一个地方调用;就是performTraversals():
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; // 这里面做了一些初始化的操作,第一次执行和后面执行的操作不一样,这里不关 // 心过多的东西,主要关心attachInfo在此处被初始化完成 // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(attachInfo.mHandler); ... performMeasure(); ... performLayout(); ... performDraw(); }
performTraversals()相信大家都听过,它非常重要,它的作用就是遍历整个View树,并且按照要求进行measure,layout和draw流程,
刚才post的Runnable就是在这里执行了,但是表面上看起来是先执行Runnable,然后执行measure,layout和draw。那么这样它又如何
是怎么获取测量的值呢?
我们仔细分析executeActions()。其实它只是调用handler的post一个Runnable方法,而且这个handler是ViewRootImpl的。所以会
在主线程执行这个Runnable.
这里引入其他知识点,Android的运行其实是消息驱动模式,程序在启动的时候会在ActivityThread的
main方法
public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
进行创建主线程的Handler和Looper,并且最后调用loop()方法进入循环,它一直在等待消息驱动它执行下去。
而像我们post的Runnable就是消息的一种了。
所以此时需要知道主线程执行的任务,然后才会去执行我们post的Runnable.
那么此时正在执行什么任务呢?正如上面所说:Android是消息驱动模式,那么在执行performTraversals()又是什么消息在驱动呢?
而这个消息正是TraversalRunnable,
因此这个时候hanlder正在执行TraversalRunnable,而我们post的Runnable需要TraversalRunnable执行完后执行
而TraversalRunnable这里面会进行measure,layout和draw流程,所以等到执行我们的Runnable。此时View的宽高就可以获取了。
2 这里提出一个疑问?
如果在onCreate里新建一个线程post,那么这个Runnable很可能就没有执行了。
注释只是说任务会在主线程中执行,并没有有什么友好的提示:
关于这个bug:
其实并不是不能在子线程中调用view.post,相反,这个方法的作用就是可以让我们把子线程任务放到主线程执行。
但是有个前提条件:要在View.onAttachToWindow执行,如果在View.post方法执行的话,那么这个Runnable讲不会得到执行。
原因分析:
在View.onAttachToWindow之后调用,那么mAttachInfo就不为空,那么早post方法就会返回attachInfo.mHandler.post(action);
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }
4.4源码有这个方法解释:
This method can be invoked from outside of the UI thread only when this View is attached to a window.
- 探究View.post获取View宽高问题
- 探究为何:在onCreate中通过View.post能获取宽高
- 通过View.post()获取View的宽高引发的两个问题
- 获取view宽高
- 获取view宽高
- 获取View的宽高
- 通过View.post()获取View的宽高引发的两个问题:1post的Runnable何时被执行,2为何View需要layout两次;以及发现Android的一个小bug
- 通过View.post()获取View的宽高引发的两个问题:1post的Runnable何时被执行,2为何View需要layout两次;以及发现Android的一个小bug
- 自定义View 中很关键的问题View获取宽/高是0 的解决办法
- 在onCreate()中获取某个View的宽高(post()方法)
- 在onCreate()中获取某个View的宽高(post()方法)
- android获取view宽高的几种方法,解决获取高宽为0问题
- 获取view的坐标 宽高 以及 view 转drawable
- view截取图片和view获取宽高
- Android获取View的宽高与View.measure详解
- Android获取View的宽高与View.measure详解
- view的宽高获取不到
- 获取view的高和宽
- Servlet 使用字符流读取文件乱码解决方法
- HYSBZ 1036 树的统计Count
- ES6 -- Promise对象
- 抓取今日头条文章
- Mongodb与spring集成 实体映射
- 探究View.post获取View宽高问题
- Qt学习之Qt控件的介绍
- Genymotion运行问题---Unable to connect to your virtual device! Genymotion will now stop........
- virtualbox完美安装Fedora64位系统(配置nfs、tftp、ssh、增强工具)
- 针对maven聚合项目,出现的一些问题
- 2.1.10 —线性表—4Sum
- MapReduce功能实现九---Pv、Uv
- Kotlin入门(10)七十二变的输入参数
- 强大的查找命令find