從Activity#setContentView談?wù)勏到y(tǒng)如何解析xml布局文件

說在前面

本次源碼來自Android API 24 Platform

引子

我們通常都會在Activity#onCreate方法中調(diào)用Activity#setContentView响巢,傳入頁面layout,關(guān)聯(lián)Activity和布局文件。那么,Activity#setContentView到底發(fā)生了什么呢?系統(tǒng)是如何解析我們的xml文件的幌蚊?

源碼分析

Activity#setContentView

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

這里,很簡單,主要是 getWindow().setContentView(layoutResID) 這一句诗鸭。
首先, getWindow()返回了什么参滴?

Activity#getWindow

    public Window getWindow() {
        return mWindow;
    }

    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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //關(guān)鍵在這里
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }

        //代碼省略
        ...
    }

在Activity#attach中强岸,創(chuàng)建了這個(gè)window,而且是Window的子類PhoneWindow砾赔。至于attach方法什么時(shí)候調(diào)用蝌箍,不是我們這個(gè)文章要討論的。(具體什么時(shí)候調(diào)用這個(gè)方法可以查看ActivityThread#performLaunchActivity)

PhoneWindow#setContentView

    @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) {
            //創(chuàng)建并初始化DecorView暴心,并給mContentParent 賦值
            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 {
            //這里就是關(guān)鍵
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //代碼省略
        ...
    }

最后調(diào)用了mLayoutInflater.inflate方法妓盲,那mLayoutInflater是什么?

PhoneWindow#constructor

    /**
     * Constructor for main window of an activity.
     */
    public PhoneWindow(Context context, Window preservedWindow) {
        this(context);
        //代碼省略
        ...
    }

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

這里就引出了解析xml的關(guān)鍵:LayoutInflater

LayoutInflater#from

    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;
    }

從context.getSystemService得到LayoutInflater专普。
這個(gè)時(shí)候我們應(yīng)該想到悯衬,這個(gè)context到底是什么?我們查看Context代碼脆诉,發(fā)現(xiàn)Context只是一個(gè)抽象類甚亭。
PhoneWindow的構(gòu)造函數(shù)來自于Activity#attach方法贷币,這個(gè)context就是Activity。
所以我們來看看Activity#getSystemService

Activity#getSystemService

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        //因?yàn)榇舜挝覀儌魅氲膮?shù)是Context.LAYOUT_INFLATER_SERVICE亏狰,所以我們最終走到了這里
        return super.getSystemService(name);
    }

ContextThemeWrapper#getSystemService

    @Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                //是的役纹,最后,從這里返回了LayoutInflater
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

我覺得差不多已經(jīng)繞暈了暇唾,其實(shí)齐苛,PhoneWindow中的LayoutInflater#from最后在ContextThemeWrapper#getSystemService執(zhí)行了LayoutInflater.from(getBaseContext()).cloneInContext(this),而ContextThemeWrapper#getBaseContext返回的就是Context退子。這個(gè)Context來自于ContextWrapper#attachBaseContext方法浓利,這個(gè)方法又在Activity#attach中執(zhí)行。

Activity#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) {
        //就是這里了够挂。這個(gè)context是attach的參數(shù)旁仿,
        attachBaseContext(context);
    }

前面我們說過了,attach方法在ActivityThread#performLaunchActivity中調(diào)用

ActivityThread#performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       //代碼省略...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            //代碼省略...
        }
        //代碼省略...

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            //代碼省略...
            if (activity != null) {
               // activity.attach的第一 個(gè)參數(shù)Context由這個(gè)方法創(chuàng)建
                Context appContext = createBaseContextForActivity(r, activity);
            //代碼省略...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);
                }           
            }
            r.paused = true;
            //代碼省略...
            mActivities.put(r.token, r);

        } 
            //代碼省略...
        

        return activity;
    }

    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        //代碼省略
          ...
      //這個(gè)就是我們想要的孽糖,Context實(shí)際創(chuàng)建的是Context的子類ContextImpl 
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.token, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
         //代碼省略
          ...
        return baseContext;
    }

感覺真的跑偏了哇枯冈,到這里,我們知道了Context办悟,實(shí)際上是其子類ContextImpl尘奏。
那么,回到剛開始病蛉,我們知道LayoutInflater#from方法的參數(shù)是ContextImpl. 我們可以去ContextImpl查找ContextImpl#getSystemService

ContextImpl#getSystemService

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

無腦下一步

SystemServiceRegistry#getSystemService

    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    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);
    }

這個(gè)很簡單炫加,就是單例的一種,創(chuàng)建一個(gè)對象铺然,保存在靜態(tài)HashMap中俗孝,獲取單例就從這個(gè)HashMap中獲取之前設(shè)置的單例對象。
而這個(gè)SystemServiceRegistry#registerService什么時(shí)候調(diào)用呢探熔?
我們可以看到驹针,SystemServiceRegistry中的靜態(tài)代碼塊中調(diào)用了這個(gè)方法。

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

