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? = nulloverride 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如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.xe.postdemo.MyView

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">

</com.xe.postdemo.MyView>

把程序运行一下,日志打印如下所示:

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 办法;

@Overridevoid 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 办法;

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    ......    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    ......}

DecorView 的 onMeasure 办法调用了 父类的 onMeasure 办法,最终的实现是在 FrameLayout 中 measure 办法;

@Overrideprotected 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 的宽。

Activity 的 onResume 办法是在 Activity 的 makeVisible 办法先被调用的,而 View 的 post 中 Runnable 接口是在 View 绘制完才会被回调的,所以Activity 的 onResume 办法先比 View.post 办法中 Runnable 接口被调用。