1. 程式人生 > >View繪製原始碼淺析(一)佈局的載入

View繪製原始碼淺析(一)佈局的載入

前言

說到View的繪製大部分人都能說上一兩句,但細節實在太多如果沒系統的去看很難吃透。近期我專門抽了一週多的時間讀了繪製相關的原始碼,這裡準備用三篇部落格做一個系統的講述,目錄如下。

  1. View繪製原始碼淺析(一)佈局的載入
  2. View繪製原始碼淺析(二)佈局的測量、佈局、繪製
  3. View繪製原始碼淺析(三)requestLayoutinvalidatepostInvalidate三者的區別

本文的原始碼基於API27。

疑問

佈局載入最重要的就是setContentView()方法了,只需要我們傳入一個佈局id即可完成佈局的載入,但實際上這裡是有幾個疑問的。

  1. 如何根據xml建立View的。
  2. 如何讀取xml中View相關屬性的。
  3. 建立的View新增到了哪。

接下來我們帶著這些問題再去看原始碼,避免迷失方向。

setContentView()

我們先從setContentView()這個佈局載入的入口開始,看看究竟如何載入佈局的。

//MainActivity.java
public class MainActivity extends AppCompatActivity {//繼承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);//拿到Activity的委託物件呼叫setContentView() } //AppCompatActivity.java
@NonNull public AppCompatDelegate getDelegate() {//獲取Activity委託物件 if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; } //AppCompatDelegate.java public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {//建立Activity委託物件 return create(activity, activity.getWindow(), callback);//這裡將activity.getWindow()傳入。 } //AppCompatDelegate.java private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) {//根據不同的版本建立不同的Activity委託物件 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 //最終是調到v9的setContentView方法 @Override public void setContentView(int resId) { ensureSubDecor();//確保SubDecor相關佈局初始化完成 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(); }

由於繼承的是appCompatActivity這個相容的Activity所以是根據不同的api版本建立不同的AppCompatDelegate實現類以相容老邏輯。setContentView()最終是調到了AppCompatDelegateImplV9setContentView(),接下來具體實現分為兩步。

  1. 通過ensureSubDecor()方法確保SubDecor相關佈局初始化完成。
  2. 找到SubDecor中id為content的佈局,將我們自己的佈局inflater到content上。

這裡說明下,SubDecor不是DecorView,只是一個變數名為subDecorViewGroup不過這裡充當DecorView的角色,不要混淆了。

這裡先說第一步ensureSubDecor()

//AppCompatDelegateImplV9.java
private void ensureSubDecor() {//確保SubDecor的建立
    if (!mSubDecorInstalled) {//如果沒有建立SubDecor
        mSubDecor = createSubDecor();//建立SubDecor
        ...
    }
}

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);//拿到AppCompat相關的主題屬性
	//根據主題中的屬性執行對應的Feature方法
    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//我們比較熟悉的FEATURE_NO_TITLE Feature
    } 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();

    mWindow.getDecorView();//確保DecorView的建立

    final LayoutInflater inflater = LayoutInflater.from(mContext);//用來填充SubDecor的inflater
    ViewGroup subDecor = null;//subDecor佈局

	//接下來是根據主題屬性初始化不同的subDecor佈局
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                R.layout.abc_dialog_title_material, null);

            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }

            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                .inflate(R.layout.abc_screen_toolbar, null);

            mDecorContentParent = (DecorContentParent) subDecor
                .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());

            /**
                 * Propagate features to DecorContentParent
                 */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                                                      new OnApplyWindowInsetsListener() {
                                                          @Override
                                                          public WindowInsetsCompat onApplyWindowInsets(View v,
                                                                                                        WindowInsetsCompat insets) {
                                                              final int top = insets.getSystemWindowInsetTop();
                                                              final int newTop = updateStatusGuard(top);

                                                              if (top != newTop) {
                                                                  insets = insets.replaceSystemWindowInsets(
                                                                      insets.getSystemWindowInsetLeft(),
                                                                      newTop,
                                                                      insets.getSystemWindowInsetRight(),
                                                                      insets.getSystemWindowInsetBottom());
                                                              }

                                                              // Now apply the insets on our view
                                                              return ViewCompat.onApplyWindowInsets(v, insets);
                                                          }
                                                      });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                    @Override
                    public void onFitSystemWindows(Rect insets) {
                        insets.top = updateStatusGuard(insets.top);
                    }
                });
        }
    }

    if (subDecor == null) {//檢查SubDecor是否建立
        throw new IllegalArgumentException(
            "AppCompat does not support the current theme features: { "
            + "windowActionBar: " + mHasActionBar
            + ", windowActionBarOverlay: "+ mOverlayActionBar
            + ", android:windowIsFloating: " + mIsFloating
            + ", windowActionModeOverlay: " + mOverlayActionMode
            + ", windowNoTitle: " + mWindowNoTitle
            + " }");
    }

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
        R.id.action_bar_activity_content);//找到subDecor中id為action_bar_activity_content的佈局

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);//找到PhoneWindow中id為content的佈局
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {//將windowContentView的子佈局全部新增到subDecor中id為action_bar_activity_content的佈局上
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        windowContentView.setId(View.NO_ID);//清除PhoneWindow中id為content的佈局id
        contentView.setId(android.R.id.content);//給subDecor中id為action_bar_activity_content的佈局設定上新的id為content以假亂真

        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);//將subDecor設定到window上
    return subDecor;
}

