本文源碼基於 Android sdk 26, 爲了邏輯清晰,省略了無關代碼,不排除後期從新加上相關代碼canvas
系統發送消息開始繪製API調用流程圖以下:api
這個要從應用程序啓動開始, 由於Android sdk使用Java寫的, 而Java程序運行是從main函數開始,因此咱們要先看 ActivityThread 。bash
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
該方法最後調用 Looper.loop() 表示主線程進入消息隊列循環,接下來的全部操做 須要 系統經過Binder機制主動向程序進程發送通知,子線程接受到信息後 向主線程發送Message,從而控制應用程序行爲方式。session
好比說當Activity顯示的時候,系統發送RESUME_ACTIVITY 消息,控制activity開始繪製。app
private class H extends Handler {
·····
public void handleMessage(Message msg) {
switch (msg.what) {
······
case RESUME_ACTIVITY:
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,args.argi3, "RESUME_ACTIVITY");
break;
······
}
}
複製代碼
在handleResumeActivity()方法中,主要作兩件事: 第一,調用Activity的生命週期函數onResume(); 第二,將以前建立的DecorView添加到 ViewRootImp中,開始測量、佈局、繪製。less
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 該方法會調用到Activity的生命週期函數 onResume()
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
boolean willBeVisible = !a.mStartedActivity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//根據源碼能夠知道該方法的具體實如今WindowManagerImpl中
wm.addView(decor, l);
}
}
......
複製代碼
WindowManagerImpl中調用ide
//WindowManagerImpl中方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//mGlobal 即 WindowManagerGlobal
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製代碼
WindowManagerGlobal 中調用函數
//WindowManagerGlobal 中方法
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
......
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
}
}
複製代碼
ViewRootImpl中調用oop
//ViewRootImpl中方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
requestLayout();
......
}
}
}
複製代碼
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
scheduleTraversals();
}
}
複製代碼
通過一連串的調用,最後方法走到這裏,方法主要作一件事,發送一個Runnable 開始View的測量、佈局 、繪製,而後通知系統開始下一幀。佈局
void scheduleTraversals() {
if (!mTraversalScheduled) {
//mTraversalRunnable 中開始View的測量、佈局、繪製
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//通知系統 繪製新一幀畫面, 即將View顯示到屏幕上
notifyRendererOfFramePending();
}
}
複製代碼
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
複製代碼
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
複製代碼
private void performTraversals() {
......
Rect frame = mWinFrame;
......
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
......
//開始測量
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
//佈局
performLayout(lp, mWidth, mHeight);
......
//繪製
performDraw();
......
複製代碼
程序使用一個32位 的 int型的整數 來表示view的尺寸信息, 其中該數據 高2位 表示測量模式, 低30位表示具體的大小數據。
EXACTLY: 精確模式,父控件 已經爲子控制肯定具體尺寸, 大小即爲低30位數值
AT_MOST : 最大值模式,子控件本身肯定本身的尺寸, 但不能超過父控件指定的最大值
UNSPECIFIED : 未肯定模式,父控件沒有作任何約束限制,子控件能夠本身指定任意大小
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
......
}
複製代碼
開始測量子控件時, 父控件會根據自身的尺寸 和子控件的LayoutParam 肯定子控件的MeasureSepc
以下源碼可知,規則以下: 當前控件自身測量模式爲
MeasureSpec.EXACTLY
當子控件LayoutParams 爲 具體值:設置子控件 MeasureSpec.EXACTLY, 尺寸爲當前設置的尺寸
當子控件LayoutParams 爲 MATCH_PARENT : 設置子控件 MeasureSpec.EXACTLY, 尺寸爲父控件尺寸
當子控件LayoutParams 爲 WRAP_CONTENT:設置子控件MeasureSpec.AT_MOST,尺寸不超過父控件尺寸
MeasureSpec.AT_MOST :
當子控件LayoutParams 爲 具體值:設置子控件 MeasureSpec.EXACTLY, 尺寸爲當前設置的尺寸
當子控件LayoutParams 爲 MATCH_PARENT 或 WRAP_CONTENT :設置子控件MeasureSpec.AT_MOST,尺寸不超過父控件尺寸
MeasureSpec.UNSPECIFIED :
當子控件LayoutParams 爲 具體值:設置子控件 MeasureSpec.EXACTLY, 尺寸爲當前設置的尺寸
當子控件LayoutParams 爲 MATCH_PARENT 或 WRAP_CONTENT : 設置子控件 MeasureSpec.UNSPECIFIED,尺寸根據當前配置 設置爲0 或當前父控件尺寸
/**
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼
/**
* View.MeasureSpec中的方法
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
複製代碼
經過父控件 爲本身指定的 MeasureSpec 和自身須要的尺寸, 計算出本身最後的大小。規則以下
/**
*
* @param size 自身須要的尺寸
* @param measureSpec 父控件爲本身指定的MeasureSpec
* @return 控件最後的尺寸
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
//該模式下, 父控件未肯定
result = size;
break;
case MeasureSpec.AT_MOST:
//使用xml中設置的尺寸,但最大值不超過父控件規定的尺寸
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
//精確模式,直接使用具體的值
result = specSize;
break;
}
return result;
}
複製代碼
經過ViewRootImpl中的performLayout(...)開始當前界面的佈局。
使用getValidLayoutRequesters(...)方法 獲得當前控件內全部須要進行佈局的子View, (過濾掉狀態爲View.Gone的view)
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
final View host = mView;
......
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
......
}
複製代碼
View.layout(...)方法 完成當前控件位置的設定,在該方法中會回調onLayout,繼承view的ViewGroup會重寫該方法,實現本身子控件 佈局的邏輯。
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;
......
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
......
}
......
}
複製代碼
好比如下代碼爲 LinearLayout中的代碼
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
複製代碼
經過ViewRootImpl中的performDraw()開始當前界面的繪製。api調用流程圖以下:
在View.draw()開始當前控件的繪製, 繪製步驟如註釋
public void draw(Canvas canvas) {
......
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done... return; } 複製代碼
View子類控件重寫 onDraw()方法 繪製自身, 如LineaLayout中:
@Override
protected void onDraw(Canvas canvas) {
if (mOrientation == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
複製代碼
viewGroup控件 重寫dispatchDraw()方法 繪製 子控件
@Override
protected void dispatchDraw(Canvas canvas) {
......
drawChild(canvas, child, drawingTime
......
}
複製代碼
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
複製代碼
完~
(若有不足,歡迎指出,共同窗習,共同進步)