AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

LayoutInflater的工作原理,從解析XML布局文件到創建Java對象,再到構建View樹

來源: 責編: 時間:2024-05-07 09:07:11 160觀看
導讀LayoutInflater在Android中是一個非常重要的組件,主要負責將XML布局文件實例化為對應的View對象。LayoutInflater是一個抽象類,不能直接通過new的方式獲取其實例,需要通過Activity.getLayoutInflater()或Context.getSyst

LayoutInflater在Android中是一個非常重要的組件,主要負責將XML布局文件實例化為對應的View對象。LayoutInflater是一個抽象類,不能直接通過new的方式獲取其實例,需要通過Activity.getLayoutInflater()或Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)來獲取與當前Context已經關聯且正確配置的標準LayoutInflater。qXd28資訊網——每日最新資訊28at.com

在實際工作中,有時會根據情況在代碼中自定義控件或者加載布局文件,這就需要用到LayoutInflater。它的作用是用來獲得布局文件View對象的。例如,在BaseAdapter的getView方法中,LayoutInflater經常被用來獲取整個View并返回。qXd28資訊網——每日最新資訊28at.com

View itemView= LayoutInflater.from(context).inflate(R.layout.layout_list_item,container,false);

通過LayoutInflater.from靜態函數獲得一個LayoutInflater實例,其實是個PhoneLayoutInflater對象:qXd28資訊網——每日最新資訊28at.com

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服務

LAYOUT_INFLATER_SERVICE服務跟AMS、WMS等服務不同,完全是APP虛擬的一個服務,主要作用是:在本地為調用者創建PhoneLayoutInflater對象,ContextImpl在注冊這個“服務”的時候,將工作委托給PolicyManager,利用makeNewLayoutInflater構建LayoutInflater。qXd28資訊網——每日最新資訊28at.com

registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {    public Object createService(ContextImpl ctx) {        return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());    }});    public static LayoutInflater makeNewLayoutInflater(Context context) {    return sPolicy.makeNewLayoutInflater(context);}

PolicyManager進一步調用com.android.internal.policy.impl.Policy對象的makeNewLayoutInflater構建PhoneLayoutInflater。qXd28資訊網——每日最新資訊28at.com

private static final String POLICY_IMPL_CLASS_NAME ="com.android.internal.policy.impl.Policy";public LayoutInflater makeNewLayoutInflater(Context context) {    return new PhoneLayoutInflater(context);}

圖片圖片qXd28資訊網——每日最新資訊28at.com

構建View樹

  1. 「讀取 XML 布局文件」:LayoutInflater 讀取 XML 布局文件。XML 文件描述了 UI 的層次結構和視圖組件的屬性。
  2. 「解析 XML」:LayoutInflater 使用 XML 解析器(如 XmlPullParser)來解析 XML 文件。遍歷 XML 文件中的每一個元素(通常是一個視圖組件,如 TextView、Button 等)和屬性。
  3. 「創建 Java 對象」:LayoutInflater 使用反射來創建對應的 Java 對象。例如,如果 XML 中有一個 TextView 元素,LayoutInflater 就會創建一個 TextView 的實例。并根據 XML 中的屬性來設置實例的初始狀態。
  4. 「構建 View 樹」: 隨著 XML 的解析和 Java 對象的創建,LayoutInflater 會將這些對象組織成一個樹形結構,即 View 樹。樹形結構反映了 XML 布局文件中定義的 UI 層次結構。
  5. 「處理布局參數」: 如果 inflate 方法被調用時傳入了父 ViewGroup,并且第三個參數為 true,LayoutInflater 會自動為新創建的視圖設置布局參數(LayoutParams)。參數通常與父 ViewGroup 的類型相關,以確保視圖能夠正確地在其父容器中布局。
  6. 「返回根視圖」:LayoutInflater 返回構建好的 View 樹的根視圖。根視圖可以被添加到任何 ViewGroup 中,以顯示在 UI 上。

LayoutInflater源碼中按照上面的流程來構建View,同時添加了些特殊標簽的處理邏輯,比如merge、include、stubview等。qXd28資訊網——每日最新資訊28at.com

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

XmlResourceParser是包含了XML文件信息的一個對象,通過XmlResourceParser將TAG信息取出,遞歸創建View。qXd28資訊網——每日最新資訊28at.com