這裡先拿到主題中的屬性,然後根據主題中的屬性設定對應的Feature,並根據條件建立對應的subDecor。接下來拿到PhoneWindow中id為content的View,把它的子佈局全部新增到subDecor中id為action_bar_activity_content的佈局上,然後將windowContentView的id移除,給subDecor中id為action_bar_activity_content的佈局設定上新的id為content以假亂真充當ContentView的角色,最後將SubDecor通過mWindow.setContentView(subDecor)設定到window上。

那麼經過ensureSubDecor()方法後我們就完成了DecorViewSubDecor的初始化並通過mWindow.setContentView(subDecor)SubDecor新增到了DecorView上。完成了SubDecorDecorView的關聯。

在回到我們之前的setContentView()

//AppCompatDelegateImplV9.java 
//最終是調到v9的setContentView方法
@Override
public void setContentView(int resId) {
    ensureSubDecor();//確保SubDecor相關佈局初始化完成
    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();
}

完成SubDecor初始化後,我們通過mSubDecor.findViewById(android.R.id.content)找到contentParent,然後直接LayoutInflater.from(mContext).inflate(resId, contentParent)將佈局新增到了contentParent上完成了佈局的新增。

那麼對於第一和第二個問題則必須在LayoutInflater.inflate()中尋找答案了,而第三個問題我們已經可以回答了

建立的View新增到了哪?

答:新增到了id為android.R.id.content的view上。

LayoutInflater.inflate()

接下來我們看下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);//根據資源id建立解析器
        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) {//當滿足root不為null並且attachToRoot為false則直接將lp設定到temp上
                            // 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不為null 並且 attachToRoot為true則直接add到root上
                        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);
            }

            
            
           

相關推薦

