[Android FrameWork 6.0源碼學習] LayoutInflater 類分析

来源:http://www.cnblogs.com/kezhuang/archive/2017/06/10/6978783.html
-Advertisement-
Play Games

LayoutInflater是用來解析XML佈局文件,然後生成對象的ViewTree的工具類。是這個工具類的存在,才能讓我們寫起Layout來那麼省勁。 我們接下來進去刨析,看看裡邊的奧秘 我們在使用這個類的時候,通常都是像上面這樣寫,首先通過from函數獲取對象,在調用inflate方法,來生成相 ...


LayoutInflater是用來解析XML佈局文件,然後生成對象的ViewTree的工具類。是這個工具類的存在,才能讓我們寫起Layout來那麼省勁。

我們接下來進去刨析,看看裡邊的奧秘

    //調用inflate方法就可以把XML解析成View對象  
    View contentView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);  

我們在使用這個類的時候,通常都是像上面這樣寫,首先通過from函數獲取對象,在調用inflate方法,來生成相應的View Tree

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {  
            final Resources res = getContext().getResources();  
            final XmlResourceParser parser = res.getLayout(resource);  
            try {  
                return inflate(parser, root, attachToRoot);  
            } finally {  
                parser.close();  
            }  
        }  

 這個方法裡邊獲取到了我們常用的Resources資源管理類,然後通過res對象獲取了XmlResourceParser這個解析器,之後傳遞到下一個方法中去

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {  
            synchronized (mConstructorArgs) {  
                final Context inflaterContext = mContext;  
                final AttributeSet attrs = Xml.asAttributeSet(parser);  
                Context lastContext = (Context) mConstructorArgs[0];  
                mConstructorArgs[0] = inflaterContext;  
                View result = root;  
      
                try {  
                    //標準的pull解析,定義解析標記
                    int type;  
                    while ((type = parser.next()) != XmlPullParser.START_TAG &&  
                            type != XmlPullParser.END_DOCUMENT) {  
                        // Empty  
                    }  
                    //這裡是一個簡單的容錯判斷,僅僅是驗證xml是否合法  
                    if (type != XmlPullParser.START_TAG) {  
                        throw new InflateException(parser.getPositionDescription()  
                                + ": No start tag found!");  
                    }  
                    //獲取xml佈局的根標簽的名字  
                    final String name = parser.getName();  
                    //判斷標簽是否是<merge>,如果是merge就用rInflate去解析  
                    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");  
                        }  
                        //回在最後講,最後會調用他去創建view tree  
                        rInflate(parser, root, inflaterContext, attrs, false);  
                    } else {  
                        //創建View樹的跟View  
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);  
                        ViewGroup.LayoutParams params = null;  
                        
                        if (root != null) {  
                            // 獲取container中的LayoutParams參數  
                            params = root.generateLayoutParams(attrs);  
                            if (!attachToRoot) {  
                                //給根View設置預設的佈局參數  
                                //如果root傳遞null,那麼就會按照wrap_content的方式去載入  
                                temp.setLayoutParams(params);  
                            }  
                        }  
      
                        //解析xml剩下的所有的佈局,併在根View中創建View樹  
                        rInflateChildren(parser, temp, attrs, true);  
                          
                        //如果需要裝飾就直接調用addView方法,把剛剛生成的 View樹 添加到root容器中  
                        if (root != null && attachToRoot) {  
                            root.addView(temp, params);  
                        }  
      
                        //不需要裝飾就直接return  
                        if (root == null || !attachToRoot) {  
                            result = temp;  
                        }  
                    }  
      
                } catch (XmlPullParserException e) {  
                    InflateException ex = new InflateException(e.getMessage());  
                    ex.initCause(e);  
                    throw ex;  
                } catch (Exception e) {  
                    InflateException ex = new InflateException(  
                            parser.getPositionDescription()  
                                    + ": " + e.getMessage());  
                    ex.initCause(e);  
                    throw ex;  
                } finally {  
                    // Don't retain static reference on context.  
                    mConstructorArgs[0] = lastContext;  
                    mConstructorArgs[1] = null;  
                }  
                return result;  
            }  
        }

 這個方法比較長,所以我直接在代碼裡加好了註釋,這樣方便閱讀,仔細看完上邊的代碼+註釋後,我來總結一下這個函數所做的事情

