最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android?Xml轉(zhuǎn)換為View過程詳解

 更新時間:2023年07月07日 11:36:20   作者:載漁之洲  
這篇文章主要為大家介紹了Android?Xml轉(zhuǎn)換為View實現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

Android布局文件Xml

通過setContentView(@LayoutResint layoutResID)或者LayoutInflater.from(context).inflate(int ResID)轉(zhuǎn)換為Java對象,開發(fā)工具Android Studio 提供的預覽功能,開發(fā)過程中界面和業(yè)務可以并行開發(fā),提高了開發(fā)效率。以下分析過程是基于 Android API 25 Platform 源碼,并以setContentView()方法為入口。

Xml 轉(zhuǎn)成 Java 對象方式

1、Activity中setContentView(@LayoutResint layoutResID)方法;

該方法都會被每個繼承 android.app.Activity 的子類重載;

2、LayoutInflater.from(Context context).inflate(@LayoutResint resource, ...)。

一般使用 Activity

  • 1). android.support.v7.app.AppCompatActivity
  • 2). android.support.v4.app.FragmentActivity
  • 3). android.app.Activity
  • 4). 其他 Activity

從Activity中setContentView()方法開始

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

跟蹤getWindow()源碼

public Window getWindow() {
        return mWindow;
    }

mWindow在Activity.java中attach()方法里初始化

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        ...
        mWindow = new PhoneWindow(this, window);
        ...
    }

所以Window.java的實現(xiàn)類是PhoneWindow.java類,@hide代表 PhoneWindow 的源碼在 sdk 里面是隱藏的,查看 PhoneWindow.setContentView(layoutResID)如下:

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

從上面代碼可以發(fā)現(xiàn)如果沒有轉(zhuǎn)場動畫時,執(zhí)行的是

mLayoutInflater.inflate(layoutResID, mContentParent);

在PhoneWindow構(gòu)造函數(shù)里發(fā)現(xiàn)mLayoutInflater對象賦值代碼如下:

public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }

所以可以得出一個結(jié)論 Activity.setContentView(resId) 最終還是使用LayoutInflater.from(context).inflate(resId, ……)。

在看下其他activity android.support.v7.app.AppCompatActivityandroid.support.v4.app.FragmentActivity 發(fā)現(xiàn) android.support.v4.app.FragmentActivity 沒有重載 android.app.Activity.setContentView(resId) 但是 android.support.v7.app.AppCompatActivity 重載了

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

getDelegate()源代碼最終會調(diào)用到 android.support.v7.app.AppCompatDelegateImplV9.setContentView(resId)

@Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

因此xml 轉(zhuǎn)成 Java 對象是通過LayoutInflaterinflate()方法來完成的

LayoutInflater 對象獲取方式

關鍵字abstract,LayoutInflater是一個抽象類,不能實例化,LayoutInflater 對象獲取的方式有:

1). 在 Activity 中通過 getLayoutInflater() 獲取

2). LayoutInflater里靜態(tài)方法from(context) 獲取

3). context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 獲取

如 Activity 的 getLayoutInflater()

 /**
     * Convenience for calling
     * {@link android.view.Window#getLayoutInflater}.
     */
    @NonNull
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }

可以看出 Activity 通過 getLayoutInflater() 獲取的是 PhoneWindow 的 mLayoutInflater。

LayoutInflater.from(context)

   /**
     * Obtains the LayoutInflater from the given context.
     */
    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對象都是通過服務獲取 LayoutInflater 實例對象

跟蹤下源碼context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Context的實現(xiàn)類是ContextImpl.java,如:

   @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

跟蹤 SystemServiceRegistry.java

  /**
     * Gets a system service from a given context.
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
   /**
     * Statically registers a system service with the context.
     * This method must be called during static initialization only.
     */
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

在 SystemServiceRegistry 類,這里只注冊各種系統(tǒng)服務的處,通過 Context.LAYOUT_INFLATER_SERVICE找到注冊代碼地方,如下:

 registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
 }});