在靜態(tài)代碼塊中生成了一堆對象放到HashMap中诀艰,其中就包括我們需要的key是Context.LAYOUT_INFLATER_SERVICE的對象柬甥。

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
                if (service == null) {
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }

        public abstract T createService(ContextImpl ctx);
    }

在SystemServiceRegistry#getSystemService中,我們最終返回的是fetcher.getService(ctx)其垄,而fetcher就是剛剛new出來的對象苛蒲。
CachedServiceFetcher很簡單,CachedServiceFetcher#getService返回的對象就是CachedServiceFetcher#createService創(chuàng)建的對象绿满,在這里臂外,就是我們需要知道的PhoneLayoutInflater。
現(xiàn)在,我們臨時(shí)總結(jié)一下:

  1. LayoutInflater#from的參數(shù)context, 實(shí)際上是Context的子類ContextImpl.
  2. LayoutInflater#from方法返回的參數(shù)實(shí)際上是LayoutInflater的子類PhoneLayoutInflater
    雖然返回的是PhoneLayoutInflater漏健,但是PhoneLayoutInflater并沒有重新實(shí)現(xiàn)inflate方法嚎货。所以接下來,我們繼續(xù)看LayoutInflater#inflate.

LayoutInflater#inflate

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    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();
        }
    }
    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.
                int type;
               //遍歷xml的節(jié)點(diǎn)蔫浆,找到第一個(gè)節(jié)點(diǎn)是開始標(biāo)簽或者結(jié)束標(biāo)簽的節(jié)點(diǎn)
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                //如果找到的這個(gè)節(jié)點(diǎn)不是開始標(biāo)簽殖属,就拋出以異常
                //上面的和下面的代碼,就是為了找到xml中的第一個(gè)開始標(biāo)簽瓦盛,從這個(gè)開始標(biāo)簽開始解析節(jié)點(diǎn)
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();
               //代碼省略...
                if (TAG_MERGE.equals(name)) {
                    //如果xml的節(jié)點(diǎn)標(biāo)簽是merge洗显,那么root 不能為空,attachToRoot也不能為false原环,
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //忽略掉merge挠唆,將merge標(biāo)簽的子元素添加到root上
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //生成根節(jié)點(diǎn),也就是布局文件的根節(jié)點(diǎn)View(開始標(biāo)簽)
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                       //代碼省略...
                        // Create layout params that match root, if supplied
                        //生成該View的LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            //如果不需要添加到root嘱吗,直接設(shè)置為該View設(shè)置LayoutParams
                            temp.setLayoutParams(params);
                        }
                    }
                    //代碼省略...
                    // Inflate all children under temp against its context.
                    //遍歷開始標(biāo)簽玄组,將該標(biāo)簽的子元素添加進(jìn)temp
                    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的子元素,添加進(jìn)root中
                        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;
                    }
                }
            } 
            //代碼省略...
             finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            return result;
        }
    }

我們調(diào)用LayoutInflater#inflate的時(shí)候谒麦,調(diào)用的是兩個(gè)參數(shù)的方法巧勤,其中,root來自于PhoneWindow#generateLayout, 查看這個(gè)方法弄匕,我們可以知道,root就是id為com.android.internal.R.id.content的元素沽瞭,必然不是null迁匠,那么attachToRoot就是true了。也就是驹溃,解析xml中的元素城丧,并把xml中的開始標(biāo)簽元素作為com.android.internal.R.id.content的子元素。

LayoutInflater#rInflateChildren和android.view.LayoutInflater#rInflate

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    //該方法的作用就是豌鹤,遍歷parent的子元素亡哄,并將子元素通過addView添加進(jìn)parent中
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        //循環(huán)遍歷子元素
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                //如果不是開始標(biāo)簽,就跳過布疙。比如</LinearLayout>
                continue;
            }

            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) {
                //解析requestFocus標(biāo)簽
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                //解析tag標(biāo)簽
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //解析include標(biāo)簽
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                //merge標(biāo)簽不能作為子元素標(biāo)簽蚊惯,必須是根標(biāo)簽
                throw new InflateException("<merge /> must be the root element");
            } else {
                //創(chuàng)建當(dāng)前元素
                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();
        }
    }

通過遞歸,完成所有標(biāo)簽的遍歷灵临,并將子元素通過addView添加進(jìn)該標(biāo)簽的父控件中截型。其中,創(chuàng)建單個(gè)標(biāo)簽所表示的View也是通過LayoutInflater#createViewFromTag來實(shí)現(xiàn)儒溉。

