ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的。
在Android
中,如果在Activity
的onCreate
方法直接获取View
的宽高是获取不到的,但如果是调用 View
的post
方法通过它(post方法)参数Runnable
接口的回调却能够获取到 View
的宽高,View
的 post
方法是马上执行的吗?它的执行时机又是什么时候呢?下面我们举个例子验证一下以下几点:
-
post
方法中的Runnable
接口的回调能否直接获取View
的宽; -
post
方法中Runnable
接口的回调和Activity
的onResume
方法的先后顺序; -
Activity
的onResume
方法能否直接获取View
的宽; -
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.xml
如下所示:
<?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
方法:
@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
的宽。
Activity
的onResume
方法是在Activity
的makeVisible
方法先被调用的,而View
的post
中Runnable
接口是在View
绘制完才会被回调的,所以Activity
的onResume
方法先比 View.post
方法中Runnable
接口被调用。