public XmlResourceParser getLayout(int id) throws NotFoundException {    return loadXmlResourceParser(id, "layout");}
XmlResourceParser loadXmlResourceParser(int id, String type)        throws NotFoundException {    synchronized (mAccessLock) {        TypedValue value = mTmpValue;        <!--獲取一個TypedValue-->        if (value == null) {            mTmpValue = value = new TypedValue();        }        <!--利用id 查詢layout,并填充TypedValue-->        getValue(id, value, true);        <!--根據布局文件的路徑,返回解析xml文件-->        if (value.type == TypedValue.TYPE_STRING) {            return loadXmlResourceParser(value.string.toString(), id,                    value.assetCookie, type);        }    }}

TypedValue是與xml定義的資源對應的值,getValue獲取對應xml資源:qXd28資訊網——每日最新資訊28at.com

public void getValue(int id, TypedValue outValue, boolean resolveRefs)        throws NotFoundException {    boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);}

mAssets是一個AssetManager對象:qXd28資訊網——每日最新資訊28at.com

final boolean getResourceValue(int ident,int density, TypedValue outValue, boolean resolveRefs) {   <!--加載資源-->    int block = loadResourceValue(ident, (short) density, outValue, resolveRefs);    if (block >= 0) {        if (outValue.type != TypedValue.TYPE_STRING) {            return true;        }        outValue.string = mStringBlocks[block].get(outValue.data);        return true;     }      return false;  }

AssetManager通過native函數加載xml文件信息:qXd28資訊網——每日最新資訊28at.com

static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident,jshort density,jobject outValue,jboolean resolve){    ...<!--獲取native AssetManager對象-->    AssetManager* am = assetManagerForJavaObject(env, clazz);    <!--獲取ResTable資源表,這里應該有緩存 不能每次都弄一次吧? 所有資源的唯一表嗎?-->    const ResTable& res(am->getResources());    Res_value value;    ResTable_config config;    uint32_t typeSpecFlags;    <!--通過ResTable獲取資源-->    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);   ...    uint32_t ref = ident;    if (resolve) {    <!--是否需要二次解析資源-->        block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);    ...    }    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;}

res.getResource并不是是每次都加載一遍,第一次加載后就能獲得單例ResTable,后面用的都是這個緩存,只不過ResTable不會緩存全部資源,對于布局、圖像資源等,緩存的都是引用,如果是真實資源的引用話,還需要通過res.resolveReference來解析真正的資源。qXd28資訊網——每日最新資訊28at.com

const ResTable* AssetManager::getResTable(bool required) const{    <!--緩存 ResTable,如果非空直接返回-->    ResTable* rt = mResources;    if (rt) {  return rt;   }   ...<!--多個apk的話,會有多個-->    const size_t N = mAssetPaths.size();    for (size_t i=0; i<N; i++) {        Asset* ass = NULL;        ResTable* sharedRes = NULL;        bool shared = true;        <!--找到Asset的路徑-->        const asset_path& ap = mAssetPaths.itemAt(i);        Asset* idmap = openIdmapLocked(ap);        <!--這里的路徑一般都不是目錄-->        if (ap.type != kFileTypeDirectory) {                   if (i == 0) {                  <!--第一個一般是框架層的系統資源,用的較多,不想每次都解析,需要緩存-->                sharedRes = const_cast<AssetManager*>(this)->mZipSet.getZipResourceTable(ap.path);            }            if (sharedRes == NULL) {                ass = const_cast<AssetManager*>(this)->mZipSet.getZipResourceTableAsset(ap.path);                if (ass == NULL) {                <!--打開resources.arsc文件-->                    ass = const_cast<AssetManager*>(this)->openNonAssetInPathLocked("resources.arsc",  Asset::ACCESS_BUFFER,  ap);                    if (ass != NULL && ass != kExcludedAsset) {                        ass = const_cast<AssetManager*>(this)->mZipSet.setZipResourceTableAsset(ap.path, ass);                    }}                if (i == 0 && ass != NULL) {                    <!--緩存第一個asset-->                    sharedRes = new ResTable();                    sharedRes->add(ass, (void*)(i+1), false, idmap);                    sharedRes = const_cast<AssetManager*>(this)->mZipSet.setZipResourceTable(ap.path, sharedRes);                } } }         ...                 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {            if (rt == NULL) {                mResources = rt = new ResTable();                updateResourceParamsLocked();            }            if (sharedRes != NULL) {                rt->add(sharedRes);            } else {                rt->add(ass, (void*)(i+1), !shared, idmap);            }  }  .. }    return rt;}

