1. 程式人生 > >Android 中LayoutInflater(佈局載入器)之原始碼篇

Android 中LayoutInflater(佈局載入器)之原始碼篇

前言

如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。

導航

概述

(1)Activity 的 getSystemService的實現過程

(2)LayoutInflater 如果將佈局資源轉換為 View 的過程

(3)LayoutInflater的 Factory,Factory2是什麼,在解析過程中的作用是什麼?

(4)LayoutInflater 的 inflater 方法的各個引數的含義,不同的情況的含義

LayoutInflater的構造方法

    protected
LayoutInflater(Context context) { mContext = context; }

這種是LayoutInflater常規的構造方法,將Context傳入,最後生成的LayoutInflater與對應的Context相繫結。

    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory
2; mPrivateFactory = original.mPrivateFactory; setFilter(original.mFilter); }

而這種構造方法來說,只是複製原LayoutInflater的內容,然後將Context物件替換,一般來說只會在cloneInContext()方法中使用。

LayoutInflater#form()方法分析

根據介紹篇的內容,LayoutInflater在Android開發中一般是通過

context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater.from(context);

因為第一種方式,已經是LayoutInflater介紹中宣告獲取的方式之一,那麼這裡我們看一下LayoutInflater#form的方法。

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

從原始碼上看,LayoutInflater#form()方法內部也是通過getSystemService()方法獲得,那麼接下來我們看一下context#getSystemService()這個方法:

    public abstract Object getSystemService(@ServiceName @NonNull String name);

發現這個只是一個抽象方法,而我們知道Activity也是Context的一個實現。

Activity#getSystemService()這個方法:

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
        //獲取WindowManager
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        //系統的搜尋框SearchManager
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

從上面看到,在Activity中只處理了兩種型別的服務,分別是獲取WindowManager、獲取SearchManager,那我們接著看其父類的SystemService()方法:

    @Override
    public Object getSystemService(String name) {
        //找到我們要的東西,注意這是個單例
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

在Activity的父類即ContextThemeWrapper的getSystemService()方法中,我們發現了LayoutInflater的建立過程,從上面的程式碼我們可以看出:

  1. 每個Activity內包含的LayoutInflater是一個單例。

  2. Activity建立LayoutInflater時,是先使用最原始的BaseContext建立,然後在將Activity的父類ContextThemeWrapper的資訊通過cloneInContext()方法與其繫結。

然後我們在看下LayoutInflater的cloneInContext的實現:

    public abstract LayoutInflater cloneInContext(Context newContext);

先看下,這個方法的介紹:

這裡寫圖片描述

  1. 這個方法通過現有的LayoutInflater建立一個新的LayoutInflater副本,唯一變化的地方是指向不同的上下文物件。

  2. 在ContextThemeWrapper通過這個方法建立的新的LayoutInflater還包含了主題的資訊。

在ContextThemeWrapper中使用cloneInContext是想將更多的資訊,賦予LayoutInflater中,與其相互繫結。

Activity中LayoutInflater建立

對於Activity的LayoutInflater,其實在Activity建立之時就已經建立完成,但是這一塊內容屬於FrameWork層的內容,博主道行太淺了,只想帶大家看下from這個方法的實現過程。

這裡如果大家想了解可以參考下這篇文章

而Activity#getLayoutInflater方法:

    @NonNull
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }

這個Window物件即PhoneWindow,此時創建出來的LayoutInflater即PhoneLayoutInflater。

這裡給大家看下PhoneLayoutInflater的cloneInContext()方法:

    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }

可以發現PhoneLayoutInflater中cloneInContext()的實現,呼叫了第二個構造方法。

這裡在Android Studio是無法查閱的,有條件的可以下載原始碼,如果下載原始碼麻煩,可以在這裡查閱。

將R.layout.xxx轉換為View的過程分析

其實這個過程即LayoutInflater.inflater()這個過程:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

在這個方法中,只是先拿到XmlResourceParser,用於後續節點的解析,我們接著往下看:

這裡只看一些關鍵的資訊,具體程式碼大家自行檢視

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

        //》》》》》》》》》》》》》》》》》第一部分》》》》》》》》》》》》》》》》》》》
            try {
                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)) {
                    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 {
         //》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

                    rInflateChildren(parser, temp, attrs, true);

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            return result;
        }
    }

第一部分:

