UI繪制流程及原理

?本篇文章主要從activity的setContentView()方法著手,講解我們的布局文件是如何添加到根布局,并最終顯示到屏幕上的。

一、View添加到DecorView的過程

首先,我們先從Activity的setContentView()方法開始分析:

public void setContentView(@LayoutRes int layoutResID) {

? ? ? ? getWindow().setContentView(layoutResID);

? ? ? ? initWindowDecorActionBar();

}

getWindow()獲取Window對象。我們知道在Android中,PhoneWindow是Window唯一的實(shí)現(xiàn)類,所以我們看一下PhoneWindow中的setContentView()方法

public void setContentView(int layoutResID) {

? ? ????if (mContentParent ==null) {

? ? ? ? ? ? ? ? // 1、首先調(diào)用installDecor()

????????????????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 {

? ? ? ? ? ? ? ? // 2、將系統(tǒng)layoutResId對應(yīng)的布局文件添加到DecorView后,再將開發(fā)者設(shè)置的布局文件添加到mContentParent中
?????????????????mLayoutInflater.inflate(layoutResID, mContentParent);
????????}
}

首次進(jìn)入,mContentParent一定為空,所以會調(diào)用installDecor()方法:(第2步,我們稍后再說)

private void installDecor() {
????????if (mDecor == null) {
? ? ? ? ? ? ? ? // 1、創(chuàng)建mDecor
?????????????????mDecor = generateDecor(-1);?
? ? ? ? }
? ??????if (mContentParent == null) {

? ? ? ? ? ? ? ? // 2、創(chuàng)建mContentParent
?????????????????mContentParent = generateLayout(mDecor);
? ? ? ? }
}

我們先來看一下第1個(gè)步驟:生成mDecor:

protected DecorViewgenerateDecor(int featureId) {

? ? ? ? // DecorView繼承自FrameLayout

????????return new DecorView(context, featureId, this, getAttributes());

}

直接new創(chuàng)建對象,沒什么好說的。接著看第2步,生成mContentParent:

protected ViewGroupgenerateLayout(DecorView decor) {

? ? ? ? // 1、通過讀取配置文件,設(shè)置Window屬性
? ??????if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
?????????????????requestFeature(FEATURE_NO_TITLE);
????????} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
????????????????requestFeature(FEATURE_ACTION_BAR);
????????}
? ??????if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
?????????????????requestFeature(FEATURE_SWIPE_TO_DISMISS);
????????}
????????if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
?????????????????setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
????????}

? ? ? ? ......
? ? ? ? //2、 通過features,來為layoutResource賦值
? ??????int layoutResource;
? ??????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;
?????????????????}
????????}

????????......

????????else {
????????????????layoutResource = R.layout.screen_simple;
????????}
? ??????3、mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
}

這段代碼主要有三個(gè)步驟:

? ? 1、通過Window Style樣式,設(shè)置Window風(fēng)格

? ? 2、加載系統(tǒng)中對應(yīng)的layoutResource,作為基礎(chǔ)布局。(開發(fā)者自己通過setContentView(int layoutId)設(shè)置的布局文件就是添加到其中的)

? ? 3、當(dāng)資源加載完畢,會調(diào)用DecorView的onresourcesLoaded()方法,進(jìn)而將上述基礎(chǔ)布局添加到頂層布局mDecorView中:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
????????if (mDecorCaptionView != null) {
?????????????????if (mDecorCaptionView.getParent() == null) {
?????????????????????????addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
?????????????????}
?????????????????mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
????????} else {
????????????????addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
????????}
}

通過addView(),最終將layoutResources添加到DecorView中。

最終的布局層次,我們以R.layout.screen_simple為例來分析,請看下圖:


說到這里,通過setContentView()設(shè)置的布局文件就已經(jīng)添加到了DecorView中,View的樹形結(jié)構(gòu)就已經(jīng)存在了。

接下來只需要將該View樹添加到Window中,并執(zhí)行View的測量流程完畢,該View就會顯示到屏幕上。

二、將View樹添加到Window中

ActivityThread是Activity的啟動入口, 在它內(nèi)部有一個(gè)內(nèi)部類H extends Handler,專門用來處理主線程的消息。

