View技術(shù)原理[1] -- DecorView加載原理

View繪制流程說簡單也簡單,僅僅是三個(gè)步驟,但是說難也是很難,看來無數(shù)的書和文章都不能完整的理解,最后還是親手來總結(jié)一番,雖然學(xué)習(xí)Android有一段時(shí)間了,但對于原理層面的問題還從來沒有認(rèn)真的去寫過,這是第一次嘗試,就當(dāng)作小白吧,共同進(jìn)步。

0. 本篇目錄

  • 對View的初步認(rèn)識(shí)
  • 對UI架構(gòu)圖的分析
  • DecorView與Window的聯(lián)系
  • 初探View繪制

1.初次見面

本部分內(nèi)容需要了解以下幾個(gè)知識(shí)點(diǎn),這是學(xué)習(xí)View的入門,也是面試基礎(chǔ)問題。

Q1:View是什么,ViewGroup又是什么,他們是以什么結(jié)構(gòu)組織的?
Q2:UI界面架構(gòu)圖是什么樣子的?或者說Window,Activity,和View都是什么?

下面來探索一下這幾個(gè)問題。

View是所有UI控件的一個(gè)基類,我們平時(shí)用的Button,TextView先不管內(nèi)部是個(gè)什么繼承邏輯,但歸根結(jié)底都有一個(gè)共同的父類就是View。

而ViewGroup按照字面意思理解,就是一個(gè)View集合,里面可以包括眾多View,而ViewGroup是以來維護(hù)這些View的,如下圖。

View樹

而其實(shí)ViewGroup也是繼承自View的,由這個(gè)樹形結(jié)構(gòu)我們可以明白一些事情,首先根節(jié)點(diǎn)或者父節(jié)點(diǎn)繪制好了才會(huì)去繪制分支節(jié)點(diǎn),或者說事件首先要經(jīng)過父節(jié)點(diǎn)才能到達(dá)根節(jié)點(diǎn),這里面就有一些 測量繪制和事件分發(fā)的知識(shí)了,具體在后面說。

更準(zhǔn)確的說,圖中藍(lán)色的根節(jié)點(diǎn),又叫做ViewParent,由它來控制整個(gè)事件或者整個(gè)測量繪制流程。

于是findViewById() 這個(gè)很熟悉的方法,就是通過遍歷這棵樹來找到目標(biāo)View的。

下面第二個(gè)問題,我們看一下手機(jī)屏幕上顯示的界面是怎么個(gè)組織方式。


https://www.cnblogs.com/jycboy/p/6219915.html

上面的圖是一個(gè)標(biāo)準(zhǔn)的界面組織方式,最外層是一個(gè)Activity,每個(gè)Activity都會(huì)擁有自己的一個(gè)Window,而在Android種Window一般是由PhoneWindow實(shí)現(xiàn)。

Window

可以看到Window是一個(gè)抽象類,而上面的doc已經(jīng)提到了,PhoneWindow是這個(gè)類唯一的實(shí)現(xiàn),稍后會(huì)驗(yàn)證Activty和PhoneWindow的綁定,我們繼續(xù)往下看。

圖中PhoneWindow會(huì)有一個(gè)DecorView,它是這個(gè)界面的根容器,但是本質(zhì)上是一個(gè)FrameLayout,而DecorView內(nèi)部是一個(gè)垂直的LinearLayout,這個(gè)LinearLayout包含兩部分,TitleView和ContentView,其中TitleView有時(shí)我們常見的ActionBar部分的容器,而ContentView就是我們自己創(chuàng)建的界面,它本身是一個(gè)FrameLayout,我們平常用的setContentView就是設(shè)置它的子View。

梳理一下:
DecorView(FrameLayout) 包含一個(gè) LinearLayout
而這個(gè)LinearLayout又包含 TitleView 和 ContentView(FrameLayout)
ContentView 內(nèi)部才是我們自定義的布局

2. UI架構(gòu)探索?

大部分人都會(huì)選擇從setContentView() 方法談起,因?yàn)檫@個(gè)方法是最常見的,用法極其簡單的,但又是有很大的說頭的。

