咨詢(xún)電話(huà)
微信掃一掃
新聞動(dòng)態(tài)
五分鐘搞懂Hello World 是怎么顯示到手機上的!
常見(jiàn)問(wèn)題 發(fā)布者:cya 2019-11-28 09:09 訪(fǎng)問(wèn)量:444
概述
Android
體系本身非常宏大,源碼中值得思考和借鑒之處眾多。以LayoutInflater
本身為例,其整個(gè)流程中除了調用inflate()
函數 填充布局功能之外,還涉及到了 應用啟動(dòng)、調用系統服務(wù)(進(jìn)程間通信)、對應組件作用域內單例管理、額外功能擴展等等一系列復雜的邏輯。
本文筆者將針對 LayoutInlater
的整個(gè)設計思路進(jìn)行描述,其整體結構如下圖:
整體思路
1、創(chuàng )建流程
顧名思義,LayoutInflater的作用就是布局填充器,其行為本質(zhì)是調用了Android本身提供的系統服務(wù)。而在A(yíng)ndroid系統的設計中,獲取系統服務(wù)的實(shí)現方式就是通過(guò)ServiceManager來(lái)取得和對應服務(wù)交互的IBinder對象,然后創(chuàng )建對應系統服務(wù)的代理。
Android應用層將系統服務(wù)注冊相關(guān)的API放在了SystemServiceRegistry類(lèi)中,而將注冊服務(wù)行為的代碼放在了ContextImpl類(lèi)中,ContextImpl類(lèi)實(shí)現了Context類(lèi)下的所有抽象方法。
Android應用層還定義了一個(gè)Context的另外一個(gè)子類(lèi):ContextWrapper,Activity、Service等組件繼承了ContextWrapper, 每個(gè)ContextWrapper的實(shí)例有且僅對應一個(gè)ContextImpl,形成一一對應的關(guān)系,該類(lèi)是 裝飾器模式的體現:保證了Context類(lèi)公共功能代碼和不同功能代碼的隔離。
此外,雖然ContextImpl類(lèi)作為Context類(lèi)公共API的實(shí)現者,LayoutInlater的獲取則交給了ContextThemeWrapper類(lèi),該類(lèi)中將LayoutInlater的獲取交給了一個(gè)成員變量,保證了單個(gè)組件 作用域內的單例。
2、布局填充流程
開(kāi)發(fā)者希望直接調用LayoutInflater#inflate()函數對布局進(jìn)行填充,該函數作用是對xml文件中標簽的解析,并根據參數決定是否直接將新創(chuàng )建的View
配置在指定的ViewGroup中。
一般來(lái)說(shuō),一個(gè)View的實(shí)例化依賴(lài)Context上下文對象和attr的屬性集,而設計者正是通過(guò)將上下文對象和屬性集作為參數,通過(guò) 反射注入到View的構造器中對View進(jìn)行創(chuàng )建。
除此之外,考慮到 性能優(yōu)化和 可擴展性,設計者為L(cháng)ayoutInflater設計了一個(gè)LayoutInflater.Factory2接口,該接口設計得非常巧妙:在xml解析過(guò)程中,開(kāi)發(fā)者可以通過(guò)配置該接口對View的創(chuàng )建過(guò)程進(jìn)行攔截:通過(guò)new的方式創(chuàng )建控件以避免大量地使用反射,亦或者 額外配置特殊標簽的解析邏輯以創(chuàng )建特殊組件(比如Fragment)。
LayoutInflater.Factory2接口在A(yíng)ndroid SDK中的應用非常普遍,AppCompatActivity和FragmentManager就是最有力的體現,LayoutInflater.inflate()方法的理解雖然重要,但筆者竊以為L(cháng)ayoutInflater.Factory2的重要性與其相比不逞多讓。
對于LayoutInflater整體不甚熟悉的開(kāi)發(fā)者而言,本小節文字描述似乎晦澀難懂,且難免有是否過(guò)度設計的疑惑,但
這些文字的本質(zhì)卻是布局填充流程整體的設計思想,讀者不應該將本文視為源碼分析,而應該將自己代入到設計的過(guò)程中。
創(chuàng )建流程
1. Context:
系統服務(wù)的提供者
上文提到,LayoutInflater作為系統服務(wù)之一,獲取方式是通過(guò)ServiceManager來(lái)取得和對應服務(wù)交互的IBinder對象,然后創(chuàng )建對應系統服務(wù)的代理。
Binder機制相關(guān)并非本文的重點(diǎn),讀者可以注意到,Android的設計者將獲取系統服務(wù)的接口交給了Context類(lèi),意味著(zhù)開(kāi)發(fā)者可以通過(guò)任意一個(gè)Context的實(shí)現類(lèi)獲取系統服務(wù),包括不限于A(yíng)ctivity、Service、Application等等:
public abstract class Context {
// 獲取系統服務(wù)
public abstract Object getSystemService(String name);
// ......
}
讀者需要理解,Context類(lèi)地職責并非只針對系統服務(wù)進(jìn)行提供,還包括諸如啟動(dòng)其它組件、獲取SharedPerferences等等,其中大部分功能對于Context的子類(lèi)而言都是公共的,因此沒(méi)有必要每個(gè)子類(lèi)都對其進(jìn)行實(shí)現。
Android設計者并沒(méi)有直接通過(guò)繼承的方式將公共業(yè)務(wù)邏輯放入Base類(lèi)供組件調用或者重寫(xiě),而是借鑒了裝飾器模式的思想:
分別定義了ContextImpl和ContextWrapper兩個(gè)子類(lèi):
2. ContextImpl:
Context的公共API實(shí)現
Context的公共API的實(shí)現都交給了ContextImpl,以獲取系統服務(wù)為例,Android應用層將系統服務(wù)注冊相關(guān)的API放在了SystemServiceRegistry類(lèi)中,而ContextImpl則是SystemServiceRegistry#getSystemService的唯一調用者:
class ContextImpl extends Context {
// 該成員即開(kāi)發(fā)者使用的`Activity`等外部組件
private Context mOuterContext;
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
}
這種設計使得系統服務(wù)的注冊(SystemServiceRegistry類(lèi))和系統服務(wù)的獲?。–ontextImpl類(lèi))在代碼中只有一處聲明和調用,大幅降低了模塊之間的耦合。
3. ContextWrapper:
Context的裝飾器
ContextWrapper則是Context的裝飾器,當組件需要獲取系統服務(wù)時(shí)交給ContextImpl成員處理,偽代碼實(shí)現如下:
// class Activity extends ContextWrapper
class ContextWrapper extends Context {
// 1.將 ContextImpl 作為成員進(jìn)行存儲
public ContextWrapper(ContextImpl base) {
mBase = base;
}
ContextImpl mBase;
// 2.系統服務(wù)的獲取統一交給了ContextImpl
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
}
ContextWrapper裝飾器的初始化如何實(shí)現呢?
每當一個(gè)ContextWrapper組件(如Activity)被創(chuàng )建時(shí),都為其創(chuàng )建一個(gè)對應的ContextImpl實(shí)例,偽代碼實(shí)現如下:
public final class ActivityThread {
// 每當`Activity`被創(chuàng )建
private Activity performLaunchActivity() {
// ....
// 1.實(shí)例化 ContextImpl
ContextImpl appContext = new ContextImpl();
// 2.將 activity 注入 ContextImpl
appContext.setOuterContext(activity);
// 3.將 ContextImpl 也注入到 activity中
activity.attach(appContext, ....);
// ....
}
}
讀者應該注意到了第3步的activity.attach(appContext, ...)函數,該函數很重要,在【布局流程】一節中會(huì )繼續引申。
4. 組件的局部單例
讀者也許注意到,對于單個(gè)Activity而言,多次調用activity.getLayoutInflater()或者LayoutInflater.from(activity),獲取到的LayoutInflater對象都是單例的——對于涉及到了跨進(jìn)程通信的系統服務(wù)而言,通過(guò)作用域內的單例模式保證以節省性能是完全可以理解的。
設計者將對應的代碼放在了ContextWrapper的子類(lèi)ContextThemeWrapper中,該類(lèi)用于方便開(kāi)發(fā)者為Activity配置自定義的主題,除此之外還通過(guò)一個(gè)成員持有了一個(gè)LayoutInflater對象:
// class Activity extends ContextThemeWrapper
public class ContextThemeWrapper extends ContextWrapper {
private Resources.Theme mTheme;
private LayoutInflater mInflater;
@Override
public Object getSystemService(String name) {
// 保證 LayoutInflater 的局部單例
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
}
而無(wú)論activity.getLayoutInflater()還是LayoutInflater.from(activity),其內部最終都執行的是ContextThemeWrapper#getSystemService(前者和PhoneWindow還有點(diǎn)關(guān)系,這個(gè)后文會(huì )提), 因此獲取到的LayoutInflater自然是同一個(gè)對象了:
public abstract class LayoutInflater {
public static LayoutInflater from(Context context) {
return (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
}布局填充流程
上一節我們提到了Activity啟動(dòng)的過(guò)程,這個(gè)過(guò)程中不可避免的要創(chuàng )建一個(gè)窗口,最終UI的布局都要展示在這個(gè)窗口上,Android中通過(guò)定義了PhoneWindow類(lèi)對這個(gè)UI的窗口進(jìn)行描述。
1. PhoneWindow:
setContentView()的真正實(shí)現
Activity將布局填充相關(guān)的邏輯委托給了PhoneWindow,Activity的setContentView()函數,其本質(zhì)是調用了PhoneWindow的setContentView()函數。
public class PhoneWindow extends Window {
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
// Activity.setContentView 實(shí)際上是調用了 PhoneWindow.setContentView()
@Override
public void setContentView(int layoutResID) {
// ...
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
讀者需要清楚,activity.getLayoutInflater()和activity.setContentView()等方法都使用到了PhoneWindow內部的LayoutInflater對象,而PhoneWindow內部對LayoutInflater的實(shí)例化,仍然是調用context.getSystemService()方法,因此和上一小節的結論并不沖突:
而無(wú)論activity.getLayoutInflater()還是LayoutInflater.from(activity),其內部最終都執行的是ContextThemeWrapper#getSystemService。
PhoneWindow 是如何實(shí)例化的呢,讀者認真思考可知,一個(gè)Activity對應一個(gè)PhoneWindow 的UI窗口,因此當Activity被創(chuàng )建時(shí),PhoneWindow就被需要被創(chuàng )建了,執行時(shí)機就在上文的 ActivityThread.performLaunchActivity()中:
public final class ActivityThread {
// 每當`Activity`被創(chuàng )建
private Activity performLaunchActivity() {
// ....
// 3.將 ContextImpl 也注入到 activity中
activity.attach(appContext, ....);
// ....
}
}
public class Activity extends ContextThemeWrapper {
final void attach(Context context, ...) {
// ...
// 初始化 PhoneWindow
// window構造方法中又通過(guò) Context 實(shí)例化了 LayoutInflater
PhoneWindow mWindow = new PhoneWindow(this, ....);
}
}
設計到這里,讀者應該對LayoutInflater的整體流程已經(jīng)有了一個(gè)初步的掌握,需要清楚的兩點(diǎn)是:
1.無(wú)論是哪種方式獲取到的LayoutInflater,都是通過(guò)ContextImpl.getSystemService()獲取的,并且在A(yíng)ctivity等組件的生命周期內保持單例;
2.即使是Activity.setContentView()函數,本質(zhì)上也還是通過(guò)LayoutInflater.inflate()函數對布局進(jìn)行解析和創(chuàng )建。
2. inflate()流程的設計和實(shí)現
從思想上來(lái)看,LayoutInflater.inflate()函數內部實(shí)現比較簡(jiǎn)單直觀(guān):
public View inflate(@LayoutRes int resource, ViewGroup root, boolean attachToRoot) {
// ...
}
對該函數的參數進(jìn)行簡(jiǎn)單歸納如下:
第一個(gè)參數代表所要加載的布局,第二個(gè)參數是ViewGroup,這個(gè)參數需要與第3個(gè)參數配合使用,attachToRoot如果為true就把布局添加到ViewGroup中;
若為false則只采用ViewGroup的LayoutParams作為測量的依據卻不直接添加到ViewGroup中。
從設計的角度上思考,該函數的設計過(guò)程中,為什么需要定義這樣的三個(gè)參數?
為什么這樣三個(gè)參數就能涵蓋我們日常開(kāi)發(fā)過(guò)程中布局填充的需求?
2.1 三個(gè)火槍手
對于第一個(gè)資源id參數而言,UI的創(chuàng )建必然依賴(lài)了布局文件資源的引用,因此這個(gè)參數無(wú)可厚非。
我們先略過(guò)第二個(gè)參數,直接思考第三個(gè)參數,為什么需要這樣一個(gè)boolean類(lèi)型的值,以決定是否將創(chuàng )建的View直接添加到指定的ViewGroup中呢,不設計這個(gè)參數是否可以?
換個(gè)角度思考,這個(gè)問(wèn)題的本質(zhì)其實(shí)是:
是否每個(gè)View的創(chuàng )建都必須立即添加在ViewGroup中?
答案當然是否定的,為了保證性能,設計者不可能讓所有的View被創(chuàng )建后都能夠立即被立即添加在ViewGroup中,這與目前Android中很多組件的設計都有沖突,比如ViewStub、RecyclerView的條目、Fragment等等。
因此,更好的方式應該是可以通過(guò)一個(gè)boolean的開(kāi)關(guān)將整個(gè)過(guò)程切分成2個(gè)
小步驟,當View生成并根據ViewGroup的布局參數生成了對應的測量依據后,開(kāi)發(fā)者可以根據需求手動(dòng)靈活配置是否立即添加到ViewGroup中——這就是第三個(gè)參數的由來(lái)。
那么ViewGroup類(lèi)型的第二個(gè)參數為什么可以為空呢?
實(shí)際開(kāi)發(fā)過(guò)程中,似乎并沒(méi)有什么場(chǎng)景在填充布局時(shí)需要使ViewGroup為空?
讀者仔細思考可以很容易得出結論,事實(shí)上該參數可空是有必要的——對于A(yíng)ctivityUI的創(chuàng )建而言,根結點(diǎn)最頂層的ViewGroup必然是沒(méi)有父控件的,這時(shí)在布局的創(chuàng )建時(shí),就必須通過(guò)將null作為第二個(gè)參數交給LayoutInlater的inflate()方法,當View被創(chuàng )建好后,將View的布局參數配置為對應屏幕的寬高:
// DecorView.onResourcesLoaded()函數
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// ...
// 創(chuàng )建最頂層的布局時(shí),需要指定父布局為null
final View root = inflater.inflate(layoutResource, null);
// 然后將寬高的布局參數都指定為 MATCH_PARENT(屏幕的寬高)
mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
}
現在我們理解了 為什么三個(gè)參數就能涵蓋開(kāi)發(fā)過(guò)程中布局填充的需求,接下來(lái)繼續思考下一個(gè)問(wèn)題,LayoutInflater是如何解析xml的。
2.2 xml解析流程
xml解析過(guò)程的思路很簡(jiǎn)單;
1. 首先根據布局文件,生成對應布局的XmlPullParser解析器對象;
2. 對于單個(gè)View的解析而言,一個(gè)View的實(shí)例化依賴(lài)Context上下文對象和attr的屬性集,而設計者正是通過(guò)將上下文對象和屬性集作為參數,通過(guò) 反射注入到View的構造器中對單個(gè)View進(jìn)行創(chuàng )建;
3. 對于整個(gè)xml文件的解析而言,整個(gè)流程依然通過(guò)典型的遞歸思想,對布局文件中的xml文件進(jìn)行遍歷解析,自底至頂對View依次進(jìn)行創(chuàng )建,最終完成了整個(gè)View樹(shù)的創(chuàng )建。
單個(gè)View的實(shí)例化實(shí)現如下,這里采用偽代碼的方式實(shí)現:
// LayoutInflater類(lèi)
public final View createView(String name, String prefix, AttributeSet attrs) {
// ...
// 1.根據View的全名稱(chēng)路徑,獲取View的Class對象
Class<? extends View> clazz = mContext.getClassLoader().loadClass(name + prefix).asSubclass(View.class);
// 2.獲取對應View的構造器
Constructor<? extends View> constructor = clazz.getConstructor(mConstructorSignature);
// 3.根據構造器,通過(guò)反射生成對應 View
args[0] = mContext;
args[1] = attrs;
final View view = constructor.newInstance(args);
return view;
}
對于整體解析流程而言,偽代碼實(shí)現如下:
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs) {
// 1.解析當前控件
while (parser.next()!= XmlPullParser.END_TAG) {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 2.解析子布局
rInflateChildren(parser, view, attrs, true);
// 所有子布局解析結束,將當前控件及布局參數添加到父布局中
viewGroup.addView(view, params);
}
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate){
// 3.子布局作為根布局,通過(guò)遞歸的方式,層級向下一層層解析
// 繼續執行 1
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
至此,一般情況下的布局填充流程到此結束,inflate()方法執行完畢,對應的布局文件解析結束,并根據參數配置決定是否直接添加在ViewGroup根布局中。
LayoutInlater的設計流程到此就結束了嗎,當然不是,更巧妙的設計還尚未登場(chǎng)。
攔截機制和解耦策略
拋出問(wèn)題
讀者需要清楚的是,到目前為止,我們的設計還遺留了2個(gè)明顯的缺陷:
1.布局的加載流程中,每一個(gè)View的實(shí)例化都依賴(lài)了Java的反射機制,這意味著(zhù)額外性能的損耗;
2.如果在xml布局中聲明了fragment標簽,會(huì )導致模塊之間極高的耦合。
什么叫做fragment標簽會(huì )導致模塊之間極高的耦合?
舉例來(lái)說(shuō),開(kāi)發(fā)者在layout文件中聲明這樣一個(gè)Fragment:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 聲明一個(gè)fragment -->
<fragment
android:id="@+id/fragment"
android:name="com.github.qingmei2.myapplication.AFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
看起來(lái)似乎沒(méi)有什么問(wèn)題,但讀者認真思考會(huì )發(fā)現,如果這是一個(gè)v4包的Fragment,是否意味著(zhù)LayoutInflater額外增加了對Fragment類(lèi)的依賴(lài),類(lèi)似這樣:
// LayoutInflater類(lèi)
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs) {
// 1.解析當前控件
while (parser.next()!= XmlPullParser.END_TAG) {
//【注意】2.如果標簽是一個(gè)Fragment,反射生成Fragment并返回
if (name == "fragment") {
Fragment fragment = clazz.newInstance();
// .....還會(huì )關(guān)聯(lián)到SupportFragmentManager、FragmentTransaction的依賴(lài)!
supportFragmentManager.beginTransaction().add(....).commit();
return;
}
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 3.解析子布局
rInflateChildren(parser, view, attrs, true);
// 所有子布局解析結束,將當前控件及布局參數添加到父布局中
viewGroup.addView(view, params);
}
}
這導致了LayoutInflater在解析fragment標簽過(guò)程中,強制依賴(lài)了很多設計者不希望的依賴(lài)(比如v4包下Fragment相關(guān)類(lèi)),繼續往下思考的話(huà),還會(huì )遇到更多的問(wèn)題,這里不再引申。
那么如何解決這樣的兩個(gè)問(wèn)題呢?
解決思路:
考慮到性能優(yōu)化和可擴展性,設計者為L(cháng)ayoutInflater設計了一個(gè)LayoutInflater.Factory接口,該接口設計得非常巧妙:在xml解析過(guò)程中,開(kāi)發(fā)者可以通過(guò)配置該接口對View的創(chuàng )建過(guò)程進(jìn)行攔截:通過(guò)new的方式創(chuàng )建控件以避免大量地使用反射,亦或者 額外配置特殊標簽的解析邏輯以創(chuàng )建特殊組件:
public abstract class LayoutInflater {
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
public void setFactory(Factory factory) {
//...
}
public void setFactory2(Factory2 factory) {
// Factory 只能被set一次
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
mFactorySet = true;
mFactory = mFactory2 = factory;
// ...
}
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
}
正如上文所說(shuō)的,Factory接口的意義是在xml解析過(guò)程中,開(kāi)發(fā)者可以通過(guò)配置該接口對View的創(chuàng )建過(guò)程進(jìn)行攔截,對于View的實(shí)例化,最終實(shí)現的偽代碼如下:
View createViewFromTag() {
View view;
// 1. 如果mFactory2不為空, 用mFactory2 攔截創(chuàng )建 View
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
// 2. 如果mFactory不為空, 用mFactory 攔截創(chuàng )建 View
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 3. 如果經(jīng)過(guò)攔截機制之后,view仍然是null,再通過(guò)系統反射的方式,對View進(jìn)行實(shí)例化
if (view == null) {
view = createView(name, null, attrs);
}
}
理解了LayoutInflater.Factory接口設計的思路,接下來(lái)一起來(lái)思考如何解決上文中提到的2個(gè)問(wèn)題。
減少反射次數
AppCompatActivity的源碼中隱晦地配置LayoutInflater.Factory減少了大量反射創(chuàng )建控件的情況——設計者的思路是,在A(yíng)ppCompatActivity的onCreate()方法中,為L(cháng)ayoutInflater對象調用了setFactory2()方法:
// AppCompatActivity類(lèi)
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
//...
}
// AppCompatDelegateImpl類(lèi)
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
}
}
配置之后,在inflate()過(guò)程中,系統的基礎控件的實(shí)例化都通過(guò)代碼攔截,并通過(guò)new的方式進(jìn)行返回:
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
// ...
// Android 基礎組件都通過(guò)new方式進(jìn)行創(chuàng )建
}
源碼也說(shuō)明了,即使開(kāi)發(fā)者在xml文件中配置的是Button,setContentView()之后,生成的控件其實(shí)是AppCompatButton, TextView或者ImageView亦然,在避免額外的性能損失的同時(shí),也保證了Android版本的向下兼容。
特殊標簽的解析策略
為什么Fragment沒(méi)有定義類(lèi)似void setContentView(R.layout.xxx)的函數對布局進(jìn)行填充,而是使用了View onCreateView()這樣的函數,讓開(kāi)發(fā)者填充并返回一個(gè)對應的View呢?
原因就在于在布局填充的過(guò)程中,Fragment最終被視為一個(gè)子控件并添加到了ViewGroup中,設計者將FragmentManagerImpl作為FragmentManager的實(shí)現類(lèi),同時(shí)實(shí)現了LayoutInflater.Factory2接口。
而在布局文件中fragment標簽解析的過(guò)程中,實(shí)際上是調用了FragmentManagerImpl.onCreateView()函數,生成了Fragment之后并將View返回,跳過(guò)了系統反射生成View相關(guān)的邏輯:
# android.support.v4.app.FragmentManager$FragmentManagerImpl
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return null;
}
// 如果標簽是`fragment`,生成Fragment,并返回Fragment的Root
return fragment.mView;
}
通過(guò)定義LayoutInflater.Factory接口,設計者將Fragment的功能抽象為一個(gè)View(雖然Fragment并不是一個(gè)View),并交給FragmentManagerImpl進(jìn)行處理,減少了模塊之間的耦合,可以說(shuō)是非常優(yōu)秀的設計。
實(shí)際上LayoutInflater.Factory接口的設計還有更多細節(比如LayoutInflater.FactoryMerger類(lèi)),篇幅原因,本文不贅述,有興趣的讀者可以研究一下。
小結
LayoutInflater整體的設計非常復雜且巧妙,從應用啟動(dòng)到進(jìn)程間通信,從組件的啟動(dòng)再到組件UI的渲染,都可以看到LayoutInflater的身影,因此非常值得認真學(xué)習一番,建議讀者參考本文開(kāi)篇的思維導圖并結合Android源碼進(jìn)行整體小結。
【推薦閱讀】
標準網(wǎng)站中訪(fǎng)問(wèn)量最大的是哪些部分?
地域分析報告中的地域與百度推廣的地域設置的關(guān)系?
關(guān)鍵字: Hello World Android 晨展科技
文章連接: http://www.gostscript.com/cjwt/622.html
版權聲明:文章由 晨展科技 整理收集,來(lái)源于互聯(lián)網(wǎng)或者用戶(hù)投稿,如有侵權,請聯(lián)系我們,我們會(huì )立即刪除。如轉載請保留
晨展解決方案
晨展新聞