為瞭解釋這個方法,我在貼一個簡單的xml佈局上來。這樣方便理解

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/test_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:gravity="center_horizontal"
        android:paddingBottom="20dp"
        android:paddingTop="20dp"
        android:text="Hello World!" />
</RelativeLayout>

 

對應上述的XML佈局來講

我們在繼續分析上邊的函數,首先創建一個根View出來(RelativeLayout),就是temp對象,之後會設置好長寬屬性

然後在通過rInflateChildren函數來遞歸解析 RelativeLayout 標簽下所有的 View/ViewGroup ,然後會按照XML里下的各個View關係來生成對應的ViewTree

我們先看一下createViewFromTag方法,看看如何根據一個Tag(RelativeLayout)創建出一個View對象的

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {  
            return createViewFromTag(parent, name, context, attrs, false);  
    }  

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,  
                boolean ignoreThemeAttr) {  
            //判斷傳進來的標簽的名字  
            if (name.equals("view")) {  
                name = attrs.getAttributeValue(null, "class");  
            }  
      
            // 設置一個主題樣式  
            if (!ignoreThemeAttr) {  
                final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);  
                final int themeResId = ta.getResourceId(0, 0);  
                if (themeResId != 0) {  
                    context = new ContextThemeWrapper(context, themeResId);  
                }  
                ta.recycle();  
            }  
            
            if (name.equals(TAG_1995)) {  
                return new BlinkLayout(context, attrs);  
            }  
      
            try {  
                View view;  
                //這裡詢問是否用自定義的工廠去創建View  
                if (mFactory2 != null) {  
                    view = mFactory2.onCreateView(parent, name, context, attrs);  
                } else if (mFactory != null) {  
                    view = mFactory.onCreateView(name, context, attrs);  
                } else {  
                    view = null;  
                }  
                //同樣也是詢問是否用自定義的工去創建View tree  
                if (view == null && mPrivateFactory != null) {  
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);  
                }  
                //如果沒有set過任何factory,那麼就是預設的創建方式  
                if (view == null) {  
                    final Object lastContext = mConstructorArgs[0];  
                    mConstructorArgs[0] = context;  
                    try {  
                        //這塊是自定義控制項還是原生控制項,原生控制項都有包名,所以用查找.去判斷  
                        //然後調用onCreateView去創建View tree  
                        //這個是一個抽象方法,具體實現在PhoneLayoutInflater類中  
                        //然後返回具體的View對象  
                        if (-1 == name.indexOf('.')) {  
                            view = onCreateView(parent, name, attrs);  
                        } else {  
                            view = createView(name, null, attrs);  
                        }  
                    } finally {  
                        mConstructorArgs[0] = lastContext;  
                    }  
                }  
      
                return view;  
            } catch (InflateException e) {  
                throw e;  
      
            } catch (ClassNotFoundException e) {  
                final InflateException ie = new InflateException(attrs.getPositionDescription()  
                        + ": Error inflating class " + name);  
                ie.initCause(e);  
                throw ie;  
      
            } catch (Exception e) {  
                final InflateException ie = new InflateException(attrs.getPositionDescription()  
                        + ": Error inflating class " + name);  
                ie.initCause(e);  
                throw ie;  
            }  
        }  

 

這個函數的也是通過一個叫createView來創建自定義控制項,用onCreateView方法來創建原生控制項,這兩個都是抽象方法,是由PhoneLayoutInflater來實現,我們先貼出創建對象的代碼。在貼出函數代碼來

//這段代碼在SystemServiceRegistry中,把服務註冊到系統中,可以看到是PhoneLayoutInflater  
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,  
                new CachedServiceFetcher<LayoutInflater>() {  
            @Override  
            public LayoutInflater createService(ContextImpl ctx) {  
                return new PhoneLayoutInflater(ctx.getOuterContext());  
}});

 

我們用Context獲取到的服務,都是通過上面的方式註冊的,我們可以看到是 new 了一個PhoneLayoutInflater的對象

       private static final String[] sClassPrefixList = {  
           "android.widget.",  
           "android.webkit.",  
           "android.app."  
       };  

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {  
      
        for (String prefix : sClassPrefixList) {  
            try {  
         //這塊又調用他父類的方法去創建View  
                View view = createView(name, prefix, attrs);  
                if (view != null) {  
                    return view;  
                }  
            } catch (ClassNotFoundException e) {  
                //....  
            }  
        }  
        //如果前幾個包都沒有滿足條件的,還有最後一手,super  
        return super.onCreateView(name, attrs);  
       }  

 

