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的建立過程,從上面的程式碼我們可以看出:
每個Activity內包含的LayoutInflater是一個單例。
Activity建立LayoutInflater時,是先使用最原始的BaseContext建立,然後在將Activity的父類ContextThemeWrapper的資訊通過cloneInContext()方法與其繫結。
然後我們在看下LayoutInflater的cloneInContext的實現:
public abstract LayoutInflater cloneInContext(Context newContext);
先看下,這個方法的介紹:
這個方法通過現有的LayoutInflater建立一個新的LayoutInflater副本,唯一變化的地方是指向不同的上下文物件。
在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;
}
將第三部分內容分拆一下主要分為以下幾塊內容:
排除標籤為include,或者merge之後,就會通過createViewFromTag()方法來建立View
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;
}
}
仔細分析上述程式碼,可以得出如下結論:
從這段程式碼中,得出以下幾個結論:
當root為null時,attachToRoot引數無效,而解析出的View作為一個獨立的View存在(不存在LayoutParams)。
當root不為null時,attactToRoot為false,那麼會給該View設定一個父View的約束(LayoutParams),然後將其返回。
當root不為null時,attactToRoot為true,那麼該View會被直接addView進父View,然後會將父View返回。
當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(佈局載入器)原始
PyQt5(2)——調整佈局(佈局管理器)第一個程式
我們拖拽一個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
Java中GUI簡介、AWT概述、以及佈局管理器(流式佈局管理器、邊界佈局管理器、網格佈局管理器、網格包佈局管理器、卡片佈局管理器)
1 GUI簡介 GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位
Android中ListView上拉載入(分頁)功能
思路 1新增頁尾,並隱藏 2監聽滑動事件,判斷當滑到低部時,顯示頁尾,並載入資料(介面回撥到activity中載入) 3資料新增完成之後隱藏頁尾 效果圖: 專案結構: 自定義listView類LoadListView package com.zhh.android;
Android中ListView錯位佈局實現(無聊向)
由於某些原因,需要個錯位的頁面,在網上找不到好的例子,試著動手寫了寫。 不考慮配色的完成圖如下: 首先考慮的是,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
Android中Listview(七)--排序ListView
ListView的A-Z字母排序和過濾搜尋功能並且實現漢字轉成拼音的功能,我們知道一般我們對聯絡人,城市列表等實現A-Z的排序,因為聯絡人和城市列表我們可以直接從資料庫中獲取他的漢字拼音,而對於一般的資料,我們怎麼實現A-Z的排序,我們需要將漢字轉換成拼音就行
Android屬性動畫(三) TimeInterpolator(插值器)
OK,繼續學習屬性動畫,本篇文章是屬性動畫系列的第三篇文章了,今天來學習一下屬性動畫中的TimeInterpolator,如果你對屬性動畫還不太熟悉,可以點選下面的連結學習一下前兩篇文章的知識: 1.介紹 先說說Interpolator,在And