1. 
          

          1. 新聞動(dòng)態(tài)

            五分鐘搞懂Hello World 是怎么顯示到手機上的!

            常見(jiàn)問(wèn)題 發(fā)布者:cya 2019-11-28 09:09 訪(fǎng)問(wèn)量:444

            作者: 卻把清梅嗅
            原文: https://juejin.im/post/5d5a62c0e51d4561ba48fde0


            概述


            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, parentparent.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)行整體小結。



            關(guān)鍵字: Hello World Android 晨展科技

            文章連接: http://www.gostscript.com/cjwt/622.html

            版權聲明:文章由 晨展科技 整理收集,來(lái)源于互聯(lián)網(wǎng)或者用戶(hù)投稿,如有侵權,請聯(lián)系我們,我們會(huì )立即刪除。如轉載請保留

            双腿国产亚洲精品无码不卡|国产91精品无码麻豆|97久久久久久久极品|无码人妻少妇久久中文字幕
                1.