onCreateView函數先遍歷一下預先定義好的3個包,然後嘗試用createView函數去創建,如果創建不成功,最後在調用super的onCreateView去創建,所謂的super其實就是LayoutInflater這個抽象

    //我們可以看到,這個super的方法,是直接定位android.view包的  
    protected View onCreateView(String name, AttributeSet attrs)  
                throws ClassNotFoundException {  
            return createView(name, "android.view.", attrs);  
    }  

 

super的onCreateView也是通過嘗試的方式去創建,只是super中創建定義的包名是view包,在分析一下createVIew函數,就瞭解了由XML轉變成java對象的過程了

   public final View createView(String name, String prefix, AttributeSet attrs)  
                throws ClassNotFoundException, InflateException {  
             //先判斷是否已經存在實例  
            Constructor<? extends View> constructor = sConstructorMap.get(name);  
            Class<? extends View> clazz = null;  
      
            try {  
                if (constructor == null) {  
                    //這塊在for迴圈的幫助下,去查找類,並載入  
                    clazz = mContext.getClassLoader().loadClass(  
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);  
                    //這塊是一個簡單的過濾器,在載入的過程中用來過濾是否允許載入並創建對象  
                    if (mFilter != null && clazz != null) {  
                        boolean allowed = mFilter.onLoadClass(clazz);  
                        if (!allowed) {  
                            failNotAllowed(name, prefix, attrs);  
                        }  
                    }  
                    //通過反射創建獲取構造器  
                    constructor = clazz.getConstructor(mConstructorSignature);  
                    constructor.setAccessible(true);  
                    sConstructorMap.put(name, constructor);  
                } else {  
                    //同樣是對過濾器的判斷,如果不設置便是空的  
                    if (mFilter != null) {  
                        Boolean allowedState = mFilterMap.get(name);  
                        if (allowedState == null) {  
                            clazz = mContext.getClassLoader().loadClass(  
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);  
                              
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);  
                            mFilterMap.put(name, allowed);  
                            if (!allowed) {  
                                failNotAllowed(name, prefix, attrs);  
                            }  
                        } else if (allowedState.equals(Boolean.FALSE)) {  
                            failNotAllowed(name, prefix, attrs);  
                        }  
                    }  
                }  
      
                Object[] args = mConstructorArgs;  
                args[1] = attrs;//這是一個context對象  
                //創建一個View實例  
                final View view = constructor.newInstance(args);  
                //這快判斷一下是否是View存根,存根就初始化設置一下載入器  
                if (view instanceof ViewStub) {  
                    final ViewStub viewStub = (ViewStub) view;  
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));  
                }  
                //到這快就真正執行完createViewFromTag方法了,返回根View  
                return view;  
            } catch (NoSuchMethodException e) {  
                //....  
            } catch (ClassCastException e) {  
                //....  
            } catch (ClassNotFoundException e) {  
                //....  
            } catch (Exception e) {  
                //....  
            } finally {  
                //....  
            }  
        }  

 

這個函數是通過反射的方式去反射我們解析出來的Tag的構造方法,來然後再創建一個對象出來,最後通過newInstance方法,創建一個view對象出來。這樣就可以把 解析出來的TAG轉換成Java里的View對象了

上邊這個過程看完後,我們就知道LayoutInflater是怎樣把一個Tag名稱,轉變成Java對象的。

接下來我們分析一下,看看它是又如何遞歸去創建各個子View的,這個的創建過程就是rInflateChildren函數了