下面是一段代碼(為了清楚,我把自己寫的代碼和源碼綜合到一起了,并且省略一些不太重要的代碼)

// 1.以下代碼來自 一個(gè)新建的Activity :DemoActivity

public class DemoActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);   // to 2
    }
}

// 2.以下代碼是Activity的setContentView方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);  
        ……        
}

首先要知道如果不調(diào)用setContentView,是無法顯示出我們的界面的,然后調(diào)用了setContentView方法之后,Activity內(nèi)部是調(diào)用了getWindow方法的setContentView方法。

那么getWindow方法是什么?

// 以下代碼是Activity的getWindow方法

 public Window getWindow() {
        return mWindow;
    }

返回了一個(gè)mWindow,而根據(jù)返回值我們知道這個(gè)mWindow必然是一個(gè)Window對象。


在哪里做的初始化?



上面這個(gè)方法是Activity的attach方法。而這個(gè)attach() 方法會(huì)在onCreate()方法之前調(diào)用,這里就涉及到Activity啟動(dòng)流程了,暫時(shí)不做深究,我們只需要知道onCreate() 前,mWindow 已經(jīng)完成了初始化,并且指向了PhoneWindow對象。

回到上面 getWindow().setContentView(layoutResID),我們查看PhoneWindow里的setContentView方法。

   // 以下代碼來自 PhoneWindow 的setContentView方法

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //1.初始化ContentView
            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.添加layoutResID布局到mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

首次進(jìn)入這個(gè)方法的時(shí)候,mContentParent 必然為null,所以上面代碼我們只需要關(guān)心標(biāo)號(hào)的兩個(gè)部分,一個(gè)是installDecor() 初始化,二是inflate 解析加載,如代碼注釋所標(biāo)。

而后者我們在寫代碼中也很常見,這里就不多說了,就是把layout解析加載到mContentParent 中,所以著重看一下installDecor。

   // 以下代碼來自 PhoneWindow 的installDecor方法

private void installDecor() {
        mForceDecorInstall = false;
        //如果decorView為空,就生成decorView
        if (mDecor == null) {
            //1.初始化decorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        //如果mContentParent 為空,就初始化
        if (mContentParent == null) {
            //2.初始化mContentParent 
            mContentParent = generateLayout(mDecor);
            ...
        }
         ...
    }

這個(gè)方法代碼很多,著重看兩個(gè)地方,

1.mDecor = generateDecor(-1);
2.mContentParent = generateLayout(mDecor);

其中很顯然,mDecor 是一個(gè) DecorView,mContentParent 是 ContentView

先看generateDecor

// 以下代碼來自 PhoneWindow 的generateDecor方法
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());
}

核心代碼就是最后一句,生成了一個(gè)DecorView返回,而我們已經(jīng)知道DecorView是一個(gè)FrameLayout,它是PhoneWindow的內(nèi)部類。

而對于generateLayout代碼很長,下面我寫個(gè)偽代碼確認(rèn)以下流程即可。

protected ViewGroup generateLayout(DecorView decor) {
      int layoutResource;
      //確認(rèn)布局
      if( xxx 主題 xxx 特性){
          layoutResource = R.layout.xxxxx1;
      } else if( xxxx  主題 xxx 特性){
           layoutResource = R.layout.xxxxx2;
     } else if(){
       ……
     }else{
      ……
    }

   mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
   ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
   ……
  
   return contentParent;


}

這段代碼其實(shí)很簡單,就是根據(jù)你設(shè)置的主題feature來選擇默認(rèn)加載的界面(xml),

什么是theme 和 feature ?
theme是<Application android:theme=""/>,或者<Activity/>節(jié)點(diǎn)指定的themes
feature是requestWindowFeature()中指定的Features
相信這些你都用過。
這也就解釋了為什么必須要在setContentView(...)之前才能執(zhí)行requestWindowFeature(...)

選擇好了之后將 布局加載到DecorView中,并且找到ID_ANDROID_CONTENT這個(gè)控件作為ViewGroup返回,也就是賦值給mContentParent

