目錄java
1、前言面試
2、咱們的目標是啥canvas
3、繪製流程從何而起windows
4、Activity 的界面結構在哪裏開始造成app
5、繪製流程如何運轉起來的ide
6、實戰函數
7、寫在最後oop
繪製流程能夠說是Android進階中必不可少的一個內容,也是面試中被問得最多的問題之一。這方面優秀的文章也已是很是之多,可是小盆友今天仍是要以本身的姿態來炒一炒這冷飯,或許就是蛋炒飯了😄。話很少說,老規矩先上實戰圖,而後開始分享。 標籤佈局 佈局
其實這篇文章,小盆友糾結了挺久,由於繪製流程涉及的東西很是之多,並不是一篇文章能夠寫完,因此這篇文章我先要肯定一些目標,防止由於追查源碼過深,而迷失於源碼中,最後致使一無所得。咱們的目標是:post
1.繪製流程從何而起 2.Activity 的界面結構在哪裏開始造成 3.繪製流程如何運轉起來
接下來咱們就一個個目標來 conquer。
咱們一說到繪製流程,就會想到或是聽過onMeasure、onLayout、onDraw這三個方法,可是有沒想過爲何咱們開啓一個App或是點開一個Activity,就會觸發這一系列流程呢?想知道繪製流程從何而起,咱們就有必要先解釋 App啓動流程 和 Activity的啓動流程。 咱們都知道 ActivityThread 的 main 是一個App的入口。咱們來到 main 方法看看他作了什麼啓動操做。
ActivityThread 的 main方法是由 ZygoteInit 類中最終經過 RuntimeInit類的invokeStaticMain 方法進行反射調用。有興趣的童鞋能夠自行查閱下,限於篇幅,就再也不展開分享。
// ActivityThread 類
public static void main(String[] args) {
// ...省略不相關代碼
// 準備主線程的 Looper
Looper.prepareMainLooper();
// 實例化 ActivityThread,用於管理應用程序進程中主線程的執行
ActivityThread thread = new ActivityThread();
// 進入 attach 方法
thread.attach(false);
// ...省略不相關代碼
// 開啓 Looper
Looper.loop();
// ...省略不相關代碼
}
複製代碼
進入 main 方法,咱們便看到很熟悉的 Handler機制。在安卓中都是以消息進行驅動,在這裏也不例外,咱們能夠看到先進行 Looper 的準備,在最後開啓 Looper 進行循環獲取消息,用於處理傳到主線程的消息。 這也是爲何咱們在主線程不須要先進行 Looper 的準備和開啓,emmm,有些扯遠了。 回過頭,能夠看到夾雜在中間的 ActivityThread 類的實例化而且調用了 attach 方法。具體代碼以下,咱們接着往下走。
// ActivityThread 類
private void attach(boolean system) {
// ...省略不相關代碼
// system 此時爲false,進入此分支
if (!system) {
// ...省略不相關代碼
// 獲取系統的 AMS 服務的 Proxy,用於向 AMS 進程發送數據
final IActivityManager mgr = ActivityManager.getService();
try {
// 將咱們的 mAppThread 傳遞給 AMS,AMS 即可控制咱們 App 的 Activity
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// ...省略不相關代碼
} else {
// ...省略不相關代碼
}
// ...省略不相關代碼
}
// ActivityManager 類
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
// ActivityManager 類
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
// 在這裏獲取 AMS 的binder
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
// 這裏獲取 AMS 的 proxy,能夠進行發送數據
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
複製代碼
咱們進入attach 方法,方法內主要是經過 ActivityManager 的 getService 方法獲取到了 ActivityManagerService(也就是咱們所說的AMS) 的 Proxy,達到與AMS 進行跨進程通訊的目的。
文中所說的 Proxy 和 Stub,是以系統爲咱們自動生成AIDL時的類名進行類比使用,方便講解。Proxy 表明着發送信息,Stub 表明着接收信息。
在 mgr.attachApplication(mAppThread); 代碼中向 AMS 進程發送信息,攜帶了一個類型爲 ApplicationThread 的 mAppThread 參數。這句代碼的做用,其實就是把 咱們應用的 「控制器」 上交給了 AMS,這樣使得 AMS 可以來控制咱們應用中的Activity的生命週期。爲何這麼說呢?咱們這就有必要來了解下 ApplicationThread 類的結構,其部分代碼以下:
// ActivityThread$ApplicationThread 類
private class ApplicationThread extends IApplicationThread.Stub {
// 省略大量代碼
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
// 會將 AMS 發來的信息封裝在 ActivityClientRecord 中,而後發送給 Handler
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
// 省略大量代碼
}
複製代碼
從 ApplicationThread 的方法名,咱們會驚奇的發現大多方法名以 scheduleXxxYyyy 的形式命名,並且和咱們熟悉的生命週期都挺接近。上面代碼留下了咱們須要的方法 scheduleLaunchActivity ,它們包含了咱們 Activity 的 onCreate、onStart 和 onResume。
scheduleLaunchActivity 方法會對 AMS 發來的信息封裝在 ActivityClientRecord 類中,最後經過 sendMessage(H.LAUNCH_ACTIVITY, r); 這行代碼將信息以 H.LAUNCH_ACTIVITY 的信息標記發送至咱們主線程中的 Handler。咱們進入主線程的 Handler 實現類 H。具體代碼以下:
// ActivityThread$H 類
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
// 省略大量代碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
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);
}
// 省略大量代碼
}
}
// 省略大量代碼
}
複製代碼
咱們從上面的代碼能夠知道消息類型爲 LAUNCH_ACTIVITY,則會進入 handleLaunchActivity 方法,咱們順着往裏走,來到下面這段代碼
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
// 得到一個Activity對象,會進行調用 Activity 的 onCreate 和 onStart 的生命週期
Activity a = performLaunchActivity(r, customIntent);
// Activity 不爲空進入
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
// 該方法最終回調用到 Activity 的 onResume
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();
}
}
}
複製代碼
咱們先看這行代碼 performLaunchActivity(r, customIntent); 最終會調用 onCreate 和 onStart 方法。眼見爲實,耳聽爲虛,咱們繼續進入深刻。來到下面這段代碼
// ActivityThread 類
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 省略不相關代碼
// 建立 Activity 的 Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// ClassLoader 加載 Activity類,並建立 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// 省略不相關代碼
} catch (Exception e) {
// 省略不相關代碼
}
try {
// 建立 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 省略不相關代碼
if (activity != null) {
// 省略不相關代碼
// 調用了 Activity 的 attach
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 這個 intent 就是咱們 getIntent 獲取到的
if (customIntent != null) {
activity.mIntent = customIntent;
}
// 省略不相關代碼
// 調用 Activity 的 onCreate
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// 省略不相關代碼
if (!r.activity.mFinished) {
// zincPower 調用 Activity 的 onStart
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
// zincPower 調用 Activity 的 onRestoreInstanceState 方法,數據恢復
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
// 省略不相關代碼
}
// 省略不相關代碼
}
// 省略不相關代碼
return activity;
}
// Instrumentation 類
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
// Activity 類
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
// 調用了 onCreate
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
// Activity 類
final void performStart() {
// 省略不相關代碼
// 進行調用 Activity 的 onStart
mInstrumentation.callActivityOnStart(this);
// 省略不相關代碼
}
// Instrumentation 類
public void callActivityOnStart(Activity activity) {
// 調用了 Activity 的 onStart
activity.onStart();
}
複製代碼
進入 performLaunchActivity 方法後,咱們會發現不少咱們熟悉的東西,小盆友已經給關鍵點打上註釋,由於不是文章的重點就再也不細說,不然篇幅過長。
咱們直接定位到 mInstrumentation.callActivityOnCreate 這行代碼。進入該方法,方法內會調用 activity 的 performCreate 方法,而 performCreate 方法裏會調用到咱們常常重寫的 Activity 生命週期的 onCreate 方法。😄至此,找到了 onCreate 的調用地方,這裏須要立個 FLAG1,由於目標二須要的開啓即是這裏,我下一小節分享,勿急。
回過頭來繼續 performLaunchActivity 方法的執行,會調用到 activity 的 performStart 方法,而該方法又會調用到 mInstrumentation.callActivityOnStart 方法,最後在該方法內便調用了咱們常常重寫的 Activity 生命週期的 onStart 方法。😊至此,找到了 onStart 的調用地方。
找到了兩個生命週期的調用地方,咱們須要折回到 handleLaunchActivity 方法中,繼續往下運行,便會來到 handleResumeActivity 方法,具體代碼以下:
// ActivityThread 類
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 省略部分代碼
r = performResumeActivity(token, clearHide, reason);
// 省略部分代碼
if (r.window == null && !a.mFinished && willBeVisible) {
// 將 Activity 中的 Window 賦值給 ActivityClientRecord 的 Window
r.window = r.activity.getWindow();
// 獲取 DecorView,這個 DecorView 在 Activity 的 setContentView 時就初始化了
View decor = r.window.getDecorView();
// 此時爲不可見
decor.setVisibility(View.INVISIBLE);
// WindowManagerImpl 爲 ViewManager 的實現類
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;
// 往 WindowManager 添加 DecorView,而且帶上 WindowManager.LayoutParams
// 這裏面便觸發真正的繪製流程
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
// 省略不相關代碼
}
複製代碼
performResumeActivity方法最終會調用到 Activity 的 onResume 方法,由於不是咱們該小節的目標,就不深刻了,童鞋們能夠自行深刻,代碼也比較簡單。至此咱們就找齊了咱們一直重寫的三個 Acitivity 的生命週期函數 onCreate、onStart 和 onResume 。按照這一套路,童鞋們能夠看看 ApplicationThread 的其餘方法,會發現 Activity 的生命週期均在其中能夠找到影子,也就證明了咱們最開始所說的 咱們將應用 「遙控器」 交給了AMS。而值得一提的是,這一操做是處於一個跨進程的場景。
繼續往下運行來到 wm.addView(decor, l); 這行代碼,wm 的具體實現類爲 WindowManagerImpl,繼續跟蹤深刻,來到下面這一連串的調用
// WindowManagerImpl 類
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// tag:進入這一行
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// WindowManagerGlobal 類
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略不相關代碼
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 省略不相關代碼
// 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 將 view 和 param 交於 root
// ViewRootImpl 開始繪製 view
// tag:進入這一行
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// ViewRootImpl 類
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 省略不相關代碼
// 進入繪製流程
// tag:進入這一行
requestLayout();
// 省略不相關代碼
}
}
}
// ViewRootImpl 類
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// tag:進入這一行
scheduleTraversals();
}
}
// ViewRootImpl 類
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 提交給 編舞者,會在下一幀繪製時調用 mTraversalRunnable,運行其run
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
中間跳轉的方法比較多,小盆友都打上了 // tag:進入這一行 註釋,童鞋們能夠自行跟蹤,會發現最後會調用到編舞者,即 Choreographer 類的 postCallback方法。Choreographer 是一個會接收到垂直同步信號的類,因此當下一幀到達時,他會調用咱們剛纔提交的任務,即此處的 mTraversalRunnable,並執行其 run 方法。
值得一提的是經過 Choreographer 的 postCallback 方法提交的任務並非每一幀都會調用,而是隻在下一幀到來時調用,調用完以後就會將該任務移除。簡而言之,就是提交一次就會在下一幀調用一次。
咱們繼續來看 mTraversalRunnable 的具體內容,看看每一幀都作了寫什麼操做。
// ViewRootImpl 類
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// ViewRootImpl 類
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// ViewRootImpl 類
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 進入此處
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
// ViewRootImpl 類
private void performTraversals() {
// 省略不相關代碼
if (!mStopped || mReportNextDraw) {
// 省略不相關代碼
// FLAG2
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 省略不相關代碼
// 進行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 省略不相關代碼
// 進行擺放
performLayout(lp, mWidth, mHeight);
// 省略不相關代碼
// 佈局完回調
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
// 省略不相關代碼
// 進行繪製
performDraw();
}
複製代碼
調用了 mTraversalRunnable 的 run 方法以後,會發現也是一連串的方法調用,後來到 performTraversals,這裏面就有咱們一直提到三個繪製流程方法的起源地。這三個起源地就是咱們在上面看到的三個方法 performMeasure、performLayout 、performDraw 。
而這三個方法會進行以下圖的一個調用鏈(😄仍是手繪,勿噴),從代碼咱們也知道,會按照 performMeasure、performLayout 、performDraw 的順序依次調用。
performMeasure 會觸發咱們的測量流程,如圖中所示,進入第一層的 ViewGroup,會調用 �measure 和 onMeasure,在 onMeasure 中調用下一層級,而後下一層級的 View或ViewGroup 會重複這樣的動做,進行全部 View 的測量。(這一過程能夠理解爲書的深度遍歷)
performLayout 和 performMeasure 的流程大同小異,只是方法名不一樣,就再也不贅述。
performDraw 稍微些許不一樣,當前控件爲ViewGroup時,只有須要繪製背景或是咱們經過 setWillNotDraw(false) 設置咱們的ViewGroup須要進行繪製時,會進入 onDraw 方法,而後經過 dispatchDraw 進行繪製子View,如此循環。而若是爲View,天然也就不須要繪製子View,只需繪製自身的內容便可。
至此,繪製流程的源頭咱們便了解清楚了, onMeasure 、 onLayout、onDraw 三個方法咱們會在後面進行詳述並融入在實戰中。
上圖是 Activity 的結構。咱們先進行大體的描述,而後在進入源碼體會這一過程。
咱們能夠清晰的知道一個 Activity 會對應着有一個 Window,而 Window 的惟一實現類爲 PhoneWindow,PhoneWindow 的初始化是在 Activity 的 attach 方法中,咱們前面也有提到 attach 方法,感興趣的童鞋能夠自行深刻。
在往下一層是一個 DecorView,被 PhoneWindow 持有着,DecorView 的初始化在 setContentView 中,這個咱們待會會進行詳細分析。DecorView 是咱們的頂級View,咱們設置的佈局只是其子View。
DecorView 是一個 FrameLayout。但在 setContentView 中,會給他加入一個線性的佈局(LinearLayout)。該線性佈局的子View 則通常由 TitleBar 和 ContentView 進行組成。TitleBar 咱們能夠經過 requestWindowFeature(Window.FEATURE_NO_TITLE); 進行去除,而 ContentView 則是來裝載咱們設置的佈局文件的 ViewGroup 了。
如今咱們已經有一個大概的印象,接下來進行詳細分析。在上一節中(FLAG1處),咱們最早會進入的生命週期爲onCreate,在該方法中咱們都會寫上這樣一句代碼setContentView(R.layout.xxxx) 進行設置佈局。通過上一節咱們也知道,真正的繪製流程是在 onResume 以後(忘記的童鞋請倒回去看一下),那麼 setContentView 起到一個什麼做用呢?我進入源碼一探究竟吧。
進入 Activity 的 setContentView 方法,能夠看到下面這段代碼。getWindow 返回的是一個 Window 類型的對象,而經過Window的官方註釋能夠知道其惟一的實現類爲PhoneWindow, 因此咱們進入 PhoneWindow 類查看其 setContentView 方法,這裏值得咱們注意有兩行代碼。咱們一一進入,咱們先進入 installDecor 方法。
// Activity 類
public void setContentView(@LayoutRes int layoutResID) {
// getWindow 返回的是 PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// Activity 類
public Window getWindow() {
return mWindow;
}
// PhoneWindow 類
@Override
public void setContentView(int layoutResID) {
// 此時 mContentParent 爲空,mContentParent 是裝載咱們佈局的容器
if (mContentParent == null) {
// 進行初始化 頂級View——DecorView 和 咱們設置的佈局的裝載容器——ViewGroup(mContentParent)
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 {
// 加載咱們設置的佈局文件 到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
installDecor 方法的做用爲初始化了咱們的頂級View(即DecorView)和初始化裝載咱們佈局的容器(即 mContentParent 屬性)。具體代碼以下
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 會進行實例化 一個mDecor
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
mContentParent = generateLayout(mDecor);
// 省略不相關代碼
}
複製代碼
在 generateDecor 中會進行 DecorView 的建立,具體代碼以下,較爲簡單
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
緊接着是generateLayout 方法,核心代碼以下,若是咱們在 onCreate 方法前經過requestFeature 進行設置一些特徵,此時的 getLocalFeatures 就會獲取到,並根據其值選擇合適的佈局賦值給 layoutResource 屬性。最後將該佈局資源解析,賦值給 DecorView,緊接着將 DecorView 中 id 爲 content 的控件賦值給 contentParent,而這個控件未來就是裝載咱們設置的佈局資源。
protected ViewGroup generateLayout(DecorView decor) {
// 省略不相關代碼
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 進行加載 DecorView 的佈局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 這裏就獲取了裝載咱們設置的內容容器 id 爲 R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 省略不相關代碼
return contentParent;
}
複製代碼
咱們折回到 setContentView 方法,來到 mLayoutInflater.inflate(...); 這行代碼,layoutResID 爲咱們設置的佈局文件,而 mContentParent 就是咱們剛剛獲取的id 爲 content 的控件, 這裏即是把他從 xml 文件解析成一棵控件的對象樹,而且放入在 mContentParent 容器內。
至此咱們知道,Activity 的 setContentView 是讓咱們佈局文件從xml 「翻譯」 成對應的控件對象,造成一棵以 DecorView 爲根結點的控件樹,方便咱們後面繪製流程進行遍歷。
終於來到核心節,咱們來繼續分析第三節最後說到的三個方法onMeasure、onLayout、onDraw,這即是繪製流程運轉起來的最後一道門閥,是咱們自定義控件中可操做的部分。咱們接下來一個個分析
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
複製代碼
要解釋清楚這個方法,咱們須要先說明兩個參數的含義和構成。兩個參數都是 MeasureSpec 的類型
MeasureSpec 是一個 32位的二進制數。高2位爲測量模式,即SpecMode;低30位爲測量數值,即SpecSize。咱們先看下源碼,從源碼中找到這兩個值的含義。
如下是 MeasureSpec 類的代碼(刪除了一些不相關的代碼)
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
// 最終結果爲:11 ...(30位)
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 父View 不對 子View 施加任何約束。 子View能夠是它想要的任何尺寸。
// 二進制:00 ...(30位)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 父View 已肯定 子View 的確切大小。子View 的大小即是父View測量所得的值
// 二進制:01 ...(30位)
public static final int EXACTLY = 1 << MODE_SHIFT;
// 父View 指定一個 子View 可用的最大尺寸值,子View大小 不能超過該值。
// 二進制:10 ...(30位)
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
// API 17 以後,sUseBrokenMakeMeasureSpec 就爲 false
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
複製代碼
類中有三個常量: UNSPECIFIED 、EXACTLY 、AT_MOST,他們對應着三種測量模式,具體含義咱們在註釋中已經寫了,小盆友整理出如下表格方便咱們查閱。
makeMeasureSpec 方法,該方法用於合併測量模式和測量尺寸,將這兩個值合爲一個32位的數,高2位爲測量模式,低30位爲尺寸。
該方法很簡短,主要得益於 (size & ~MODE_MASK) | (mode & MODE_MASK) 的位操做符,但也帶來了必定的理解難度。咱們拆解下
size & ~MODE_MASK 剔除 size 中的測量模式的值,即將高2位置爲00
mode & MODE_MASK 保留傳入的模式參數的值,同時將低30位置爲 0...(30位0)
(size & ~MODE_MASK) | (mode & MODE_MASK) 就是 size的低30位 + mode的高2位(總共32位)
至於 &、~、|這三個位操做爲什麼能作到如此的騷操做,請移步小盆友的另外一博文——Android位運算簡單講解。(內容很簡短,不熟悉這塊內容的童鞋,強烈推薦瀏覽一下)
getMode 方法用於獲取咱們傳入的 measureSpec 值的高2位,即測量模式。
getSize 方法用於獲取咱們傳入的measureSpec 值的低30位,即測量的值。
解釋完 MeasureSpec 的是什麼,咱們還有兩個問題須要搞清楚:
1.這兩個參數值從哪來 2.這兩個參數值怎麼使用
藉助下面這張簡圖,設定當前運行的 onMeasure 方法處於B控件,則其兩個MeasureSpec值是由其父視圖(即A控件)計算得出,計算的規則ViewGroup 有對應的方法,即 getChildMeasureSpec。
getChildMeasureSpec 的具體代碼以下。咱們繼續使用上面的情景, B中所得到的值,是 A使用自身的MeasureSpec 和 B 的 LayoutParams.width 或 LayoutParams.height 進行計算得出B的MeasureSpec。
// ViewGroup 類
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) {
// 父視圖爲肯定的大小的模式
case MeasureSpec.EXACTLY:
/**
* 根據子視圖的大小,進行不一樣模式的組合:
* 一、childDimension 大於 0,說明子視圖設置了具體的大小
* 二、childDimension 爲 {@link LayoutParams.MATCH_PARENT},說明大小和其父視圖同樣大
* 三、childDimension 爲 {@link LayoutParams.WRAP_CONTENT},說明子視圖想爲其本身的大小,但
* 不能超過其父視圖的大小。
*/
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;
// 父視圖已經有一個最大尺寸限制
case MeasureSpec.AT_MOST:
/**
* 根據子視圖的大小,進行不一樣模式的組合:
* 一、childDimension 大於 0,說明子視圖設置了具體的大小
* 二、childDimension 爲 {@link LayoutParams.MATCH_PARENT},
* -----說明大小和其父視圖同樣大,可是此時的父視圖還不能肯定其大小,因此只能讓子視圖不超過本身
* 三、childDimension 爲 {@link LayoutParams.WRAP_CONTENT},
* -----說明子視圖想爲其本身的大小,但不能超過其父視圖的大小。
*/
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;
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼
咱們將這段代碼整理成表格
因此最終,B的 onMeasure 方法得到的兩個值,即是 父視圖A 對 B 所作的約束建議值。
你可能會有一個疑惑, 頂級DecorView 的約束哪裏來,咱們切回 FLAG2 處,在進入 performMeasure 方法時,攜帶的兩個MeasureSpec 是由 WindowManager 傳遞過來的 Window 的 Rect 的寬高 和 Window 的 WindowManager.LayoutParam 共同決定。簡而言之,DecorView的約束從 Window的參數得來。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 進行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
複製代碼
咱們上面一直提到的一個詞叫作 「建議」,是由於到達B的兩個維度(橫縱)的 MeasureSpec,不是就已經決定了控件B的寬高。
這裏咱們能夠類比爲 父母老是語重心長的跟本身的孩子說,你要怎麼作怎麼作(即算出了子View 的 MeasureSpec),懂事的孩子會知道遵從父母的建議可讓本身少走彎路(即遵循傳遞下來的MeasureSpec約束),而調皮一點的孩子,以爲打破常規更加好玩(即無論 MeasureSpec 的規則約束)。
按照約定,咱們是要遵循父View給出的約束。而B控件再進行計算其本身子View的MeasureSpec(若是有子View),子View 會再進行測量 孫View,這樣一層層的測量(這裏能感覺到樹結構的魅力了吧😄)。
B控件完成子View的測量,調用setMeasuredDimension 將自身最終的 測量寬高 進行設置,這樣就完成B控件的測量流程就完畢了。
protected void onLayout(boolean changed, int l, int t, int r, int b)
複製代碼
onLayout 則是進行擺放,這一過程比較簡單,由於咱們從 onMeasure 中已經獲得各個子View 的寬高。父View 只要按照本身的邏輯負責給定各個子View 的 左上座標 和 右下座標 便可。
protected void onDraw(Canvas canvas)
複製代碼
繪製流程中,onDraw 應該說是童鞋們最爲熟悉的,只要在 canvas 繪製自身須要繪製的內容即可以。
上一節總結起來,就是咱們在面試時總會說的那句話,onMeasure負責測量、onLayout負責擺放、onDraw負責繪製,但理論老是過於空洞,咱們如今將理論融入到操做中來。咱們用標籤的流式佈局來講明進一步解釋這一切。
在這種標籤流式佈局的情景中,咱們會往控件TagFlowLayout中放入標籤TextView(固然也能夠是更復雜的佈局,這裏爲了方便講清楚思路)。 咱們放入四個標籤,分別爲 「大Android」、「猛猛的小盆友」、「JAVA」、「 PHP是最好的語言」。
咱們藉助這張小盆友手繪的流程圖,來說清楚這繪製流程。
最開始,控件是空的,也就是第一幅小圖。
接着將第一個標籤 「大Android」 放入,此時不超出 TagFlowLayout 的寬,如第二幅小圖所示。
而後將第二個標籤 「猛猛的小盆友」 放入,此時如第三幅小圖所示,超出了 TagFlowLayout 的寬, 因此咱們進行換行,將 「猛猛的小盆友」 放入第二行。
在接着將第三個標籤 「JAVA」 放入,此時不超出 TagFlowLayout 的寬,如第四幅小圖所示。
最後把剩下的 「PHP是最好的語言」 也放入,當此時有個問題,即便一行放一個也容不下(第五幅小圖),由於 「 PHP是最好的語言」 的寬已經超出 TagFlowLayout 的寬,因此咱們在給 「PHP是最好的語言」 測量的MeasureSpec時,須要進行「糾正」,使其寬度爲 TagFlowLayout 的寬,最終造成了第六幅小圖的樣子。
最後還須要將咱們測量的結果經過 setMeasuredDimension 設置咱們自身的 TagFlowLayout 控件的寬高。
通過 onMeasure ,TagFlowLayout 心中已經知道本身的 每一個孩子的寬高 和 每一個孩子要「站」在哪一行,但具體的座標仍是須要進行計算。
「大Android」 的標籤比較座標比較容易(咱們這裏討論思路的時候不考慮padding和margin),(l1,t1) 就是 (0,0),而 (r1,b1) 則是 (0+ width, 0+height)。
「猛猛的小盆友」 的座標須要依賴 「大Android」,(l2,t2) 則爲 (0, 第一行的高度) ,(r2,b2) 爲 (自身的Width,第一行的高度+自身的Height)。
「JAVA」 的座標則須要依賴「猛猛的小盆友」 和 「大Android」, (l3,t3) 爲 (「猛猛的小盆友」的Width, 第一行的高度) ,(r3,b3) 爲 (「猛猛的小盆友」的Width + 自身的Width, 第一行的高度+自身的Height)。
「PHP是最好的語言」 須要依賴前兩行的總高度,具體看座標的計算。 (l4,t4) 爲 (0,第一行高+第二行高), (r4,b4) 爲 (自身的Width,第一行高+第二行高+自身的Height)。
這個方法在咱們這個控件中不須要,由於繪製的任務是由各個子View負責。確切的說 onDraw 在咱們的 TagFlowLayout 並不會被調用,具體緣由咱們在前面已經說了,這裏就不贅述了。
雖然鋪墊了不少,可是 TagFlowLayout 的代碼量並很少,這裏也再也不粘貼出來。咱們只須要在onMeasure 中進行測量,而後將測量的值進行存儲,最後在 onLayout 依賴測量的結果進行擺放便可。