這裡第一部分的內容,主要是一個XML檔案的讀取過程,這裡有兩個判斷:

(1)遍歷XML內容尋找XML標籤的開始的標誌或者文件結尾的標誌才可以跳出迴圈。

(2)如果該XML沒有開始的標識,則丟擲異常。

下面給大家介紹下,幾種常見的解析標識:


XmlPullParser.START_DOCUMENT                                    文件開始

XmlPullParser.END_DOCUMENT                                      文件結束

XmlPullParser.START_TAG                                         XML標籤的開始

XmlPullParser.END_TAG                                           XML標籤的結束

XmlPullParser.TEXT                                              XML標籤的內容

第二部分

這部分的一開始先進行了Merge標籤的檢驗,如果發現該節點是Merge,必須滿足父View存在,並且與父View繫結的狀態。

轉換為程式碼:

root != null && attachToRoot ==true

這裡Merge是減少佈局層級存在的標籤,通常和include標籤一起使用,所以其必須存在父View,而且merge標籤的內容必須與父View繫結。

這裡呼叫rInflate()方法去解析Merge的標籤,而rInflate()方法,在另一篇文章已經單獨分析。

第三部分

我們再看一下第三部分的程式碼,程式碼中會有一些簡要的說明:

         //》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》
                    //createViewFromTag是一個根據name來建立View的方法
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    //解析子標籤
                    rInflateChildren(parser, temp, attrs, true);

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            return result;
        }

將第三部分內容分拆一下主要分為以下幾塊內容:

  1. 排除標籤為include,或者merge之後,就會通過createViewFromTag()方法來建立View

  2. root是inflater()方法的第二個引數,而attachToRoot是第三個引數,最後會根據這兩個引數來決定返回的View

在這部分中,createViewFromTag()是根據name(名稱),來建立View的一個方法。

由於createViewFromTag()方法的通用性,這塊內容博主給單獨拿出來,連結如下:

接下來,我們要介紹的是inflater()方法中的引數,到底有什麼作用?

                    ViewGroup.LayoutParams params = null;
                    //當Root存在
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            //設定View在父佈局下Params
                            temp.setLayoutParams(params);
                        }
                    }
                    //遍歷子節點
                    rInflateChildren(parser, temp, attrs, true);

                    //如果Root存在並且attachToRoot為true,即與父View繫結
                    //這裡在解析的同時,就會將其新增至父View上
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    //如果父Viewwe為null或者沒有繫結父View都會將當前解析的View返回,否則返回父View
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

仔細分析上述程式碼,可以得出如下結論:

從這段程式碼中,得出以下幾個結論:

  1. 當root為null時,attachToRoot引數無效,而解析出的View作為一個獨立的View存在(不存在LayoutParams)。

  2. 當root不為null時,attactToRoot為false,那麼會給該View設定一個父View的約束(LayoutParams),然後將其返回。

  3. 當root不為null時,attactToRoot為true,那麼該View會被直接addView進父View,然後會將父View返回。

  4. 當root不為null的話,attactToRoot的預設值是true。

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }

上面的程式碼中,我們還少分析了一處程式碼rInflateChildren(),即解析子類:

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

可以看到這裡面解析子類呼叫了rInflate方法, 在來一次rInflate()的分析連線。

如果你之前沒看過這段程式碼,其實你會像博主之前一樣,一直在試,而不知道這段程式碼正確的含義,但是有時候原始碼會是一個很好的老師,通過它能夠得到你想要的。

流程圖

這裡寫圖片描述

相關推薦

