- 欢迎关注我的公众号
- 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
源码基于安卓8.0分析结果
-
View
是何时开始绘制的?Activity
走了onCreate
方法吗?这篇文章就是从程序的入口ActivityThread
入口程序,去解释View中的measure()方法
、View中的layout
、View中的draw
怎么开始调用的,非常有意思!虽然好多的技术文档,在半个月前已经做好了,这篇文章,对我自己来讲的话,是个很好的复习~~ - 为了更好地阐述着这篇文章,我这里就直接抛出结论了,为啥会这样的,在下篇文章会讲到,这里就记住一点,在
Activity onResume
后,调用了View onAttachedToWindow
才会开始View measure
- 为什么会这样子?先看
ActivityThread
类里面有个内部private class H extends Handler
这就是系统的Handler
,具体分析请看Android源码分析(Handler机制),里面有个case RESUME_ACTIVITY
,获取焦点
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- handleResumeActivity()方法,这里只截取了关键的代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
mSomeActivitiesChanged = true;
//在这里执行performResumeActivity的方法中会执行Activity的onResume()方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//PhoneWindow在这里获取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在这里获取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
// TODO: 2018/5/24 WindowManager
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
//获取ViewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
}
}
- 第一点分析得出
performResumeActivity()
肯定先于wm.addView(decor, l);
执行的~这也是为啥我们Activity
先获取焦点了,才去绘制View
- performResumeActivity(),可以得出调用的是
r.activity.performResume();
- 关于
r.activity.performResume();
;这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识。注意这个方法mInstrumentation.callActivityOnResume(this);
;然后才会执行onPostResume
;这也就是为什么,Activity先获取焦点,后执行onPostResume()
;
final void performResume() {
performRestart();
mInstrumentation.callActivityOnResume(this);
mCalled = false;
//这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
}
-
关注这个方法
mInstrumentation.callActivityOnResume(this)
;果然不出所料,这里执行了activity.onResume();
;
-
既然在上面知道了,activity 获取焦点,会在上面执行,那么View的绘制就会在下面的函数中进行。
- 1、获取PhoneWindow;
activity.getWindow()
,Window
类的唯一子类 - 2、获取
window.getDecorView()
;DecorView,PhoneWindow的内部类,private final class DecorView extends FrameLayout
,安卓的事件分发和它密切相关Android源码分析(事件传递),也就是从Activity
传递到ViewGroup
的过程~~ - 3、获取
ViewManager wm = a.getWindowManager();
,其实也就是activity.getWindowManager()
,也就是获取的是ViewManager
的子类对象WindowManager
,这里的知道WindowManager
其实也是一个接口.
- 4、
wm.addView(decor, l);
,也就是到这里来了,WindowManager.addView(decor,l)
.
- 1、获取PhoneWindow;
//PhoneWindow在这里获取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在这里获取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
// TODO: 2018/5/24 WindowManager
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
//获取ViewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//在这里WindowManager将DecorView添加到PhoneWindow中
wm.addView(decor, l);
}
}
- 分析到这里来了,会通过
WindowManager.addView(decor,l)
.我们需要去找WindowManager
的实现。WindowManagerImpl
;
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
- 去寻找
WindowManagerGlobal
的addView()
方法。这里有个单利模式,在源码好多地方使用的单利模式都是这样,并没有进行双重判断,在老牌的图片加载框架ImageLoader
也是这样获取单利对象,如果想了解更多设计模式的姿势,可以看这片文章二十三种设计模式.
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
- 在这里!就是
WindowManagerGlobal.addView()
的关键的方法,我做了两个注释,一个是view.setLayoutParams(wparams);
,这个方法非常有意思,最近在研究ViewGroup的源码,发现不论什么情况下,View或者是ViewGroup都会有两次测量,这里是根本的原因,我先给结论。
api26:执行2次onMeasure、1次onLayout、1次onDraw。
api25-24:执行2次onMeasure、2次onLayout、1次onDraw,
api23-21:执行3次onMeasure、2次onLayout、1次onDraw,
api19-16:执行2次onMeasure、2次onLayout、1次onDraw,
API等级24:Android 7.0 Nougat
API等级25:Android 7.1 Nougat
API等级26:Android 8.0 Oreo
API等级27:Android 8.1 Oreo
后续我会做一篇文章详细解释下,为什么会这样,这里不过多的解释了,自提一句,非常有意思的代码!以前还会有两次的layout,说明谷歌也在优化安卓framework
。todo
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
//view setLLayoutParams()在这里
view.setLayoutParams(wparams);
try {
// TODO: 2018/6/4 这里呢?就是ViewRootImpl 调用的setView的方法,就在这里
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
- ok,现在继续的关注这个方法
ViewRootImpl.setView(view, wparams, panelParentView)
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) { try {
// TODO: 2018/6/4 这里传入的attrs 决定了View 或者是ViewGroup是否会onMeasure 两次
mWindowAttributes.copyFrom(attrs);
} catch (RemoteException e) {
// TODO: 2018/5/24 就会调动这里的来
unscheduleTraversals();
} finally {
if (restore) {
attrs.restore();
} if (res < WindowManagerGlobal.ADD_OKAY) {
// TODO: 2018/5/24 就会调动这里的来
unscheduleTraversals();}
}
- unscheduleTraversals(),没有Activity获取焦点的时候,这个方法肯定会执行
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
- 关注mTraversalRunnable对象
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
- doTraversal()方法,Traversal翻译过来就是遍历的意思~~
// TODO: 2018/5/24 到这里来了 ----> Traversal 遍历
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals
这里就是整个View绘制的开始,所有的绘制,都会从这里开始,虽然这个方法代码有点多,但是关键的地方我都做了注释,下面一步一步的分析
/**
* 最终会调动到这里
* ViewRootImpl的performTraversals()方法完成具体的视图绘制流程
*/
private void performTraversals() {
// cache mView since it is used so much below...
//mView 就是DecorView根布局
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
//如果host=null 或者是mAdded=false 直接就return了
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上需要绘制View
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
/**
* 这个WindowMananger 这里标记了 是否是需要 onMeasure 两次 哈哈
*/
// TODO: 2018/6/4
WindowManager.LayoutParams lp = mWindowAttributes;
/*
顶层视图DecorView所需要的窗口的宽度和高度
*/
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = false;
} else {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = true;
}
}
mWindowAttributesChangesFlag = 0;
Rect frame = mWinFrame;
//在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
// TODO: 2018/5/25 注意这个方法内部做了什么
/*
return lp.type == TYPE_STATUS_BAR_PANEL
|| lp.type == TYPE_INPUT_METHOD
|| lp.type == TYPE_VOLUME_OVERLAY;
*/
// 如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要的窗口的宽度和高度
//就是除了状态栏
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//否者顶层视图DecorView所需要的窗口的宽度和高度就是整个屏幕的宽度
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mHasWindowFocus = false;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
// If we are in auto resize mode, then we need to determine
// what mode to use now.
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i
5、第一次绘制的时候,这个标记一定是didLayout
一定是true,一定会走到这个方法里面去performLayout(lp, mWidth, mHeight);
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// TODO: 2018/5/25 执行布局操作
performLayout(lp, mWidth, mHeight);
}
}
- 关于
performLayout
这个方法,直接会调用host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
,也就是View的layout的方法。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//测试层级
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
6、这两个标记也是!cancelDraw && !newSurface
为true
,那么就会走到performDraw();
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// TODO: 2018/5/25 执行绘制的操作
performDraw();
}
- 关于
performDraw();
方法,直接调用的是draw(fullRedrawNeeded);
private void performDraw() {
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
- 关于
draw(fullRedrawNeeded);
,会调用到这里来drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
- 关于这个方法
drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
。官方的解释为如果绘图成功,如果发生错误,则为false。返回false的,程序就发生了异常,也就是程序GG掉了,绘制失败,这里仅仅贴出关键的代码~~~,这样字,就调用到了mView.draw(canvas);
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
// TODO: 2018/5/25 调用了View里面的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;
}
}
}
-
最后做了两张图
- 说明几点
- 如果感兴趣的,一定要去打个断点看一下这个流程
- 限于作者水平有限,一定会存在有些错误,还望指出,谢谢