欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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 對象獲取方式

關(guān)鍵字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)容,更多關(guān)于Android Xml轉(zhuǎn)換View的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論