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