View的創(chuàng)建過(guò)程

--視圖布局的加載
--setContentView()
--LayoutInflater.inflate()是如何解析xml的搏讶?
--createViewFromTag() 創(chuàng)建View
------自定義View的創(chuàng)建
------系統(tǒng)View的創(chuàng)建

要分析View的創(chuàng)建過(guò)程雪隧,應(yīng)該從視圖布局的加載開始分析邪驮。

視圖布局的加載

在開發(fā)中我們一般通過(guò)setContentView()加載Activity的布局善榛,通過(guò)LayoutInflater.inflate()方法加載fragment、recyclerview里adapter加載item布局等等皮胡。

而setContentView()實(shí)際上使用的就是LayoutInflater.inflate()進(jìn)行的布局加載烫罩,所以我們從setContentView()開始分析最好不過(guò)。

setContentView()

獲取一個(gè)window實(shí)例院究,實(shí)際上是調(diào)用了PhoneWindow的setContentView()方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID); //重點(diǎn)
        initWindowDecorActionBar();
}
public Window getWindow() {
    return mWindow;
}

PhoneWindow的setContentView源碼:

其實(shí)下邊就可以看出實(shí)際上setContentView()是通過(guò)LayoutInflater.inflate()進(jìn)行的布局加載洽瞬。 也就是說(shuō),實(shí)際上加載xml布局的是LayoutInflater.inflate()方法业汰。

@Override
    public void setContentView(int layoutResID) {
            //···忽略
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //···忽略
        } else {
         //重點(diǎn)
         // 調(diào)用LayoutInflater的inflate方法解析布局文件伙窃,并生成View樹,
            mLayoutInflater.inflate(layoutResID, mContentParent); 
        }
        mContentParent.requestApplyInsets(); //mContentParent是View樹的根節(jié)點(diǎn)
        
        //回調(diào)Activity的onContentChanged方法通知視圖發(fā)生改變
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

上面的mLayoutInflater是在PhoneWindow的構(gòu)造方法中被實(shí)例的样漆。

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

LayoutInflater.inflate()是如何解析xml的为障?

inflate()一共有三個(gè)重載方法,其中前兩個(gè)實(shí)際上都是調(diào)用的第三個(gè)方法放祟,在第三個(gè)方法中鳍怨,通過(guò)上下文獲取到Resource實(shí)例,再通過(guò)getLayout()方法傳入layout的布局id獲取到XmlResourceParser對(duì)象跪妥。 接著又調(diào)用了一個(gè)inflate()方法鞋喇。

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, 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); //重點(diǎn)
        try {
            return inflate(parser, root, attachToRoot); //重點(diǎn)
        } finally {
            parser.close();
        }
    }

LayoutInflater的實(shí)例實(shí)際上是通過(guò)getSystemService()創(chuàng)建的

深入到下一個(gè)inflate()方法中,首先遍歷整個(gè)XML尋找merge標(biāo)簽眉撵,如果查到進(jìn)行merge標(biāo)簽里的創(chuàng)建侦香,如果沒有則調(diào)用createViewFromTag()進(jìn)行view的創(chuàng)建落塑。 這段代碼比較長(zhǎng),詳細(xì)的信息寫在注釋里罐韩。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
             //···省略
            try {
                // 循環(huán)查找根節(jié)點(diǎn)
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                final String name = parser.getName();

                //如果查找到是merge標(biāo)簽
                //private static final String TAG_MERGE = "merge";
                if (TAG_MERGE.equals(name)) {
                    //如果是merge標(biāo)簽憾赁,根節(jié)點(diǎn)不能為空attachToRoot不能為false,否則拋出異常
                    //因?yàn)閙erge標(biāo)簽需要依附在父布局里才能使用
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //然后調(diào)用rInflate加載布局
                    rInflate(parser, root, inflaterContext, attrs, false);
                                 
                //如果不是merge標(biāo)簽
                 } else {
                    //重點(diǎn) 
                    //View是通過(guò)createViewFromTag()方法創(chuàng)建出來(lái)的
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                
                  }
                 //···省略  
            } catch (XmlPullParserException e) {
                //···省略
            } finally {
                //···省略
            }
            return result;
        }
    }

createViewFromTag() 創(chuàng)建View

我們知道了View是createViewFromTag() 創(chuàng)建的散吵,那么看里邊的實(shí)現(xiàn)龙考,具體寫在注釋里。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
    

        //首先會(huì)使用mFactory2矾睦,mFactory晦款,mPrivateFactory這三個(gè)對(duì)象按先后順序創(chuàng)建view。
        //如果這三個(gè)對(duì)象都為空的話顷锰,則會(huì)默認(rèn)流程來(lái)創(chuàng)建View柬赐,最后返回View。
        //通常來(lái)講這三個(gè)Factory都為空官紫,如果我們想要控制View的創(chuàng)建過(guò)程就可以利用這一機(jī)制來(lái)定制自己的factory。  
        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);
            }
            
            //判斷名字中是否有"." ,這主要是為了區(qū)分系統(tǒng)自帶View和自定義View州藕。
            //因?yàn)橄到y(tǒng)View是直接使用類名不用寫全包名的束世,而自定義View在使用的時(shí)候一定要寫全包名
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //如果是自定義View則調(diào)用createView來(lái)創(chuàng)建View,否則調(diào)用onCreateView方法床玻。
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
}