那么問題是ID_ANDROID_CONTENT到底是個(gè)什么?
很顯然,他就是id為content的那個(gè)frameLayout(在各種各樣的主題xml中,都是這個(gè)名字)

至于onResourcesLoaded,我們知道他是將layoutResource加載到mDecor里的方法,內(nèi)部調(diào)用了addView,這里就不深究了。

總結(jié)

稍稍總結(jié)一下。

Q3: setContentView的流程是什么?

  1. 首先 attach 方法建立 PhoneWindow,在PhoneWindow的 setContentView 方法中 初始化 DecorView,
  2. 調(diào)用generateLayout方法選擇合適主題布局加載到decorView上,最后對mContentParent 賦值,并且將setContentView所傳入的xml布局加載在mContentParent 上。
  3. 到此為止實(shí)現(xiàn)了布局的加載。
拙劣的畫技

最后補(bǔ)充一點(diǎn),狀態(tài)欄導(dǎo)航欄也是在DecorView中的
這里借一張Hierarchy 圖展示一下

https://blog.csdn.net/dreamsever/article/details/78440417

可以看到DecorView中除了LinearLayout還有其他兩部分,分別是狀態(tài)欄和底部導(dǎo)航欄。

看到這里,我們就可以對一個(gè)界面有很深刻的認(rèn)識(shí),那么雖然這不是本文的重點(diǎn),但是也是作為基礎(chǔ)的重要一環(huán),下面來看看View的繪制吧。

3. Decor與Window的小確幸

學(xué)習(xí)到這里,我們發(fā)現(xiàn)界面顯示貌似與Activity無關(guān),所有的View操作都是PhoneWindow來完成的,所以我們還要深究這個(gè)Window。

通過上面我們看到了UI界面的一個(gè)架構(gòu),我們?nèi)庋鬯姷木褪荄ecorView,那么DecorView的內(nèi)容是如何加載到Window上的,你可能會(huì)說了上面setWindow不是嗎?其實(shí)那只是配置一下關(guān)系而已,按照正常的流程需要Window 添加 DecorView才對不是嗎?

Window偷偷說:你也太小瞧我了,我還沒發(fā)話了,你們就自行搞定了???

那么這個(gè)流程是怎么完成的呢?
還記得我們剛才提到過的attach方法吧,我們說attach是onCreate方法調(diào)用之前所調(diào)用的,是屬于Activity啟動(dòng)的一部分,在attach之前的過程,我們暫時(shí)先不說,我們說接下來的流程。

//以下代碼Activity 的 attach方法,有省略

 final void attach( …… ){
    ……
   //1. 創(chuàng)建PhoneWindow
   mWindow = new PhoneWindow(this, window, activityConfigCallback);

    ……
   //2. setWindowManager
    mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
   
}
   ……
   //3 .getWindowManager
   mWindowManager = mWindow.getWindowManager();

可以看到上面的代碼主要分為三部分,1.創(chuàng)建PhoneWindow,我們已經(jīng)熟悉了,2是設(shè)置一個(gè)WindowManager,3是取用這個(gè)WindowManager。

那么WindowManager是什么?

WindowManager繼承自ViewManager,是Android中一個(gè)重要的Service,全局唯一。WindowManager主要用來管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等。

ViewManager

如上圖是ViewManager,里面有幾個(gè)方法,addView等,是對View的操作,而ViewGroup也是實(shí)現(xiàn)了這個(gè)接口,可以自己去探索。

正是因?yàn)閷?shí)現(xiàn)了這個(gè)接口,WindowManager和ViewGroup擁有了控制View的能力。
暫時(shí)不深究,我們繼續(xù)看源碼。

// 以下代碼來自Window類的setWindowManager

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);

        //核心代碼
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

檢查傳入的WindowManager 是否為null,為null就通過系統(tǒng)服務(wù)獲取一個(gè),(會(huì)發(fā)現(xiàn)調(diào)用前后都是通過系統(tǒng)服務(wù)獲取,不知道為啥還要判斷……)然后生成它的實(shí)現(xiàn)。