Android LayoutInflater佈局載入原始碼

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 (1)Activity 的 getSystemService的實現過程 (2

Android LayoutInflater佈局載入實戰

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 效果 可以看出在滑動時,會出現視覺差效果。 可以看出在滑動時,物品會飄出去。

Android LayoutInflater佈局載入介紹

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 本篇作為Android 中LayoutInflater(佈局載入器)系列的介紹篇,該篇內容知識內容比較基礎,建議先看一些概述,如果感覺

Android LayoutInflater佈局載入原始碼parseInclude方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是作為Android中LayoutInflater(佈局載入器)原

Android LayoutInflater佈局載入原始碼rInflate方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是屬於Android 中LayoutInflater(佈局載入器)原始

PyQt52——調整佈局佈局管理第一個程式

我們拖拽一個UI檔案,轉為PY檔案後生成一個類Ui_MainWindow 此時,我們新建一個檔案,用來控制業務邏輯(繼承介面中的類),跟介面分開,這樣我們就完成了介面和邏輯相分離(這段程式碼使用率基本100%,牢牢記住)。 1 __author__ = "WSX" 2 import sys 3

Scrapy爬蟲入門教程七 Item Loaders專案載入

目錄 專案載入器 巢狀裝載器 開發環境: Python 3.6.0 版本 (當前最新) Scrapy 1.3.2 版本 (當前最新) 專案載入器 專案載入器提

連結資料庫報錯Communications link failure解決

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet

JavaGUI簡介、AWT概述、以及佈局管理流式佈局管理、邊界佈局管理、網格佈局管理、網格包佈局管理、卡片佈局管理

1 GUI簡介   GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位

AndroidListView上拉載入分頁功能

思路 1新增頁尾,並隱藏 2監聽滑動事件,判斷當滑到低部時,顯示頁尾,並載入資料(介面回撥到activity中載入) 3資料新增完成之後隱藏頁尾 效果圖: 專案結構: 自定義listView類LoadListView package com.zhh.android;

AndroidListView錯位佈局實現無聊向

由於某些原因,需要個錯位的頁面,在網上找不到好的例子,試著動手寫了寫。 不考慮配色的完成圖如下: 首先考慮的是,listview每一行左右都有可能縮排。 先假設一行的佈局就是ImageView,TextView,ImageView,程式碼如下: 1 <Line

WinForm,每隔一段時間(參數)調用一次函數使用定時

pre tick break switch 時間 器) chan pri args 1      System.Windows.Forms.Timer setTimer; //定義一個定時器 2 int flg = 0;

_030_Android_ Android開發SmsManager簡訊管理詳解

轉自https://blog.csdn.net/qq_37443229/article/details/80039836,感謝作者的無私分享。 Android開發之SmsManager(簡訊管理器)詳解         SmsManager是

反射Constructor、Field、Method、類載入

一:什麼是反射 在認識反射之前,首先回顧下什麼是正向處理。如果我們需要例項化一個物件,首先需要匯入這個類的包,在這個包中找這個類: package CODE.反射; import java.util.Date; public class Fan { public static

1600802047 android 第三次作業音樂播放

一、實現的功能 播放、暫停、上一首、下一首    顯示列表 二、UI介面截圖   第一首歌   第二首歌   第三首歌   第四首歌 list列表   點選播放音樂時圖片旋轉,點選上一首切換上一

一起Talk Android第一百回:Android使用自定義控制元件

各位看官們,大家好,上一回中咱們說的是Android中使用自定義佈局的例子,這一回說的例子是Android中使用自定義控制元件。閒話休提,言歸正轉。讓我們一起Talk Android吧! 看官們,我們在上一回中通過自定義佈局巧妙地實現了分隔線,不過這個分隔線中看

Android開發筆記一百六十休眠模式下的定時控制

定時器AlarmManager常常用於需要週期性處理的場合,比如鬧鐘提醒、任務輪詢等等。並且定時器來源於系統服務,即使App已經不在運行了,也能收到定時器發出的廣播而被喚醒。似此迴光返照的神技,便遭到開發者的濫用,造成使用者手機充斥著各種殺不光程序,就算通過手機安全工具一再地

Android自定義DataTimePicker日期選擇

package com.wwj.datetimepicker; import java.text.SimpleDateFormat; import java.util.Calendar; import android.app.Activity; import android.app.AlertDialog

AndroidListview--排序ListView

 ListView的A-Z字母排序和過濾搜尋功能並且實現漢字轉成拼音的功能,我們知道一般我們對聯絡人,城市列表等實現A-Z的排序,因為聯絡人和城市列表我們可以直接從資料庫中獲取他的漢字拼音,而對於一般的資料,我們怎麼實現A-Z的排序,我們需要將漢字轉換成拼音就行

Android屬性動畫 TimeInterpolator插值

OK,繼續學習屬性動畫,本篇文章是屬性動畫系列的第三篇文章了,今天來學習一下屬性動畫中的TimeInterpolator,如果你對屬性動畫還不太熟悉,可以點選下面的連結學習一下前兩篇文章的知識: 1.介紹 先說說Interpolator,在And