通過上面的操作,完成了resources.arsc文件的解析,獲得了一個ResTable對象,該對象包含了應用程序的全部資源信息(動態加載的先不考慮),之后就可以通過ResTable的getResource來獲得指定資源,而對于xml布局文件,這里獲得的就是一個引用,需要res.resolveReference二次解析,之后就得到了id對應的資源項。xml布局文件對應的資源項的值是一個字符串,其實是一個布局文件路徑,指向一個經過編譯的二進制格式保存的xml資源文件。有了這個Xml資源文件的路徑之后,會再次通過loadXmlResourceParser來對該Xml資源文件進行解析,從而得到布局文件解析對象XmlResourceParser。qXd28資訊網——每日最新資訊28at.com

XmlResourceParser loadXmlResourceParser(String file, int id,    int assetCookie, String type) throws NotFoundException {    if (id != 0) {        try {...                  <!--解析xml文件-->            XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);            if (block != null) {                int pos = mLastCachedXmlBlockIndex+1;                if (pos >= num) pos = 0;                mLastCachedXmlBlockIndex = pos;                XmlBlock oldBlock = mCachedXmlBlocks[pos];                if (oldBlock != null) {                    oldBlock.close();                }                <!--緩存-->                mCachedXmlBlockIds[pos] = id;                mCachedXmlBlocks[pos] = block;                <!--返回-->                return block.newParser();         ...

返回XmlResourceParser對象,進而來實例化各種View:qXd28資訊網——每日最新資訊28at.com

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {    synchronized (mConstructorArgs) {        final AttributeSet attrs = Xml.asAttributeSet(parser);        Context lastContext = (Context)mConstructorArgs[0];        mConstructorArgs[0] = mContext;        View result = root;        try {            int type;            final String name = parser.getName();            <!--Merge標簽的根布局不能直接用LayoutInflater進行inflate-->            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, attrs, false);            } else {                View temp;                if (TAG_1995.equals(name)) {                    temp = new BlinkLayout(mContext, attrs);                } else {                <!--利用tag創建View-->                    temp = createViewFromTag(root, name, attrs);                }                ViewGroup.LayoutParams params = null;                if (root != null) {                    <!--是否有container來輔助,或者添加到container中,或者輔助生成布局參數-->                    params = root.generateLayoutParams(attrs);                    if (!attachToRoot) {                        temp.setLayoutParams(params);                    }                }                <!--如果有必要,遞歸生成子View,并添加到temp容器中-->                rInflate(parser, temp, attrs, true);                    <!--是否需要添加到root的container容器總-->                if (root != null && attachToRoot) {                    root.addView(temp, params);                }                <!--如果不添加root中,返回結果就是infate出的根布局View,否則就是root根布局-->                if (root == null || !attachToRoot) {                    result = temp;                }            }        }         return result;     }}

inflate的主要作用是生成layout的根布局文件,并且根據參數看看是否需要添加container容器中,之后根據需要調用rInflate遞歸生成子View。qXd28資訊網——每日最新資訊28at.com

void rInflate(XmlPullParser parser, View parent, final 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;        }        final String name = parser.getName();        if (TAG_REQUEST_FOCUS.equals(name)) {            parseRequestFocus(parser, parent);        } else if (TAG_INCLUDE.equals(name)) {            // inclue標簽,不能用在getDepth() == 0            if (parser.getDepth() == 0) {                throw new InflateException("<include /> cannot be the root element");            }            parseInclude(parser, parent, attrs);        } else if (TAG_MERGE.equals(name)) {            <!--merge標簽必須是布局的根元素,因此merge使用方式一定是被inclue-->            throw new InflateException("<merge /> must be the root element");        } else if (TAG_1995.equals(name)) {            final View view = new BlinkLayout(mContext, attrs);            final ViewGroup viewGroup = (ViewGroup) parent;            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);            rInflate(parser, view, attrs, true);            viewGroup.addView(view, params);                        } else {            <!--創建View,如果有必要,接著遞歸-->            final View view = createViewFromTag(parent, name, attrs);            final ViewGroup viewGroup = (ViewGroup) parent;            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);            rInflate(parser, view, attrs, true);            <!--添加View-->            viewGroup.addView(view, params);        }    }    if (finishInflate) parent.onFinishInflate();}

rInflate主要作用是開啟遞歸遍歷,生成View樹,createViewFromTag的主要作用是利用反射生成View對象,最終將View數顯示到屏幕上。qXd28資訊網——每日最新資訊28at.com

qXd28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-86982-0.htmlLayoutInflater的工作原理,從解析XML布局文件到創建Java對象,再到構建View樹

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 基于Spring Boot 3.x與Flowable的順序會簽模式實踐

下一篇: @Async注解失效的 9 種場景

標簽:
  • 熱門焦點
Top