通過以上代碼發(fā)現(xiàn) LayoutInflater 的實現(xiàn)類是 PhoneLayoutInflater

LayoutInflater 讀取 Xml 文件并創(chuàng)建 View 對象,繼續(xù)跟蹤LayoutInflater.inflate()方法

1).View inflate(@LayoutRes int resource, @Nullable ViewGroup root)

2).View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 

重點看第二個方法,代碼如下:

   /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

根據(jù)以上代碼邏輯,首先通過 resource 對象把 resId 指向的 xml 文件轉(zhuǎn)換為XmlResourceParser,然后執(zhí)行inflate(parser, root, attachToRoot)方法,核心代碼如下:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            try {
                // Look for the root node.
                ...
                final String name = parser.getName();
                //分析1
                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");
                    }
                    //分析2
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //分析3
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {                   
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    //分析4
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            //異常處理部分
            return result;
        }
    }

分析

分析1:

如果 Xml 根標簽是 TAG_MERGE(即merge),則 root 不能為空, attachToRoot 為 true,在執(zhí)行rInflate(parser, root, inflaterContext, attrs, false)

分析2 rInflate()方法

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;
            }
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

rInflate(parser, root, inflaterContext, attrs, false) 總結(jié)如下

1). while遍歷該節(jié)點的子節(jié)點

2). 子節(jié)點有 "requestFocus"、"tag"、""、"include" 

3). 子節(jié)點不能是 "merge"

4). 子節(jié)點的其他情況,則是各種 View 的標簽

5). View 標簽和  "include" 標簽會創(chuàng)建 View 對象

6). 遍歷結(jié)束以后執(zhí)行 parent.onFinishInflate()

如果子節(jié)點是 include,則執(zhí)行 parseInclude() ,parseInclude() 的源碼和 inflate(parser, root, attachToRoot) 類似,都是讀取xml對應的文件,轉(zhuǎn)換成 XmlResourceParser 然后遍歷里的標簽。

createViewFromTag(parent, name, context, attrs)負責創(chuàng)建 View 對象

分析3、4

1). root 不為 null,才會讀取 xml 跟布局的 params 屬性;

2). attachToRoot 為 True ,返回的是  root 對象。否則返回的是 xml 創(chuàng)建的根標簽指定的 View

3). 調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs) 方法創(chuàng)建 View 

4). rInflateChildren()->rInflate();和 分析2一樣的

綜上所述,LayoutInflater.createViewFromTag()創(chuàng)建 View 對象,源碼如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
        // Apply a theme wrapper, if allowed and one is specified.
        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)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
        try {
            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;
            }
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
       //異常處理
       ...
    }

mFactory2、mFactorymPrivateFactory 三個對象,似乎都是可以創(chuàng)建 View , 對于android.app.Activity,這三個對象為 null 或者空實現(xiàn),創(chuàng)建 View 對象直接看如下代碼:

               final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //自定義
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }

注:如果 name屬性里面含有.表示這是一個自定義 View,系統(tǒng)自帶 View 我們可以省略類的路徑,而自定義 View 則不能省略

自定義View創(chuàng)建,核心代碼如下:

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                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 we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        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;
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        //異常處理
        ......
    }

以上代碼可以看出constructor.newInstance(args) ,通過反射創(chuàng)建 View 對象

對于 Android 內(nèi)置的各種 View 在 LayoutInflater 的實現(xiàn)類PhoneLayoutInflater中重載了onCreateView()方法

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 = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }
        return super.onCreateView(name, attrs);
    }

LayoutInflater 中的代碼如下:

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }
protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

對于系統(tǒng)內(nèi)置的 View,會依次在 View 的標簽前面加上android.widget.,android.webkit.,android.app.android.view. 然后通過反射的方法創(chuàng)建 View。

