我们都知道Activity的生命周期流程,我们也知道View绘制的三个方法onMeasure、onLayout、onDraw
。但是你知道在启动一个Activity时,它们是工作在哪个生命周期的吗?这边我开始做一个完整的分析。以下代码都是基于Android 8.0的源码进行分析的,由于本人能力有限,不喜勿喷。
首先用一个时序图来表示这一整个分析流程,performTraversals
是View三大流程onMeasure、onLayout、onDraw
方法执行开始的地方,我们的分析的就是是从ActivityThread
到RootViewImpl
执行performTraversals
的过程,
ActivityThread
这个类是我们平时说的UI线程,但是他不是一个真的线程类。它是一个App程序运行的管理和执行的类,其中Activty的生命周期就是在这里通过Handler
来执行的,我们可以定位到H
这个内部类,它是一个Handler的子类,在handleMessage
中包含着四大组件的生命周期的消息处理。
其中的这段代码就是代表Activity启动的开始,handleLaunchActivity
是其中最重要的方法。
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
定位到handleLaunchActivity
方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
//创建WSM
WindowManagerGlobal.initialize();
//在这里创建了Activity对象,执行onCreate和onStart方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//执行onResume方法,View的工作流程方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
performPauseActivityIfNeeded(r, reason);
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
...
}
我们看看handleResumeActivity
这个方法,这里首先会根据token来取得要处理的Activity,然后performResumeActivity
这个方法才是真正执行onResume
的方法,注意在onResume
执行的时候,View还没绘制完成,这时候是拿不到View的宽高的,更不要说在onCreate
和onStart
了。decor一开始会被设置为不可见,所以在onResume
之前,界面对用户都是不可见的。从wm.addView(decor, l)
这段代码开始才是真正的绘制过程,并且在r.activity.makeVisible();
这里开始才把View设置为可见
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
// 在这个方法里面执行onResume的回调,实际上这时还没开始执行视图的测量,所以解释了为什么没办法在onResume中获取view的宽高
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//这个view是DecorView,在Activity的attach方法中创建PhoneWindow时创建的。
View decor = r.window.getDecorView();
//decor一开始会被设置为不可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//把Decorview添加到WindowManager中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
}
...
if (r.activity.mVisibleFromClient) {
//在这里把DecorView设置为可见,也就是界面可见
r.activity.makeVisible();
}
...
}
上面代码的WindowManager
是Activity在attach()
方法中创建的。ViewManager是一个接口,WindowManager是继承于ViewManager的一个接口,WindowManager的实现类是WindowManagerImpl,所以上图代码中的addView调用的实际是WindowManagerImpl
里面的方法
WindowManagerImpl
在WindowManagerImpl中找到了addView方法的实现,但是这边也很简单,它把addView的操作委托给了WindowManagerGlobal的addView方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这几个类的关系可以用UML图表示为
WindowManagerGlobal
在这个方法中执行添加View的是ViewRootImpl,首先先创建了ViewRootImpl对象,然后把view、创建的ViewRootImpl和布局参数保存在三个数组当中,然后将添加工作托管给了ViewRootImpl
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
//调整布局参数等
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
//同一个view不能在WindowManager中添加两次
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//保存布局参数和view,在更新视图时用到
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//真正完成视图工作流程的方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
上面的流程可以大概的总结成
- 父窗口对子窗口的布局参数进行调整
- 检查View的添加次数,同一个View不能在WindowManager中添加两次
- 创建ViewRootImpl,将View、ViewRootImpl、布局参数保存在三个数组中,以供之后的查询之需
- 调用ViewRootImpl.setView()函数,将控件交给ViewRootImpl进行托管。
ViewRootImpl
ViewRootImpl
是一个非常重要的类,实现了ViewParent接口,作为整个控件树的根部,负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站,我们平时处理的事件分发也是通过这个类的中转处理之后才来到Activity和各层级的View的。ViewRootImpl如此的重要我们可以看看它的构造方法里面做了什么事情
public ViewRootImpl(Context context, Display display) {
mContext = context;
//从WindowManagerGlobal拿到IWindowSession,它是ViewRootImpl和WMS通信的代理
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
//把当前线程保存起来,即UI线程,在绘制时会对发起的thread和mThread进行比较,不一致时会抛出异常
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
//创建AttachInfo对象,里面保存了Handler,window等
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager, mHandler);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
//Choreographer是一个依附于当前线程的信号同步类,用于通过VSYNC特性进行界面绘制和刷新,界面的三大流程就是着他的回调事件里面进行的
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
loadSystemProperties();
}
我们定位到setView
方法,其中刷新的方法是requestLayout
,它是ViewParent
接口的方法,用于刷新整个视图树,当视图有变动时都会通过这个方法来通知跟布局刷新
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
}
继续看requestLayout
方法,这里先进行UI线程检查,然后开始计划视图树的遍历工作
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//ui线程检查,
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
mThread
就是ViewRootImpl
初始化时保存起来的当前线程,它检查的并不是当前线程是否是UI线程,而是当前线程是否是操作线程。这个操作线程就是创建ViewRootImpl
对象的线程,其实这里可以看出来操作不是一定子线程不能操作UI,只要创建和执行在同一个线程就是可以的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
可以试试这段代码在activity执行会不会报错
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
MyDialog dialog = new MyDialog(MainActivity.this);
dialog.show();
Looper.loop();
}
}).start();
然后看scheduleTraversals
方法,它通过Choreographer
的回调来通知执行的时机。为什么要这样做呢?
首先来理解一下,图形界面的绘制,大概是有CPU准备数据,然后通过驱动层把数据交给GPU来进行绘制。图形API不允许CPU和GPU直接通信,所以就有了图形驱动(Graphics Driver)来进行联系。Graphics Driver维护了一个序列(Display List),CPU不断把需要显示的数据放进去,GPU不断取出来进行显示。
其中Choreographer
起调度的作用。统一绘制图像到Vsync
的某个时间点。这个就是VSYNC
(垂直同步)的作用,我们都知道界面刷新速度每秒60帧以上时就不会感受到界面的卡顿,我们就可以理解为当VSYNC信号间隔是16毫秒时,我们就不会觉得卡顿了。
最后执行定位到了performTraversals
方法,这个方法就是View里面onMeasure、onLayout、onDraw
方法的起点。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//Choreographer的回调,里面执行界面的绘制
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
performTraversals方法非常长,这里下面是最简化后的工作流程,按顺序调用了performMeasure、performLayout、performDraw方法。而这三个方法由直接或者间距的调用了onMeasure、onLayout、onDraw方法。
private void performTraversals() {
//定义预测量想要的宽高,对宽高进行赋值,这两个变量将是生成MeasureSpec参数SPEC_SIZE候选
int desiredWindowWidth;
int desiredWindowHeight;
...
//执行RunQueue的任务,平时我们通过`view.post`发送的任务就是在这里被执行的。通过attachInfo里面的handler将Runnable对象发送到主线程执行
getRunQueue().executeActions(attachInfo.mHandler);
...
//1. 执行测量工作
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
dispatchApplyInsets(host);
if (mLayoutRequested) {
//执行performMeasure()对视图树进行测量
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
...
//2. 执行布局
performLayout(lp, mWidth, mHeight);
...
//3. 执行视图绘制
performDraw();
...
}
measureHierarchy的方法执行如下
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
//对于父控件是WRAP_CONTENT时,这里指的是浮动窗口,要进行测量参数的协商
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
//第二次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第三次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//view的测量尺寸和窗口尺寸不一致时告诉外面,窗口尺寸有可能变化
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
//窗口尺寸是否可能需要发生变化
return windowSizeMayChange;
}
上面的代码中首先会判断测量的View的宽度是否为WRAP_CONTENT,这种一般是悬浮窗口,如果是则可能会比普通的窗口多两次performMeasure
,上层的View通过MeasureSpec指导子View的测量,我们平时在onMeasure(int widthMeasureSpec,int heightMeasureSpec)
就是从这边开始往下传递的。它和子控件自身期望的尺寸工具决定了子控件最终的测量结果。我们具体看看getRootMeasureSpec计算结果
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
上面的方法决定了DecorView测量参数,可以得知
- ViewGroup.LayoutParams.MATCH_PARENT,表示测量参数的大小就是窗口尺寸的大小,测量模式为MeasureSpec.EXACTLY。
- ViewGroup.LayoutParams.WRAP_CONTENT,表示子控件可以是它所期望的尺寸,但是不得大于窗口尺寸。
- 默认是固定大小,表示子控件的尺寸就是它设置的尺寸大小
我们再往下分析调用流程
此时测量工作已经来到了View层级了。performMeasure将measureHierarchy给予的widthSpec与heightSpec交给DecorView。而DecorView就是布局的顶级View,它是一个ViewGroup,实现了FrameLayout布局。从这里开始就会遍历视图树中的所有View的onMeasure
方法。至此来到了我们熟悉的onMeasure流程
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
measure方法中没有任何进行测量的代码,只是调用了onMeasure
方法,这里面还做对onMeasur
的正确使用做了检查,当没有setMeasuredDimension
时会抛出异常。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//调用onMeasure
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
//当开发者没有调用setMeasuredDimension时会抛出异常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
...
}
performLayout将会决定所有View四个顶点的位置,host.layout
将会执行DecorView的布局流程,getMeasuredWidth和getMeasuredHeight是上一步measure得到的结果
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
final View host = mView;
...
try {
//decorView的布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
...
}
host.layout
将调用View里面的layout方法
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//保存原始坐标
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//将l, t, r, b设置给mLeft, mTop, mBottom, mRight
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//从DecorView开始调用控件树的onLayout方法
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
//通知监听了布局变化的地方
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
从上面可知layout做了如下三件事
- 通过setFrame给View设置四个角度坐标
- 调用onLayout,使控件树开始执行布局操作
- 通知设置了
View.addOnLayoutChangeListener()
的地方
经过测量和布局之后,每个View已经知道自己都大小和位置了,最后我们来看看最终的绘制方法performDraw。
private void performDraw() {
...
try {
//调用draw方法进行实际的绘制
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
performDraw
方法很简单只是调用draw方法进行实际的绘制,我们继续看看
private void draw(boolean fullRedrawNeeded) {
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//当满足以下条件时将进行硬件绘制
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
//硬件加速绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
...
//软件绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
}
draw方法中会产生软件绘制和硬件加速绘制两个分支,我们看drawSoftware
方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
...
//创建canvas
canvas = mSurface.lockCanvas(dirty);
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
}
...
try {
//进行canvas进行平移
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//调用DecorView的draw方法,对控件树进行遍历绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
...
try {
//最后将绘制的内容显示出来
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
}
drawSoftware方法主要进行了如下四步操作
- 创建canvas
- 进行canvas进行平移
- 调用DecorView的draw方法,对控件树进行遍历绘制
- 将绘制的内容显示出来
到这里View工作流程的onDraw就执行完了,回到handleResumeActivity的代码中,通过调用r.activity.makeVisible();
把Activity的内容显示出来
Activity
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
从上面的调用过程我们把View的绘制和Activity的生命周期联系起来了,对于理解整个系统的运作有更深的理解,知道对于UI线程得知是一个相对的概念,vsync垂直同步的机制等。View的工作流程是一个非常复杂的过程,里面的每一个点都值得深入的分析,这里只是理清了三大流程的调用过程。
- Activit启动时View的绘制过程是从生命周期的onResume开始的
- Activity从onResume开始才是对用户可见的
- 在onResume或者onCreate中没办法直接获得View的宽高,因为这时测量还没完成