//以下代碼來自WindowManagerImpl的createLocalWindowManager

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

根據(jù)上面這兩步,我們發(fā)現(xiàn)mWindowManager 已經(jīng)被初始化為WindowManagerImpl 。

那么我們就會(huì)明白,Activity內(nèi)部的mWindowManager 是一個(gè)WindowManagerImpl,插個(gè)小曲,看一眼WindowManagerImpl

//WindowManagerImpl 部分代碼節(jié)選

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
}

可以看到,WindowManagerImpl 確實(shí)是 WindowManager 的實(shí)現(xiàn)類,而且內(nèi)部有實(shí)現(xiàn)addView方法,但是它的addView卻是調(diào)用 WindowManagerGlobal 的addView方法了,所以我們還要繼續(xù)去探索WindowManagerGlobal 。

// 以下代碼來自 WindowManagerGlobal 中的addView

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
         ……
        
        //1. 關(guān)注 ViewRootImpl 
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {

             ……
            //2 . 對ViewRootImpl的初始化操作
            root = new ViewRootImpl(view.getContext(), display);

            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 {
                 //3. 給root設(shè)置我們的布局
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

上面的代碼比較長,但要是理解這個(gè),我們本部分內(nèi)容就基本上結(jié)束了,我們一點(diǎn)點(diǎn)看。

首先看注釋1的地方,新建了一個(gè)ViewRootImpl 聲明,并在2的地方進(jìn)行了初始化,同時(shí)在3的地方調(diào)用了它的setView方法傳入了view(這里的view就是我們上一部分講的DecorView,先知曉一下,后面會(huì)驗(yàn)證。)

那么說白了最終我們的工作都交給了ViewRootImpl 去做,而ViewRootImpl是View中的最高層級,屬于所有View的根,但ViewRootImpl不是View,只是實(shí)現(xiàn)了ViewParent接口,可以看到ViewRootImpl一頭是View,一頭是WindowManager

而在上面注釋3的地方,我們說調(diào)用了ViewRootImpl的setView方法,這個(gè)方法我就不貼了,在這個(gè)方法里調(diào)用了addToDisplay方法來實(shí)現(xiàn)了Window中添加View。

mWindowSession實(shí)現(xiàn)了IWindowSession接口,它是Session的客戶端Binder對象.
addToDisplay是一次AIDL的跨進(jìn)程通信,通知WindowManagerService添加IWindow

到此結(jié)束。
但是好像有點(diǎn)不對勁?。可厦嬲f的流程怎么這么亂?下面來串聯(lián)一下吧。

工作流程圖(圖片最后一步有點(diǎn)問題,待修改)

首先我們上了一張圖,這個(gè)圖上有我們熟悉的,也有我們不熟悉的,我們從起點(diǎn)梳理一下。

當(dāng) startActivity方法調(diào)用的時(shí)候,首先執(zhí)行handleLaunchActivity來創(chuàng)建新Activity。

//以下代碼來自ActivityThread的handleLaunchActivity方法,只保留兩句核心代碼

public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
    ...
  
       Activity a = performLaunchActivity(r, customIntent);
    ...
      
       handleResumeActivity(……)    
   
}

首先是調(diào)用了performLaunchActivity方法 ,其次調(diào)用了handleResumeActivity方法,這里跟我們上面圖中所畫是一樣的。

我們看一下performLaunchActivity源碼

//以下代碼來自ActivityThread的performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //創(chuàng)建Activity所需的Context
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;

    try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //Activity通過ClassLoader創(chuàng)建出來
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            //創(chuàng)建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            appContext.setOuterContext(activity);
            //將Context與Activity進(jìn)行綁定,并調(diào)用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);
            //調(diào)用activity.oncreate
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        }
}

可以看到這里面調(diào)用了newActivity,調(diào)用了attach,然后還有callActivityOnCreate(回調(diào)onCreate)方法,這也跟我們圖中所畫的生命周期相關(guān)方法及流程一樣。