以上就是Android Xml轉(zhuǎn)換為View過程詳解的詳細內(nèi)容,更多關于Android Xml轉(zhuǎn)換View的資料請關注腳本之家其它相關文章!

相關文章

  • Android編程實現(xiàn)播放MP3功能示例

    Android編程實現(xiàn)播放MP3功能示例

    這篇文章主要介紹了Android編程實現(xiàn)播放MP3功能,結(jié)合實例形式分析了Android播放MP3功能的界面布局與功能實現(xiàn)相關操作技巧,需要的朋友可以參考下
    2017-02-02
  • Android使用TextView跑馬燈效果

    Android使用TextView跑馬燈效果

    這篇文章主要為大家詳細介紹了Android使用TextView跑馬燈效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Android如何獲取APP啟動時間

    Android如何獲取APP啟動時間

    大家好,本篇文章主要講的是Android如何獲取APP啟動時間,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • Android編程之OpenGL繪圖技巧總結(jié)

    Android編程之OpenGL繪圖技巧總結(jié)

    這篇文章主要介紹了Android編程之OpenGL繪圖技巧,結(jié)合實例形式總結(jié)分析了Android基于OpenGL繪圖的原理與具體步驟,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-11-11
  • Kotlin學習筆記之const val與val

    Kotlin學習筆記之const val與val

    這篇文章主要給大家介紹了關于Kotlin學習筆記之const val與val的相關資料,并給大家介紹了const val和val區(qū)別以及Kotlin中var和val的區(qū)別,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧
    2018-05-05
  • android fm單體聲和立體聲的切換示例代碼

    android fm單體聲和立體聲的切換示例代碼

    切換是需要在一定的條件下滿足才會進行切換,切換的條件和電臺的信號強度RSSI、信號穩(wěn)定性CQI等等都有關系
    2013-06-06
  • Android開發(fā)基礎實現(xiàn)最簡單的視頻播放示例

    Android開發(fā)基礎實現(xiàn)最簡單的視頻播放示例

    這篇文章主要為大家介紹了Android開發(fā)基礎實現(xiàn)最簡單的視頻播放示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Android編程實現(xiàn)的微信支付功能詳解【附Demo源碼下載】

    Android編程實現(xiàn)的微信支付功能詳解【附Demo源碼下載】

    這篇文章主要介紹了Android編程實現(xiàn)的微信支付功能,結(jié)合實例形式詳細分析了Android微信支付功能的實現(xiàn)步驟與具體操作技巧,并附帶了Demo源碼供讀者下載參考,需要的朋友可以參考下
    2017-07-07
  • Android開發(fā)實現(xiàn)ListView異步加載數(shù)據(jù)的方法詳解

    Android開發(fā)實現(xiàn)ListView異步加載數(shù)據(jù)的方法詳解

    這篇文章主要介紹了Android開發(fā)實現(xiàn)ListView異步加載數(shù)據(jù)的方法,結(jié)合具體實例形式分析了Android操作ListView實現(xiàn)異步加載數(shù)據(jù)的具體步驟與相關實現(xiàn)技巧,需要的朋友可以參考下
    2017-11-11
  • Android 中讀取Excel文件實例詳解

    Android 中讀取Excel文件實例詳解

    這篇文章主要介紹了Android 中讀取Excel文件實例詳解的相關資料,需要的朋友可以參考下
    2017-04-04

最新評論

合山市| 屏东县| 乌兰浩特市| 开封县| 孝义市| 济南市| 永安市| 鄢陵县| 临西县| 延安市| 滦南县| 苍南县| 中西区| 沾化县| 庄浪县| 阜城县| 巢湖市| 龙里县| 金川县| 平原县| 海安县| 砀山县| 勐海县| 开封市| 桃园市| 南部县| 昌乐县| 平陆县| 石嘴山市| 兴海县| 仲巴县| 基隆市| 道孚县| 龙游县| 疏勒县| 永新县| 温泉县| 吐鲁番市| 孟津县| 仙桃市| 阿瓦提县|