对Android中View的post方法进行探索

ps:本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的。
在 Android 中,如果在 Activity 的 onCreate 方法直接获取 View 的宽高是获取不到的,但如果是调用 View 的 post 方法通过它(post方法)参数 Runnable 接口的回调却能够获取到 View 的宽高,View 的 post 方法是马上执行的吗?它的执行时机又是什么时候呢?下面我们举个例子验证一下以下几点:
1、post 方法中的 Runnable 接口的回调能否直接获取 View 的宽。
2、 post 方法中 Runnable 接口的回调和 Activity 的 onResume 方法的先后顺序。
3、Activity 的 onResume 方法能否直接获取 View 的宽。
4、View 没有被添加到 Window 里的时候,执行 post 方法,Runnable 接口能否被回调。
PostDemo
(1)新建一个 kotlin 类型的类 MyView 并继承于 View:
class MyView: View {

constructor(context: Context): super(context) {}constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {} constructor(context: Context,@Nullable attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) Log.d(MainActivity.TAG,"------onMeasure--") }override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) Log.d(MainActivity.TAG,"------onLayout--") }override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) Log.d(MainActivity.TAG,"------onDraw--") }

}
(2)新建一个 kotlin 语言类型的 Activity,名叫 MainActivity:
class MainActivity: AppCompatActivity() {
companion object { var TAG: String = "MainActivity" } var mView: View? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mView = findViewById(R.id.my_view); mView?.post(Runnable { Log.d(TAG,"在 post 方法中获取 View 的宽--" + mView?.getWidth()) }) }override fun onResume() { super.onResume() Log.d(TAG,"----onResume---") Log.d(TAG,"在 onResume 方法中获取 View 的宽--" + mView?.getWidth()) }

}
(3)MainActivity 的布局界面 activity_main如下所示:

xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/my_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FF0000" tools:context="com.xe.postdemo.MainActivity">


把程序运行一下,日志打印如下所示:
08-28 08:54:24.364 10133-10133/com.xe.postdemo D/MainActivity: ----onResume---
08-28 08:54:24.364 10133-10133/com.xe.postdemo D/MainActivity: 在 onResume 方法中获取 View 的宽--0
08-28 08:54:24.435 10133-10133/com.xe.postdemo D/MainActivity: ------onMeasure--
08-28 08:54:24.487 10133-10133/com.xe.postdemo D/MainActivity: ------onMeasure--
08-28 08:54:24.490 10133-10133/com.xe.postdemo D/MainActivity: ------onLayout--
08-28 08:54:24.501 2285-2738/? D/Launcher.AllAppsList: updatePackage, find appInfo=AppInfo, id=-1, itemType=0, user=UserHandle{0}, mIconType=0, pkgName=com.xe.postdemo, className=com.xe.postdemo.MainActivity, screenId=-1, container=-1, cellX=-1, cellY=-1, spanX=1, spanY=1, isLandscapePos=false from ComponentInfo{com.xe.postdemo/com.xe.postdemo.MainActivity}
08-28 08:54:24.505 2285-2738/? D/Launcher.Model: onReceiveBackground, mAllAppsList=add=[], remove=[], modified=[(0, AppInfo, id=-1, itemType=0, user=UserHandle{0}, mIconType=0, pkgName=com.xe.postdemo, className=com.xe.postdemo.MainActivity, screenId=-1, container=-1, cellX=-1, cellY=-1, spanX=1, spanY=1, isLandscapePos=false)]
08-28 08:54:24.542 10133-10133/com.xe.postdemo D/MainActivity: ------onDraw--
08-28 08:54:24.714 1472-1555/? I/Timeline: Timeline: Activity_windows_visible id: ActivityRecord{afbc1c9 u0 com.xe.postdemo/.MainActivity t7087} time:4018603
08-28 08:54:24.715 1472-1530/? I/ActivityManager: Displayed com.xe.postdemo/.MainActivity: +4s904ms
08-28 08:54:24.762 10133-10133/com.xe.postdemo D/MainActivity: 在 post 方法中获取 View 的宽--720
从日志可以看出,post 方法中 Runnable 接口的回调可以直接获取到 View 的,Activity 的 onResume 方法比 post 方法中 Runnable 接口的回调先执行,post 方法中 Runnable 接口是在 View 的绘制(主要是 View 的 onMeasure、onLayout 和 onDraw 方法)之后才会被回调,在Activity 的 onResume 方法不能直接获取 View 的宽,因为 Activity 的 onResume 方法比 View 的 onMeasure 和 onLayout 方法先执行。
好,我们把 MainActivity 的 onCreate 方法做一下修改,其他不变:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// setContentView(R.layout.activity_main)
// mView = findViewById(R.id.my_view);
mView = MyView(this) mView?.post(Runnable { Log.d(TAG,"在 post 方法中获取 View 的宽--" + mView?.getWidth()) }) }

