概述
View的繪製流程大致可以分爲兩大塊,一塊是setContentView()和View顯示在屏幕上這個整體流程的梳理,另外一塊是measure、layout、draw細節的實現,由於內容比較多所以我準備分兩篇博客講述。
那麼本篇先從整體入手,分析下setContentView()和View顯示到屏幕這個流程,掌握一個大體流程。測量、佈局、繪製的話將在第二篇介紹。
本文的源碼基於API27。
setContentView()
setContentView()
見名知義,就是將我們的佈局設置到id爲content的佈局上並不包含View的繪製流程,接下來看下實現細節。
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
//AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);//這裏需要注意他是把activity.getWindow()傳入到了後面,也就是PhoneWindow
}
//AppCompatDelegate.java
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else {
return new AppCompatDelegateImplV14(context, window, callback);
}
}
//AppCompatDelegateImplV9.java
//最終是調到這個類的setContentView方法
@Override
public void setContentView(int resId) {
ensureSubDecor();//確保DecorView相關佈局初始化完成
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id爲content的view
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的佈局添加到id爲content的佈局上
mOriginalWindowCallback.onContentChanged();
}
主要的流程就是找到AppCompatDelegate
的實現類,然後最終調到了AppCompatDelegateImplV9
的setContentView()
方法,接下來分爲兩步。
- 通過
ensureSubDecor()
方法確保DecorView
相關佈局初始化完成。 - 找到
DecorView
中id爲content的佈局,將我們自己的佈局inflater到content上。
這裏先說第一步ensureSubDecor()
//AppCompatDelegateImplV9.java
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
...
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//拿到各種Feature進行相應的標記處理
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);//這個標記熟悉吧no_title
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
// Now let's make sure that the Window has installed its decor by retrieving it
mWindow.getDecorView();//確保decorview創建了
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
//省略了很多條件判斷,其實是根據各種Feature條件創建不同的subDecor
subDecor = (ViewGroup) inflater.inflate(R.layout.xxx, null);
// 將subDecor設置到Decorview上
mWindow.setContentView(subDecor);
return subDecor;
}
這裏先拿到各種Feature其中有個我們比較熟悉的FEATURE_NO_TITLE
。平時調用requestWindowFeature()
的時候我們都是在setContentView()
前面,看到這裏你也應該知道爲啥了,因爲setContentView()
方法中會處理相關邏輯,所以需要在他之前調用。
這裏的mWindow
是之前通過activity.getWindow()
方法傳進來的,具體實現類是PhoneWindow
,調用getDecorView()
確保DecorView
初始化了,然後根據各種Feature條件創建不同的subDecor通過setContentView()
方法添加到DecorView
上。
接下來第二步inflate
我們的佈局到id位content的view上。
//LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
...
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {//拿到第一個節點
// Empty
}
if (type != XmlPullParser.START_TAG) {//如果不是開始標籤報錯
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();//拿到節點名字
if (TAG_MERGE.equals(name)) {//merge單獨處理
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);//創建xml中根佈局
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);//根據父佈局創建xml中根佈局的lp,因爲自己的lp是跟父佈局有關的。
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);//inflate temp的子佈局,具體實現就是遞歸的把view添加到temp上
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
inflate比較簡單,根據xml解析拿到xml中根佈局temp
,然後通過root.generateLayoutParams(attrs)
創建lp,之所以要這樣是因爲每個view的lp的創建都是跟父佈局有關的,比如root是LinearLayout
那麼創建的就是LinearLayout.LayoutParams
並初始化weight
和gravity
屬性,而如果root是RelativeLayout
那麼創建的是RelativeLayout.LayoutParams
並初始化跟各個佈局的約束關係。接下來再通過rInflateChildren(parser, temp, attrs, true)
將temp的子佈局inflate到temp上,最後我們把temp添加到root上。
這裏我們可以看下LinearLayout.generateLayoutParams()
方法
//LinearLayout.java
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
//LinearLayout.LayoutParams 內部類
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);//這裏還調用了父類MarginLayoutParams的構造方
TypedArray a =
c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);//獲取xml中weight屬性
gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);//獲取layout_gravity屬性
a.recycle();
}
//ViewGroup.MarginLayoutParams 內部類 初始化margin屬性
public MarginLayoutParams(Context c, AttributeSet attrs) {
super();
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
setBaseAttributes(a,
R.styleable.ViewGroup_MarginLayout_layout_width,
R.styleable.ViewGroup_MarginLayout_layout_height);//調用父類ViewGroup.LayoutParams的setBaseAttributes()讀取最基礎的寬高屬性
//下面就是讀取xml各種margin屬性
int margin = a.getDimensionPixelSize(
com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
if (margin >= 0) {
leftMargin = margin;
topMargin = margin;
rightMargin= margin;
bottomMargin = margin;
} else {
int horizontalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
int verticalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
if (horizontalMargin >= 0) {
leftMargin = horizontalMargin;
rightMargin = horizontalMargin;
} else {
leftMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
UNDEFINED_MARGIN);
if (leftMargin == UNDEFINED_MARGIN) {
mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
leftMargin = DEFAULT_MARGIN_RESOLVED;
}
rightMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginRight,
UNDEFINED_MARGIN);
if (rightMargin == UNDEFINED_MARGIN) {
mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
rightMargin = DEFAULT_MARGIN_RESOLVED;
}
}
startMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginStart,
DEFAULT_MARGIN_RELATIVE);
endMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
DEFAULT_MARGIN_RELATIVE);
if (verticalMargin >= 0) {
topMargin = verticalMargin;
bottomMargin = verticalMargin;
} else {
topMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginTop,
DEFAULT_MARGIN_RESOLVED);
bottomMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
DEFAULT_MARGIN_RESOLVED);
}
if (isMarginRelative()) {
mMarginFlags |= NEED_RESOLUTION_MASK;
}
}
final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
}
// Layout direction is LTR by default
mMarginFlags |= LAYOUT_DIRECTION_LTR;
a.recycle();
}
//ViewGroup.LayoutParams 內部類
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {//讀取寬高
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
可以看到LinearLayout.LayoutParams
有兩層繼承關係,LinearLayout.LayoutParams
負責拿到LinearLayout
的特有屬性,ViewGroup.MarginLayoutParams
負責拿到margin
屬性,ViewGroup.LayoutParams
負責拿到寬高。一般的ViewGroup
基本都是實現自己的lp然後繼承ViewGroup.MarginLayoutParams
。
那麼對activity.setContentView()
總結成一句話就是完成DecorView相關佈局初始化,然後將我們的佈局inflater
到DecorView中id爲content的ViewGroup上並初始化好對應的LayoutParams
,到此佈局的添加和xml中相關屬性的初始化完成,接下來在看他是如何顯示到屏幕上的。
View顯示到屏幕上
View的顯示是Activity收到Resume事件以後,這個事件其實是AMS發送給客戶端的,在收到後會依次執行測量、佈局、繪製流程,並將PhoneWindow添加到屏幕上。
不過再說Resume
事件前,我們先看下是如何監聽的。是在ActivityThread.attach()
方法中添加的。
final ApplicationThread mAppThread = new ApplicationThread();
private void attach(boolean system) {
···
sCurrentActivityThread = this;
mSystemThread = system;
final IActivityManager mgr = ActivityManager.getService();//拿到ams
try {
mgr.attachApplication(mAppThread);//將mAppThread設置給了ams 類似於setOnClickListener設置監聽
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
···
}
mAppThread
其實就是個binder對象,通過ActivityManager.getService()
拿到系統進程的AMS然後調用attachApplication()
把客戶端的監聽mAppThread
設置給他。
這裏我們看下ApplicationThread
這個接受AMS
事件的監聽
private class ApplicationThread extends IApplicationThread.Stub {
...//省略多個schedulexxxActivity()方法
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {//收到AMS的Resume事件
int seq = getLifecycleSeq();
if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
+ " operation received seq: " + seq);
updateProcessState(processState, false);
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);//發送事件
}
private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
"seq= " + seq);
Message msg = Message.obtain();
msg.what = what;
SomeArgs args = SomeArgs.obtain();
args.arg1 = obj;
args.argi1 = arg1;
args.argi2 = arg2;
args.argi3 = seq;
msg.obj = args;
mH.sendMessage(msg);//發送到主線程Handler
}
...
}
是有個scheduleResumeActivity()
方法接受Resume事件,然後由於是進程間通信所以該方法運行在binder線程池上,因此通過sendMessage()
到主線程Handler
執行相應邏輯。
private class H extends Handler {
...
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");//真正處理Resume事件的方法
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
所以Resume事件實際上調到了ActivityThread.handleResumeActivity()
方法
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
···
ActivityClientRecord r = mActivities.get(token);
r = performResumeActivity(token, clearHide, reason);//執行activity的onResume方法
if (r != null) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//將decorview添加到wm 記住哦後面的view在這個流程中都是DecorView
}
}
}
}
···
}
最終是調到WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);//創建ViewRootImpl
view.setLayoutParams(wparams);
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);//調用ViewRootImpl.setView()傳入DecorView
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
然後創建了一個ViewRootImpl
調到ViewRootImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
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.
requestLayout();//進行measure、layout、draw
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);//將view添加到屏幕上顯示
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
view.assignParent(this);//給decorView添加父對象ViewRootImpl
}
}
}
先調用了requestLayout()
對佈局進行測量、佈局、繪製,然後通過mWindowSession.addToDisplay()
將View顯示到window上完成顯示。
這裏我們看下requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//檢查是不是主線程
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//運行mTraversalRunnable
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();//真正的traversals方法
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
最終走到了performTraversals()
private void performTraversals() {
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight)//實際內部也是performMeasure()
performLayout(lp, mWidth, mHeight);
performDraw();
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
return windowSizeMayChange;
}
看到這裏可以發現其實requestLayout()
實際上是分別調用了performMeasure()
、performLayout()
、performDraw()
去執行的測量、佈局、繪製,具體實現細節將會在第二篇分析。
這裏我們總結下view顯示到屏幕上的調用鏈
ActivityThread.handleResumeActivity()
->WindowManager.addView()
->WindowManagerImpl.addView()
->WindowManagerGlobal.addView()
->ViewRootImpl.setView()
->IWindowSession.addToDisplay()
在ViewRootImpl.setView()
中完成了View的測量、佈局、繪製過程,然後通過IWindowSession.addToDisplay()
使VIew顯示出來。
總結
梳理下整體流程
setContentView
完成DecorView
相關佈局初始化並將我們的佈局通過LayoutInflater.inflate()
方法添加到id爲Content的ViewGroup上。- 在收到AMS的Resume事件,最終調到
ViewRootImpl.setView()
當中通過ViewRootImpl.requestLayout()
完成View的測量、佈局、繪製,然後在通過IWindowSession.addToDisplay()
顯示到屏幕上。