對于粉色對話泡泡1處,我們已經(jīng)了然于胸了,上文不止一次提到過attach里面所做的工作,包括下面的windowManager的創(chuàng)建,在attach完成window及windowmanager的初始化工作之后,

緊接著onCreate開始大展拳腳,setContentView的調(diào)用,粉色對話泡泡2,本文一開始就說了,這里也不再贅述。

接下來onStart就會(huì)被調(diào)用,(至于怎么被調(diào)用?我們不深究了,其實(shí)可以推斷應(yīng)該也是在DecorView初始化完成后的一個(gè)回調(diào))。

接著來到了主線程的流程。
handleResumeActivity的調(diào)用。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

        //1. 調(diào)用activity.onResume
        ActivityClientRecord r = performResumeActivity(token, clearHide);
 
        if (r != null) {
            final Activity a = r.activity;
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow(); 
                //2. DecorView的獲取
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //3. 獲取一個(gè)WindowManger
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //4.把當(dāng)前的DecorView與WindowManager綁定一起
                    wm.addView(decor, l);
                }

            ... 
    }

我畫出了4條重點(diǎn),其中1處是調(diào)用了onResume方法,2是獲取了DecorView,3是獲取了WindowManagerImpl,4是調(diào)用了WindowManagerImpl的addView方法將DecorView傳入進(jìn)去,這也就印證了我們剛才的鋪墊,DecorView就是從這里傳入進(jìn)去的,并且與Window建立連接的,至于WindowManagerImpl的addView都做了啥,我們上面都說過了,你不會(huì)忘記吧。(也就是粉色對話泡泡3的流程)

到此為止,我們的View就加載到Window里面了,那么接下來要做什么神奇的事情呢?

4 . 初探View繪制

上面我們提到一個(gè)關(guān)鍵的內(nèi)容ViewRootImpl,我們說他是溝通View與Window的橋梁,而且我們也對它的setView方法做了較為簡單的分析,下面我們詳細(xì)分析,setView方法內(nèi)容也比較多,這里就不貼了,里面除了調(diào)用addToDisplay來綁定window與decorView外,還在此前調(diào)用了requestLayout() 方法

requestLayout()主要是讓View經(jīng)歷measure layout draw三個(gè)階段,

//以下代碼來自ViewRootImpl的requestLayout

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

這個(gè)代碼比較簡單,關(guān)注最后一句,調(diào)用了scheduleTraversals,我們來看一下它。

//以下代碼來自ViewRootImpl的scheduleTraversals

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //核心內(nèi)容
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

關(guān)注postCallback方法,傳入了一個(gè)mTraversalRunnable任務(wù)。而通過定位我們會(huì)發(fā)現(xiàn)這個(gè)mTraversalRunnable其實(shí)是一個(gè)TraversalRunnable。

ViewRootImpl內(nèi)定義

可以看到run方法只調(diào)用了doTraversal方法。

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

             //關(guān)鍵內(nèi)容,這個(gè)就是繪制入口
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

輾轉(zhuǎn)反側(cè),終于找到了繪制入口,而這個(gè)方法內(nèi)容比較多,我們只需要它內(nèi)部是如下類似的邏輯即可。

private void performTraversals() {
   //測量
   performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
  //定位  
  performLayout(lp,desiredWindowWidth,desiredWindowHeight);
  //繪制 
  performDraw();
}

好嘞,就到這里。

什么情況?就這樣就結(jié)束了?
NO,更關(guān)鍵的內(nèi)容等著我們?nèi)グl(fā)現(xiàn)嘞,明天我們繼續(xù)吧。

5 . 總結(jié)

我們本文從setContentView入手講了DecorView是如何初始化的,然后又從ActivityThread的角度來分析了DecorView是如何加載到Window上的,最后對View繪制進(jìn)行了簡單的探索,通過對performTraversals的初步探索,我們發(fā)現(xiàn)后面會(huì)有很多內(nèi)容,要完整的講明白是一件難事,所以一般會(huì)從主線入手,下一篇文章就來真正的談?wù)劺L制過程。

可以試著畫畫流程圖哦,看看我畫的兩張圖綜合起來是什么效果。

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

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