运行程序,日志打印如下所示:
08-31 13:24:02.481 10794-10794/com.xe.postdemo D/MainActivity: ----onResume---
08-31 13:24:02.481 10794-10794/com.xe.postdemo D/MainActivity: 在 onResume 方法中获取 View 的宽--0
从日志可以看出,View 没有被添加到 Window 里的时候,执行 post 方法,Runnable 接口不会被回调。
好了,以上验证的4点以及 View 的 post 方法执行时机可以从源码中找到原因,我们先从 View 的 post 方法看起;
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;

}
这里 attachInfo 是为空的,所以不会执行 attachInfo.mHandler.post(action) 这行代码,我们看 getRunQueue().post(action) 这行代码,getRunQueue() 方法得到的是一个 HandlerActionQueue 类型的对象,我们点击 HandlerActionQueue 的 post 方法查看;
public void post(Runnable action) {
postDelayed(action, 0);

}
HandlerActionQueue 的 post 方法又调用了自己的 postDelayed 方法,这里的参数0表示延时所有的时间,我们往下看 HandlerActionQueue 的 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++; }

}
从这个方法看出,HandlerAction 表示要执行的任务,要执行的任务 HandlerAction 保存在数组长度为4的 mActions 数组中,mCount 表示数组 mActions 的下标,每次都加1;这个 postDelayd 方法并没有马上执行任务,而是保存了任务,那么执行任务的语句在哪里呢?
有时候我们会说看到 Activity 的界面后 onResume 方法就会被回调过,所以我们从调用 Activity 的 onResume 方法的 AcitivityThread.handleResumeActivity 方法说起;
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ...... //1、 // TODO Push resumeArgs into the activity for consideration r = performResumeActivity(token, clearHide, reason); if (r != null) { ...... if (!r.activity.mFinished && willBeVisible ...... if (r.activity.mVisibleFromClient) {//2、 r.activity.makeVisible(); } }...... } else { ...... }

}
这里的注释1 最终会调用 Activity 的 onResume方法,我们往下看注释2 的代码,它是 Activity 的 makeVisible方法;
void makeVisible() {
if (!mWindowAdded) { ViewManager wm = getWindowManager(); //3、 wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE);

}
注释3 表示将 View 视图添加到 ViewManager 中,我们点击注释3 的代码进去看看,ViewManager 的实现类是 WindowManagerImpl,所以我们看的是 WindowManagerImpl的 addView 方法;
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params); //4、 mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

}
注释4 处调用了 WindowManagerGlobal的 addView 方法,我们往下看;
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) { ...... synchronized (mLock) { ...... //5、 root = new ViewRootImpl(view.getContext(), display); ...... try { //6、 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ...... } }

}
我们点击注释5 的代码,看看 ViewRootImpl 的构造方法;
public ViewRootImpl(Context context, Display display) {
...... //7、 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); ......

}
一刚开始的时候,我们在Activity 的 onCreate 方法中先执行 View.post 方法,但是 ViewRootImpl 在 Activity 的 onResume 方法之后才会被初始化,还顺便在 ViewRootImpl 的构造方法中初始化 AttachInfo,所以说一开始 View.post 方法中的 attachInfo 就为 null,从而执行 getRunQueue().post(action) 语句;我们往下看注释6 的代码,也就是 ViewRootImpl 的 setView 方法;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) { if (mView == null) { //8、 mView = view; ...... // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. //9、 requestLayout(); ...... } }

}
这里注释8 的 View 是底部容器 DecorView,我们继续往下看注释9 的方法,也就是 ViewRootImpl 的 requestLayout 方法;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }

}
requestLayout 方法调用了 ViewRootImpl 的 scheduleTraversals 方法,我们且看 scheduleTraversals 方法;
void scheduleTraversals() {
if (!mTraversalScheduled) { ...... //10、 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //11、 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...... }

}
注释10 这里先不说,后面再说,注释11 的代码会回调 mTraversalRunnable 对象,而 mTraversalRunnable 对象是一个 Runnable 接口的实现类 TraversalRunnable 具体的对象,mTraversalRunnable 对象又调用了 ViewRootImpl 的 doTraversal 方法,doTraversal 方法又调用了 ViewRootImpl 的 performTraversals 方法,我们来看看 performTraversals 方法;
private void performTraversals() {
//12 // cache mView since it is used so much below... final View host = mView; ...... if (mFirst) { ...... //13、 host.dispatchAttachedToWindow(mAttachInfo, 0); ...... } else { ...... } ...... if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) { mForceNextWindowRelayout = false; ...... if (!mStopped || mReportNextDraw) { ...... //14、 // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ...... } } else { ...... } ...... if (didLayout) { //15、 performLayout(lp, mWidth, mHeight); ...... } ...... if (!cancelDraw && !newSurface) { ...... //16、 performDraw(); } else { ...... } ......

}
注释12 就是上面注释8 中说的底部容器 DecorView,注释13 表示 DecorView 关联 AttachInfo,dispatchAttachedToWindow 方法是在 ViewGroup 里实现,该方法会遍历 DecorView 的子元素进行 关联 AttachInfo,我们看一下 ViewGroup 的 dispatchAttachedToWindow 方法;
@Override void dispatchAttachedToWindow(AttachInfo info, int visibility) { ...... for (int i = 0; i < count; i++) { final View child = children[i]; //17、 child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } ...... }

这个方法是很重要的,子元素关联了 AttachInfo,然后将之前 View.post 保存的任务添加到 AttachInfo 内部的 Handler,所以 View 没有被添加到 Window 里的时候,执行 post 方法,Runnable 接口没有被回调;我们看注释17 的代码,它是 View 的 dispatchAttachedToWindow 方法;
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
...... // Transfer all pending runnables. if (mRunQueue != null) { //18、 mRunQueue.executeActions(info.mHandler); mRunQueue = null; } ......

}
注释18 的代码表示调用了 HandlerActionQueue 的 executeActions 方法,我们来看看 executeActions 方法;
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]; //19、 handler.postDelayed(handlerAction.action, handlerAction.delay); }//20、 mActions = null; mCount = 0; }

}
注释19 行的代码我们先留下悬念;注释20 是将 mActions 置空,从第二次调用 View.post 开始,Runnable 会被添加到 AttachInfo 内部的 Handler,而不是 HandlerAction,View 的 onMeasure、onLayout 和 onDraw 方法也不会被调用;我们回过头来看注释14 的代码,也就是 ViewRootImpl 的 performMeasure 方法;
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...... try {//21、 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }

}
注释21 的 mView 表示 DecorView,它的 measure 方法是在 View 里,我们来看一下 View 的 measure 方法;
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...... if (forceLayout || needsLayout) { ...... if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back //22、 onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { ...... } ...... } ......

}
注释22 的代码是调用的是 DecorView 的 onMeasure 方法,而不是 View 的 onMeasure 方法,我们往下看 DecorView 的 onMeasure 方法;
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ...... super.onMeasure(widthMeasureSpec, heightMeasureSpec); ...... }