View繪製原始碼淺析(佈局載入

前言 說到View的繪製大部分人都能說上一兩句,但細節實在太多如果沒系統的去看很難吃透。近期我專門抽了一週多的時間讀了繪製相關的原始碼,這裡準備用三篇部落格做一個系統的講述,目錄如下。 View繪製原始碼淺析(一)佈局的載入 View繪製原始碼淺析(二)佈局的測量、

View繪製原始碼淺析(二佈局的測量、佈局繪製

前言 在第一篇View繪製原始碼淺析(一)佈局的載入我們知道了setContentView()完成了DecorView的建立,並且將xml中的佈局標籤轉換成了對應的View、屬性轉換成了對應的LayoutParams然後新增到了id為content的佈局上,也就是說完成了佈局物件的建

Android原始碼解析(二十-->PopupWindow載入繪製流程

在前面的幾篇文章中我們分析了Activity與Dialog的載入繪製流程,取消繪製流程,相信大家對Android系統的視窗繪製機制有了一個感性的認識了,這篇文章我們將繼續分析一下PopupWindow載入繪製流程。 在分析PopupWindow之前,我們將

laravel框架原始碼分析(自動載入

一、前言   使用php已有好幾年,laravel的使用也是有好長時間,但是一直對於框架原始碼的理解不深,原因很多,歸根到底還是php基礎不紮實,所以原始碼看起來也比較吃力。最近有時間,所以開啟第5、6遍的框架原始碼探索之旅,前面幾次都是看了一些就放棄,希望這次能夠看完。每一次看原始碼都會有新的收穫,因為框

Log4j2原始碼分析系列:(配置載入

前言 在實際開發專案中,日誌永遠是一個繞不開的話題。本系列文章試圖以slf4j和log4j2日誌體系為例,從原始碼角度分析日誌工作原理。 學習日誌框架,首先要熟悉各類日誌框架,這裡推薦兩篇文章,就不再贅述了。 https://www.cnblogs.com/rjzheng/p/10042911.

精盡 MyBatis 原始碼分析 - MyBatis 初始化(載入 mybatis-config.xml

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

《深入理解java虛擬機器》讀書筆記(---- 類載入機制

類載入的時機 1、類從虛擬機器載入到記憶體開始,到卸載出記憶體為止,整個生命週期分為七個階段:載入、驗證、準備、解析、初始化、使用和解除安裝。其中驗證、準備和解析統稱為連線階段。 2、載入、驗證、準備、初始化和解除安裝這五個階段是按順序執行的,而解析階段卻不一定,解析可以在初始化之後

ReentrantLock和condition原始碼淺析()

 轉載請註明出處。。。。。 一、介紹 大家都知道,在java中如果要對一段程式碼做執行緒安全操作,都用到了鎖,當然鎖的實現很多,用的比較多的是sysnchronize和reentrantLock,前者是java裡的一個關鍵字,後者是一個java類。這兩者的大致區別,在這裡羅列下 相同點: &

白話Spring原始碼:怎麼閱讀原始碼

跟大家分享Spring原始碼前我想先聊聊: 為什麼要閱讀原始碼? 怎麼閱讀原始碼? 希望大家在學習某個新的知識前多問幾個為什麼,好奇心是我們學習的一大動力。 一、為什麼要閱讀原始碼 剛入行時,我們會接觸很多框架:spirng,Struts,Hibernate,mybatis等等,

MFC/Qt下呼叫caffe原始碼---將caffe原始碼生成動態連結庫dll

本人研一,最近想將用caffe訓出的模型,通過MFC做出一個介面,扔進一張圖片,點選預測,即可呼叫預測分類函式完成測試,並且通過MessageBox彈出最終分類的資訊。 首先通過查資料總結出兩種方法,第一:直接呼叫編譯好的caffe原始碼;(本次用到的原始碼是classif

Spring5原始碼淺析()--BeanFactory

       Spring廣受歡迎的兩大特性是IOC和AOP。IOC是Spring所提供的容器技術,主要用來維護系統中的元件的例項化.而容器中最基本的就是BeanFactory.BeanFactory的結構如下所示:      

Java虛擬機器(載入

1、類載入機制 虛擬機器把類的資料從class載入到記憶體,並對資料進行校驗、解析和初始化,最終形成虛擬機器可以直接使用的Java型別,即是虛擬機器的類載入機制。 類載入器並不需要等到某個類被“首次主動使用”時再載入它,JVM規範允許類載入器在預料某個類將要被使用時就預先載入它,如果在預先載

分析開源oschina客戶端的原始碼

由於近來要做一個個性化新聞推送的客戶端。便來學習學習oschina的設計。 目前開源了1.7的版本。 一,目錄結構和引用的jar: 主要來分析目錄src的分類: 1.前面四項,com.barcode.* 都是關於掃描二維碼的程式碼,是之後整合的。  2.com.weib

精講 DispatchServlet原始碼解析(OnRefresh初始化

1.  固定特殊bean的id /** 用於檔案上傳解析 */ public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; /** 用於區域解析 */ pub

JavaScript DOM(--頁面載入完成事件與獲取元素節點

window.onload與$(document).ready()比較 比較項\js or jQuery window.onload $(document).ready()

深入Preact原始碼jsx要轉化成virtualDOM發生了什麼

本文和自己在掘金的同步 jsx要轉化成virtualDOM,首先經過babel,再經過h函式的呼叫形成virtualDOM。具體如下 原始碼連結 ./src/h.js 相當於react得createElement(),jsx經過babel轉碼後是h的迴圈

深入理解OkHttp原始碼——提交請求

本篇文章主要介紹OkHttp執行同步和非同步請求的大體流程。主要流程如下圖: 主要分析到getResponseWidthInterceptorChain方法,該方法為具體的根據請求獲取響應部分,留著後面的部落格再介紹。 Dispatcher類

Android繪製優化(繪製效能分析

前言 一個優秀的應用不僅僅是要有吸引人的功能和互動,同時在效能上也有很高的要求。執行Android系統的手機,雖然配置在不斷的提升,但仍舊無法和PC相比,無法做到PC那樣擁有超大的記憶體以及高效能的CPU,因此在開發Android應用程式時也不可能無限制的使用

Android 自定義View--ProgressBar篇(

1、概述 1.1 目的 : 在我們的日常開發中,有很多Android UI介面上有一些特殊或者特別的控制元件與介面,是Android自帶的控制元件所不能滿足的,需要我們自己定製一些適合的控制元件來完成。 1.2 Android自定義View步驟 : 自定

curator原始碼 初始化、啟動和關閉。

Curator框架是zookeeper客戶端框架,官網有句話說的很簡潔:curator對於zookeeper就像Guava對於java。 重複策略,例項化,眾多實用的食譜選單(分散式鎖,計數器,佇列,柵欄,訊號量,路徑快取)。 初始化 1.直接呼叫