我們在rInflateChildren中其實也是解析到tag名稱。然後通過createViewFromTag函數來創建對象。就是我們上面分析的流程

   //調用一下rInflate方法,我貼出rInflate的源碼  
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,  
                boolean finishInflate) throws XmlPullParserException, IOException {  
            rInflate(parser, parent, parent.getContext(), attrs, finishInflate);  
        }  


    void rInflate(XmlPullParser parser, View parent, Context context,  
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {  
      
            final int depth = parser.getDepth();  
            int type;  
      
            while (((type = parser.next()) != XmlPullParser.END_TAG ||  
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {  
      
                if (type != XmlPullParser.START_TAG) {  
                    continue;  
                }  
                //獲取xml標簽名字  
                final String name = parser.getName();  
                if (TAG_REQUEST_FOCUS.equals(name)) {  
                    //調用parent的view.requestFocus方法註冊焦點  
                    parseRequestFocus(parser, parent);  
                } else if (TAG_TAG.equals(name)) {  
                    //給parent設置tag,view.setTag方法  
                    parseViewTag(parser, parent, attrs);  
                } else if (TAG_INCLUDE.equals(name)) {  
                    if (parser.getDepth() == 0) {  
                        throw new InflateException("<include /> cannot be the root element");  
                    }  
                    //解析include標簽,大概原理就是找到layout  
                    //用上邊的rInflate createViewFromTag和rInflateChildren進行view樹創建  
                    //創建好後調用parent的addView加進去  
                    parseInclude(parser, context, parent, attrs);  
                } else if (TAG_MERGE.equals(name)) {  
                    throw new InflateException("<merge /> must be the root element");  
                } else {  
                    //最後到控制項部分,各種TextView,Button,ImageView啊之類的  
                    //還是那個套路,通過createViewFromTag創建View對象  
                    //這塊是個遞歸思路  
                    final View view = createViewFromTag(parent, name, context, attrs);  
                    //獲取當前標簽的父View  
                    final ViewGroup viewGroup = (ViewGroup) parent;  
                    //獲取佈局參數  
                    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
                    //繼續遞歸,直到最底層View,然後逐步向上創建,並加到viewGroup容器中  
                    rInflateChildren(parser, view, attrs, true);  
                    viewGroup.addView(view, params);  
                }  
            //這個是View下的一個方法,載入完後會調用一下,自定義View的時候可以用到這個方法  
            //你可以重寫這個方法,來find一些View對象  
            if (finishInflate) {  
                parent.onFinishInflate();  
            }  
        }  

 

 主要實現在rInflate方法中

首先呢,獲取標簽的名字,然後在通過createViewFromTag方法去創建對象,然後繼續向下遞歸,查看是否還存在ViewGroup。知道所有view都解析完成,最終返回出去的view,就是一個完整的view tree

 

這塊就是LayoutInflater的工作原理。如果分析有錯誤或者有疑問,請在評論區指出,我會在第一時間更正或解惑,謝謝支持。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一 start命令 ionic start sdscapp --type=ionic1 ...
  • UX瀏覽服務是為了加速瀏覽網頁而開發的瀏覽服務,它解決了WebView的一系列問題,它能夠在網路差的情況下快速的瀏覽,比webview快一倍以上,是webview的優化代替方案。它擁有完善的緩存管理策略,經過優化的載入順序,廣告攔截引擎。 這次更新我們修複大量問題: 1. 緩存加速、DNS加速、弱網 ...
  • 一、在app/src/main/res下有 AndroidManifest.xml打開,打開後如下圖1 二、日誌工具log log.v() log.d() log.i() log.w() log.e() 在下圖中 中存在輸出 ...
  • 先看效果: 參照Android的實現方式用RadioButton來實現,但是Uwp的RadioButton並沒有安卓的Selector選擇器 下麵是一個比較簡單的實現,如果有同學有更好的實現,歡迎留言,讓我們共同進步。 1、首先自定義一個RadioImageButton控制項,並定義幾個依賴屬性,代碼 ...
  • 1.Android DVM(Dalvik VM)的進程和Linux的進程, 應用程式的進程是同一個概念嗎? DVM(Dalvik VM)指dalvik的虛擬機。每一個Android應用程式都在它自己的進程中運行,都擁有一個獨立的Dalvik虛擬機實例。而每一個DVM都是在Linux 中的一個進程,所 ...
  • iOS CAShapeLayer、CADisplayLink 實現波浪動畫效果 效果圖 代碼已上傳 GitHub:https://github.com/Silence GitHub/CoreAnimationDemo 可以自定義波浪高度、寬度、速度、方向、漸變速度、水的深度等參數。 實現原理 波浪的 ...
  • 作者:Antonio Leiva 時間:Jun 6, 2017 原文鏈接:https://antonioleiva.com/interfaces-kotlin/ 與Java相比,Kotlin介面允許你重用更多的代碼。 原因非常簡單:你能夠向你的介面加代碼。如果你已經試用過Java8,這非常類似。 能 ...
  • 瞭解這一章節,需要先瞭解LayoutInflater這個工具類,我以前分析過:http://www.cnblogs.com/kezhuang/p/6978783.html Window是Activity類中的一個全局變數,Window的作用是輔助Activity(也有可能是其他組件,本章拿Activ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...