DecorView 的 onMeasure 方法调用了 父类的 onMeasure 方法,最终的实现是在 FrameLayout 中 measure 方法;
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ...... for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) {//23、 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); ...... } } ...... }

注释23 的代码会对所有子 View 进行了一遍测量,并计算出所有子 View 的最大宽度和最大高度,我们往下看 FrameLayout 的 measureChildWithMargins 方法;
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { ...... child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

我们假设 child 是具体的 View,而不是 ViewGroup,View 的 measure 方法最终调用了 View 的 onMeasure 方法,onMeasure 方法最终是测量 View 的宽高;我们回过头来看,注释15 的代码最终会调用 View.onLayout 方法,它的调用过程是 ViewRootImpl.performLayout-> host.layout(ViewGroup.layout)->super.layout(View.layout)-> View.onLayout,最终完成 View 位置的确定;注释16 的代码最终会调用 View.onDraw 方法,它的调用过程是 ViewRootImpl.performDraw -> ViewRootImpl.draw->ViewRootImpl.drawSoftware-> mView.draw(DecorView.draw)->super.draw(View.draw)-> View.onDraw,最终完成 View 的绘画出来;
好了,现在我们可以解答上面剩下的疑问了,AcitivityThread.handleResumeActivity 方法 先调用自己的 performResumeActivity 方法,而该方法最终调用 Activity 的 onResume 方法,而后再调用 Activity 的 makeVisible 方法,Activity 的 makeVisible 方法最终完成 View 的测量宽高、位置确定和绘画,所以 Activity 的 onResume 方法不能直接获取 View 的宽。
我们在回过头来看,注释19 的代码,它的延时时间为0啊,而且比 View 的 onMeasure、onLayout 和 onDraw 方法先被调用啊,为什么从上面的 demo 日志看出最终 Runnable 接口等 View 的 onMeasure、onLayout 和 onDraw 方法调用完之后再调用,是因为注释10 的代码先比注释19 的代码先被调用,注释10 表示开启了同步消息屏障,Android 中它有一个异步消息优先级比较高的权利,保障 View 绘制完后再给其他消息执行,所以在 View.post 方法中的 Runnable 接口的回调能直接获取 View 的宽。
【对Android中View的post方法进行探索】Activity 的 onResume 方法是在 Activity 的 makeVisible 方法先被调用的,而 View 的 post 中 Runnable 接口是在 View 绘制完才会被回调的,所以Activity 的 onResume 方法先比 View.post 方法中 Runnable 接口被调用。

    推荐阅读