LayoutInflater#createViewFromTag

    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) {
        //如果標(biāo)簽的名字是view宦焦,那么找到標(biāo)簽里面的class屬性,作為View的類名
        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);
            //如果有設(shè)置theme屬性,則重現(xiàn)生成context
            //ContextThemeWrapper就是ContextWrapper的子類波闹,里面多了mThemeResource這個(gè)屬性酝豪,
            //而ContextWrapper就是Context的裝飾類
            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;
            //可以設(shè)置mFactory2 和mFactory ,實(shí)現(xiàn)特殊的解析邏輯
            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是長度為2的數(shù)組精堕,數(shù)組的第一個(gè)元素是context孵淘,
                //如果沒有設(shè)置theme,這個(gè)context就是Activity中的context锄码,
                //也就是創(chuàng)建Activity的時(shí)候生成的那個(gè)ContextImpl
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        //如果節(jié)點(diǎn)標(biāo)簽的名字沒有. 比如TextView夺英,就是沒有包名,前綴
                        view = onCreateView(parent, name, attrs);
                    } else {
                       //如果節(jié)點(diǎn)標(biāo)簽有包名滋捶,比如自定義控件痛悯,比如support中的控件(android.support.v7.widget.RecyclerView)
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
        //代碼省略...
    }

該方法根據(jù)節(jié)點(diǎn)名稱創(chuàng)建節(jié)點(diǎn)View.

LayoutInflater#onCreateView

    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

直接調(diào)用了兩個(gè)參數(shù)的LayoutInflater#onCreateView,上文我們說過重窟,關(guān)于LayoutInflater我們實(shí)際使用的是PhoneLayoutInflater, 而在PhoneLayoutInflater中载萌,我們重寫了兩個(gè)參數(shù)的onCreateView方法。

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;
                }
            } 
            //代碼省略...
        }
        return super.onCreateView(name, attrs);
    }

該方法就是給name加上包名巡扇,比如TextView扭仁,我們一般寫在xml中是TextView,而不是android.widget.TextView厅翔,那么乖坠,我們通過遍歷sClassPrefixList,給TextView加上包名刀闷,去生成View控件熊泵,如果加上android.widget.,LayoutInflater#createView可以生成View甸昏,那么就停止遍歷顽分,如果沒有生成,就繼續(xù)使用android.webkit.作包名施蜜,如果三個(gè)都不行卒蘸,使用LayoutInflater#onCreateView來生成View,而LayoutInflater#onCreateView也并沒什么特殊的翻默,只不過是包名作為android.view.來生成這個(gè)View.至此缸沃,我們知道,這個(gè)PhoneLayoutInflater#onCreateView冰蘑,會逐個(gè)使用android.widget.和泌, android.webkit., android.app.祠肥, android.view. 來作為View的包名武氓,去生成View梯皿,如果View可以生成,就意味著包名正確县恕,不再嘗試后面的包名东羹。

LayoutInflater#createView

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        //如果sConstructorMap里面保存的有該類的構(gòu)造函數(shù),就直接使用
        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) {
                 //如果sConstructorMap里面沒有保存的有該類的構(gòu)造函數(shù)忠烛,就通過反射去獲取該類的構(gòu)造
                // Class not found in the cache, see if it's real, and try to add it
                //如果有前綴(包名)属提,則拼接包名和類名,如果沒有前綴美尸,直接使用name冤议,
                //通過ClassLoader,獲取類的Class對象
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
               //代碼省略...
               //通過反射师坎,獲取類的構(gòu)造函數(shù)恕酸,并保存到sConstructorMap中
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                //代碼省略
            }
            //上文已經(jīng)說過了,mConstructorArgs是長度為2的數(shù)組胯陋,數(shù)組的第一個(gè)元素是context
            Object[] args = mConstructorArgs;
            //現(xiàn)在把a(bǔ)ttrs賦值給數(shù)組的第二個(gè)元素
            args[1] = attrs;
            //通過反射蕊温,生成View對象
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                //針對ViewStub的特殊處理
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        }
        //代碼省略...
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市遏乔,隨后出現(xiàn)的幾起案子义矛,更是在濱河造成了極大的恐慌,老刑警劉巖盟萨,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉翻,死亡現(xiàn)場離奇詭異,居然都是意外死亡捻激,警方通過查閱死者的電腦和手機(jī)噪矛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铺罢,“玉大人,你說我怎么就攤上這事残炮【伦福” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵势就,是天一觀的道長泉瞻。 經(jīng)常有香客問我,道長苞冯,這世上最難降的妖魔是什么袖牙? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮舅锄,結(jié)果婚禮上鞭达,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好畴蹭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布坦仍。 她就那樣靜靜地躺著,像睡著了一般叨襟。 火紅的嫁衣襯著肌膚如雪繁扎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天糊闽,我揣著相機(jī)與錄音梳玫,去河邊找鬼。 笑死右犹,一個(gè)胖子當(dāng)著我的面吹牛提澎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播傀履,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼虱朵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钓账?” 一聲冷哼從身側(cè)響起碴犬,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梆暮,沒想到半個(gè)月后服协,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啦粹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年偿荷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠椭。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跳纳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贪嫂,到底是詐尸還是另有隱情寺庄,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布力崇,位于F島的核電站斗塘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亮靴。R本人自食惡果不足惜馍盟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茧吊。 院中可真熱鬧贞岭,春花似錦八毯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至讲婚,卻和暖如春尿孔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筹麸。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工活合, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人物赶。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓白指,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酵紫。 傳聞我的和親對象是個(gè)殘疾皇子告嘲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容