View与Activity是如何关联起来的?
Android的UI层级绘制体系
- PhoneWindow:每个Activity都会创建一个Window用来承载View的显示。Window是一个抽象类,它的唯一实现就是PhoneWindow,该类中包含一个DecorView。
- DecorView:DecorView是最顶层的View,继承于FrameLayout,内部包含两个部分->ActionBar and ContentView。
- ContentView:我们在Ac中调用setContentView()传入布局(xml/view)就在该View中加载显示。
- ViewRootImpl: 视图层次结构的顶部。一个Window对应一个ViewRootImpl和一个DecorView。通过该实例对DecorView进行控制。最终通过执行ViewRootImpl的performTraversals()开启对整个View树的绘制。
View的加载流程
- 从Activity的setContentView(xx)来看,最终调用了PhoneWindow的setContentView(xx)方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- PhoneWindow最终生成一个DecorView对象,加载系统基础布局到mDecor中,并根据
R.id.content生成一个mContentParent,
- 最终将
Activity#setContentView()传入的xml/view加载到mContentParent
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
........
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....
}
}
protected DecorView generateDecor(int featureId) {
......
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
....
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}
.....
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
.....
mDecor.finishChanging();
return contentParent;
}
View的视图绘制流程剖析
- DecorView被加载到Window中。在ActivityThread的handleResumeActivity方法中通过WindowManager将DecorView加载到Window中。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
.......
// 此处执行Activity的onResume()方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason)
if (r == null) {
return
}
if (mActivitiesToBeDestroyed.containsKey(token)) {
return
}
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 = ActivityTaskManager.getService().willActivityBeVisible(
a.getActivityToken())
} catch (RemoteException e) {
throw e.rethrowFromSystemServer()
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取window对象
r.window = r.activity.getWindow()
// 获取decorView
View decor = r.window.getDecorView()
decor.setVisibility(View.INVISIBLE)
// 获取WindowMananger,实际上是获取ViewManager的子类对象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
// 获取viewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl()
if (impl != null) {
impl.notifyChildRebuilt()
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true
// 在这里WindowManager将DecorView添加到PhoneWIndow中
wm.addView(decor, l)
}
......
}
}
......
Looper.myQueue().addIdleHandler(new Idler())
}
- WindowManagerImpl 将DecorView添加到Window中
# WindowManagerImpl.class view是DecorView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
- 新建ViewRootImpl对象将内部mView(DecorView)通过setView赋值,并调用requaetLayout()方法,在
mChoreographer发送了一个遍历绘制的任务,最终开启了View的真正绘制流程。DecorView的行为交给ViewRootImpl来控制
# WindowManagerGlobal.class
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
..... 判空处理
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView,userId);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
# ViewRootImpl.class
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
......
requestLayout();
......
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
- ViewRootImpl的perfromTraversals()方法完成具体的视图绘制流程
# ViewRootImpl.class
private void performTraversals() {
......
if (!mStopped || mReportNextDraw) {
View的测量时递归逐层测量,由父布局与子布局共同确认子View的测量模式,在子布局测量完毕时确认父布局的宽高
performMeasure(childWidthMeasureSpec,
childHeightMeasureSpec);
layoutRequested = true;
}
......
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
......
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
if (mPendingTransitions != null &&mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size();++i) {
mPendingTransitions.get(i).startChangingAnmations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
- performMeasure()
循环往下遍历,直到当前控件为View为止
# ViewRootImpl.class
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);
}
}
- MeasureSpec存储了测量模式和具体的宽高信息,高两位代表测量模式,低30位表示具体的宽高信息
- 测量模式
- UNSPECFIED:未指定模式,父容器不限制View的大小,这种情况下计算出来的size值基本就用不到,一般来讲可滑动的布局对子View的限制就是UNSPECFIED,实际上就是让子View在他们内部滚动,意味着子View的尺寸要大于父View,所以不应该给它施加约束。
- EXACTLY:精确模式,对应xml中设置为match_parent或者具体的数值,子View的尺寸是一个确切的值
- AT_MOST:最大模式,子View最大不能超过得到的size值
# ViewRootImpl.class
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}
private boolean draw(boolean fullRedrawNeeded) {
drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
mView.draw(canvas);
}
requestLayout() 重新绘制视图,子View调用requestLayout()方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRooyImpl会调用三大流程,从measure开始,对于每个含有标记位的View及其子View都会进行测量、布局、绘制。
# View.clas
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
......
}
- 一直往上追溯,最顶层的ViewGroup就是DecorView,那DecorView的mParent又是谁呢?没错 就是ViewRootImpl
#ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
view.assignParent(this);
}
- 最终调用了ViewRootImpl的requestLayout()方法
# ViewRootImpl.class
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
invalidate()在UI线程中重新绘制视图。当调用了View.invalidate()方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,,直到ViewRootImpl处理该事件,最终触发performTraversals()方法,进行开始View树重绘流程(只绘制需要重绘的视图)
# View.class
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
}
- 最终递归遍历到ViewRootImpl的performTraversals()方法
# ViewRootImpl.class
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
....
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}