class H extends Handler {

????public void handleMessage(Message msg) {

? ? ????switch (msg.what) {

????????????case LAUNCH_ACTIVITY: {

? ? ? ? ? ? ????final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

? ? ? ? ? ????? r.packageInfo = getPackageInfoNoCheck(

????????????????r.activityInfo.applicationInfo, r.compatInfo);

? ? ? ? ? ? ????handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");

? ? ? ? }

????????break;

當(dāng)啟動一個(gè)新的Activity,會觸發(fā)handleLaunchActivity()方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

......

// 1、創(chuàng)建Activity

Activity a = performLaunchActivity(r, customIntent);

......

// 2、調(diào)用activity的onResume方法

handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

我們先看一下performLaunchActivity()方法的源碼:

private ActivityperformLaunchActivity(ActivityClientRecord r, Intent customIntent) {

????Activity activity =null;

????try {

????????????java.lang.ClassLoader cl = appContext.getClassLoader();

? ? ? ? ? ? // 1、創(chuàng)建Activity

? ? ????????activity =mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

? ????? ?} catch (Exception e) {

?????????}

? ? // 2、創(chuàng)建Application

????Application app = r.packageInfo.makeApplication(false, mInstrumentation);

? ? // 3、創(chuàng)建Window

? ??Window window =null;

????if (r.mPendingRemoveWindow !=null && r.mPreserveWindow) {

????????????window = r.mPendingRemoveWindow;

? ? ????????r.mPendingRemoveWindow =null;

? ? ????????r.mPendingRemoveWindowManager =null;

????}

? ? // 4、關(guān)聯(lián)activity和window

????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);

通過源碼,可以看到,performLaunchActivity()方法主要是創(chuàng)建Activity、Application、Window對象,并關(guān)聯(lián)到一起。緊接著,調(diào)用activity的onCreate()方法,進(jìn)而調(diào)用setContentView()方法,構(gòu)建View樹。

然后我們再看一下handleResumeActivity()方法的源碼:

final void handleResumeActivity(IBinder token,? boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ? ? ? ? ? ? // 1、創(chuàng)建 ViewManager?對象

????????ViewManager wm = a.getWindowManager();

????????WindowManager.LayoutParams l = r.window.getAttributes();

????????if (a.mVisibleFromClient) {

????????????if (!a.mWindowAdded) {

????????????????????a.mWindowAdded =true;

? ? ? ? ? ? ? ? ? ? // 2、將DecorView添加到Window中

? ? ? ? ????????????wm.addView(decor, l);

????????}else {

? ? ? ? ????a.onWindowAttributesChanged(l);

? ? ????}

}

在該方法內(nèi)部,創(chuàng)建ViewManager對象,并調(diào)用addView()方法,將DecorView添加到Window中。(通過查看源碼,ViewManager.addView() --> WindowManager.addView() --> WindowManagerGlobal.addView(),這不是本文的重點(diǎn),就不一一列舉了)

至此,View樹就已經(jīng)添加到Window中,但是此時(shí)用戶還看不到界面,因?yàn)檫€沒有進(jìn)行繪制。所以接下來就需要將該View繪制出來

三、View的繪制流程:

我們看一下WindowManagerGlobal.addView(),在該方法內(nèi)部會調(diào)用ViewRootImpl的setView()方法,在該方法內(nèi)部,會調(diào)用requestLayout()方法,進(jìn)而出發(fā)View的繪制流程:

public void requestLayout() {

????if (!mHandlingLayoutInLayoutRequest) {

? ? ? ? ? ? // 1、檢查是否在主線程

????????????checkThread();

? ? ? ? ? ? // 2、執(zhí)行traversal

? ? ? ????? scheduleTraversals();

? ? }

}

void scheduleTraversals() {

????????if (!mTraversalScheduled) {

? ? ? ????????? mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

? ? ????}

}

final TraversalRunnablemTraversalRunnable =new TraversalRunnable();

final class TraversalRunnableimplements Runnable {

????@Override

? ? public void run() {

????????doTraversal();

? ? }

}

void doTraversal() {

????if (mTraversalScheduled) {

????????????performTraversals();

? ? ? ? }

????}

}

private void performTraversals() {

????????performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

????????performLayout(lp, mWidth, mHeight);

????????performDraw();
}

從源碼中,可以看到,最終在performTraversals()方法中,執(zhí)行了View的測量、布局和繪制過程。

至此,View繪制完畢后,就可以顯示到屏幕上了。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容