從上面可以看出毁涉,mFactory2,mFactory其實(shí)都是可以hook的點(diǎn)锈死,通過(guò)這里進(jìn)行攔截贫堰,添加我們想要進(jìn)行的操作。 而view的實(shí)際創(chuàng)建待牵,系統(tǒng)進(jìn)行判斷其屏,是自定義view還是系統(tǒng)自帶view,通過(guò)是否有包名去判斷缨该。 我們?cè)偕钊肟?createView()方法偎行。

自定義View的創(chuàng)建

從下面代碼可以看出每個(gè)View都是通過(guò)反射進(jìn)行創(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 {
                ···
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            //view的創(chuàng)建
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {   
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } catch (NoSuchMethodException e) {
            ···
        } finally {
            ···
        }
    }

系統(tǒng)View的創(chuàng)建

在LayoutInflater中我們找到了onCreateView()方法

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

onCreateView方法創(chuàng)建系統(tǒng)View最終的實(shí)現(xiàn)也是交給了createView方法贰拿,只是傳入了一個(gè)字符串android.view.蛤袒,這樣在創(chuàng)建構(gòu)造器時(shí)就會(huì)與View的名字拼接到一起獲取對(duì)應(yīng)的Class對(duì)象,使最終能夠成功創(chuàng)建對(duì)應(yīng)的View膨更。

在PhoneLayoutInflater里找到了createView()方法妙真。

主要完成的是遍歷一個(gè)存放了三個(gè)包名字符串的數(shù)組,然后調(diào)用createView方法創(chuàng)建View荚守,只要這三次創(chuàng)建View有一次成功珍德,那么就返回創(chuàng)建的View练般,否則最終返回的還是父類傳入"android.view."時(shí)創(chuàng)建的View。

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 {
                //重點(diǎn)
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) { 
            }
        }

        return super.onCreateView(name, attrs);
    }

createView的具體實(shí)現(xiàn):

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
            //從緩存器中獲取構(gòu)造器
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
         
            //沒有緩存的構(gòu)造器
            if (constructor == null) {
                //通過(guò)傳入的prefix構(gòu)造出完整的類名 并加載該類
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                //···省略
                
                //從class對(duì)象中獲取構(gòu)造器
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //存入緩存器中
                sConstructorMap.put(name, constructor);
            } else {
                //···省略
            }
           //···省略
          
            //這里可以看到菱阵,系統(tǒng)應(yīng)用也是通過(guò)反射創(chuàng)建View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
        } 

    }

結(jié)合前面的分析踢俄,我們可以清楚的看到view的創(chuàng)建過(guò)程。

經(jīng)歷了從 加載布局 — 到遍歷xml各個(gè)節(jié)點(diǎn) — 判斷是否系統(tǒng)view — view的創(chuàng)建 的過(guò)程晴及,這對(duì)于我們以后的開發(fā)大有幫助都办。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虑稼,隨后出現(xiàn)的幾起案子琳钉,更是在濱河造成了極大的恐慌,老刑警劉巖蛛倦,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歌懒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡溯壶,警方通過(guò)查閱死者的電腦和手機(jī)及皂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)且改,“玉大人验烧,你說(shuō)我怎么就攤上這事∮瞩耍” “怎么了碍拆?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)慨蓝。 經(jīng)常有香客問(wèn)我感混,道長(zhǎng),這世上最難降的妖魔是什么礼烈? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任弧满,我火速辦了婚禮,結(jié)果婚禮上济丘,老公的妹妹穿的比我還像新娘谱秽。我一直安慰自己,他們只是感情好摹迷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布疟赊。 她就那樣靜靜地躺著,像睡著了一般峡碉。 火紅的嫁衣襯著肌膚如雪近哟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天鲫寄,我揣著相機(jī)與錄音吉执,去河邊找鬼疯淫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛戳玫,可吹牛的內(nèi)容都是我干的熙掺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼咕宿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼币绩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起府阀,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缆镣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后试浙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體董瞻,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年田巴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钠糊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壹哺,死狀恐怖眠蚂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斗躏,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布昔脯,位于F島的核電站啄糙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏云稚。R本人自食惡果不足惜隧饼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望静陈。 院中可真熱鬧燕雁,春花似錦、人聲如沸鲸拥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刑赶。三九已至捏浊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撞叨,已是汗流浹背金踪。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工浊洞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胡岔。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓法希,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親靶瘸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苫亦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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