更新:2017-02-13 10:18
大。3.3M
下載地址掃描二維碼安裝到手機(jī)
為安卓系統(tǒng)建立聯(lián)系是自由教育資源全世界貯藏庫(kù)被出版根據(jù)一個(gè)創(chuàng)造性的共同性歸屬執(zhí)照。保存項(xiàng)目對(duì)您的喜愛并且觀看連接使所有滿意在機(jī)器人的連接App。
1.在應(yīng)用程序視圖的教科書。
2.在圖書搜索
3.返回你在本書前面的位置
4.搜索OpenStaxCNX您需要的內(nèi)容。
5.記筆記的應(yīng)用程序,它們通過(guò)電子郵件或文本導(dǎo)出為文本文件到您的手機(jī)或共享。
一、問題
1.Xml文件是布局基礎(chǔ),但是它是怎么樣和Activity建立聯(lián)系的,作為視圖展示到手機(jī)屏幕上的?
2.findViewById()是怎樣找到對(duì)應(yīng)的Xml文件中的元素并把Xml文件中的元素展示成一個(gè)View?
3.怎樣把一個(gè)Xml文件解析成View展示出來(lái)的呢?
二、幾個(gè)關(guān)鍵的對(duì)象
DecorViewmDecor;//Thisisthetop-levelviewofthewindow,containingthewindowdecor.
ViewGroupmContentParent;//Thisistheviewinwhichthewindowcontentsareplaced.ItiseithermDecoritself,orachildofmDecorwherethecontentsGo.
ViewGroupmContentRoot;//Thisistheviewinwhichthewindowcontents
LayoutInflatermLayoutInflater;//
三、Window展示視圖的結(jié)構(gòu)
在手機(jī)上展示出來(lái)的內(nèi)容結(jié)構(gòu)是下圖中這樣的,在最外層有一個(gè)頂級(jí)容器DecorView,然后是我們的內(nèi)容的根視圖mContentRoot(ViewGroup),然后才是我們Xml或者new出來(lái)的View。
四、從源碼了解
從setContentView(layoutResID)著手,一般我們?cè)O(shè)置Activity的Layout時(shí)都是通過(guò)該方法設(shè)置對(duì)應(yīng)的layoutId,然后把layoutId對(duì)應(yīng)的Xml文件解析成我們看到的視圖界面,所以入手點(diǎn)就是我們熟知并且使用過(guò)千百遍的setContentView(layoutResID),先看一下源碼:
publicvoidsetContentView(intlayoutResID){
//Note:FEATURE_CONTENT_TRANSITIONSmaybesetintheprocessofinstallingthewindow
//decor,whenthemeattributesandthelikearecrystalized.Donotcheckthefeature
//beforethishappens.
if(mContentParent==null){//step1
installDecor();
}elseif(!hasFeature(FEATURE_CONTENT_TRANSITIONS)){
mContentParent.removeAllViews();
}
if(hasFeature(FEATURE_CONTENT_TRANSITIONS)){
finalScenenewScene=Scene.getSceneForLayout(mContentParent,layoutResID,
getContext());
transitionTo(newScene);
}else{
mLayoutInflater.inflate(layoutResID,mContentParent);//step2
}
……
}
這段代碼就是本文的入口,從step1開始分析,先判斷mContentParent是否是空的,如果是空的則執(zhí)行installDecor(),初始化后第一次打開頁(yè)面mContentParent肯定是空的,所以執(zhí)行installDecor()方法,先不用管對(duì)應(yīng)的elseif判斷條件中的內(nèi)容,這不是我們要了解的重點(diǎn),那么接下來(lái)看一下installDecor()是干什么的呢:
privatevoidinstallDecor(){
if(mDecor==null){
mDecor=generateDecor();
……
}
if(mContentParent==null){
mContentParent=generateLayout(mDecor);
……
}
}
這個(gè)方法的比較長(zhǎng),大部分是和本文的主題不相關(guān)的,關(guān)鍵的也就那么幾行,去掉不重要的代碼讓我們的思路更清晰。只要找準(zhǔn)這幾個(gè)關(guān)鍵的地方就可以明白這個(gè)所表達(dá)的真正含義了,其它的都是附屬品。從方法名的字面意思可以看出這個(gè)方法的目的就是install展示內(nèi)容的Decor(DecorView),這就是我們?cè)谀夸浂刑岬降年P(guān)鍵對(duì)象之一,這個(gè)對(duì)象是做什么的呢,它就是手機(jī)上看到的應(yīng)用視圖的頂級(jí)View,所有的在手機(jī)上呈現(xiàn)出來(lái)的view的頂級(jí)容器,它繼承自FrameLayout,每一個(gè)打開的手機(jī)窗口首先都是有一個(gè)頂級(jí)的容器來(lái)裝載我們要展示的內(nèi)容。從第一個(gè)if語(yǔ)句開始,如果mDecor是null則mDecor=generateDecor(),generateDecor()的目的是生成一個(gè)沒有feature的DecorView。再看第二個(gè)if語(yǔ)句,它的目的是生成mContentParent,也是目錄二中提到的關(guān)鍵對(duì)象之一(它是這是窗口內(nèi)容被放置的視圖,它可以是mDecor本身,也可以是一個(gè)子mDecor的內(nèi)容,這里就要視情況而論了,當(dāng)作為子view(inflate一個(gè)view的時(shí)候)就是mDecor本身),這個(gè)方法的內(nèi)容也是非常多,關(guān)鍵的內(nèi)容也還是那么幾行,其它的都是針對(duì)設(shè)置的feature做相應(yīng)的配置信息,例如,actionbar、floatWindow等。
/**
*TheIDthatthemainlayoutintheXMLlayoutfileshouldhave.
*/
publicstaticfinalintID_ANDROID_CONTENT=com.android.internal.R.id.content;
protectedViewGroupgenerateLayout(DecorViewdecor){
……
mDecor.startChanging();
……
ViewGroupcontentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
//ID_ANDROID_CONTENT是com.android.internal.R.id.content,這就是內(nèi)容展示的主要view
……
returncontentParent;
}
這樣1千多行的代碼就被很好的分解了,得到希望看到的內(nèi)容,剔除掉和目的不相關(guān)的干擾項(xiàng)剩下的就是真相。這個(gè)過(guò)程大體就可以清楚了,通過(guò)findViewById找到google定義的一個(gè)內(nèi)部view,賦給contentParent作為返回內(nèi)容,然后再回到setContentView(layoutResID)中看關(guān)鍵代碼:
if(hasFeature(FEATURE_CONTENT_TRANSITIONS)){
finalScenenewScene=Scene.getSceneForLayout(mContentParent,layoutResID,
getContext());
transitionTo(newScene);
}else{
mLayoutInflater.inflate(layoutResID,mContentParent);//這是重點(diǎn)
}
繞了半天終于用到了我們最關(guān)心的一個(gè)變量layoutResID,這一句代碼是不是很熟悉,在使用listview的時(shí)候,getView中常會(huì)用到的或者使用fragment時(shí)常用到的,當(dāng)然還有很多地方我們都會(huì)用到,例如,here。
接下來(lái),查看方法inflate:
publicViewinflate(@LayoutResintresource,@NullableViewGrouproot){
returninflate(resource,root,root!=null);
}
然后再進(jìn)入到方法inflate(resource,root,root!=null):
publicViewinflate(@LayoutResintresource,@NullableViewGrouproot,booleanattachToRoot){
finalResourcesres=getContext().getResources();
……
finalXmlResourceParserparser=res.getLayout(resource);
try{
returninflate(parser,root,attachToRoot);
}finally{
parser.close();
}
}
現(xiàn)在離我們的目的已經(jīng)不遠(yuǎn)了,其實(shí)已經(jīng)很明了了,就是通過(guò)一個(gè)Xml解析器解析我們的Xml文件,然后返回解析后的View,我們繼續(xù)往下看:
publicViewinflate(XmlPullParserparser,@NullableViewGrouproot,booleanattachToRoot){
synchronized(mConstructorArgs){
Trace.traceBegin(Trace.TRACE_TAG_VIEW,"inflate");//記錄解析日志
finalContextinflaterContext=mContext;
finalAttributeSetattrs=Xml.asAttributeSet(parser);//通過(guò)parser中得到layout中的所有view的屬性集保存在attrs中
ContextlastContext=(Context)mConstructorArgs[0];
mConstructorArgs[0]=inflaterContext;
Viewresult=root;
try{
//Lookfortherootnode.
……
finalStringname=parser.getName();//得到layout的節(jié)點(diǎn)name,例如,view、merge、include等
……
if(TAG_MERGE.equals(name)){//這里忽略,先不研究merge
if(root==null||!attachToRoot){
thrownewInflateException("<merge/>canbeusedonlywithavalid"
+"ViewGrouprootandattachToRoot=true");
}
rInflate(parser,root,inflaterContext,attrs,false);
}else{//忽略merge后的入口entrence
//Tempistherootviewthatwasfoundinthexml
finalViewtemp=createViewFromTag(root,name,inflaterContext,attrs);
ViewGroup.LayoutParamsparams=null;
if(root!=null){
//Createlayoutparamsthatmatchroot,ifsupplied
params=root.generateLayoutParams(attrs);
if(!attachToRoot){//note1
//Setthelayoutparamsfortempifwearenot
//attaching.(Ifweare,weuseaddView,below)
temp.setLayoutParams(params);
}
}
//Inflateallchildrenundertempagainstitscontext.
rInflateChildren(parser,temp,attrs,true);
//Wearesupposedtoattachalltheviewswefound(inttemp)
//toroot.Dothatnow.
if(root!=null&&attachToRoot){
root.addView(temp,params);
}
//Decidewhethertoreturntherootthatwaspassedinorthe
//topviewfoundinxml.
if(root==null||!attachToRoot){
result=temp;
}
}
}catch(XmlPullParserExceptione){
……
}catch(Exceptione){
……
}finally{
……
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
returnresult;
}
}
在這個(gè)方法中,通過(guò)resource在parser解析出layout中所有元素的屬性然后放在變量attrs中,然后在上述代碼紅色標(biāo)記的entrence處調(diào)用createViewFromTag方法根據(jù)attrs屬性集中的屬性創(chuàng)建出對(duì)應(yīng)的view,到這兒,基本上已經(jīng)可以大概知道view創(chuàng)建的流程,為了更詳細(xì)的去了解過(guò)程,我們有必要看剩下最后一個(gè)關(guān)鍵的方法createViewFromTag(root,name,inflaterContext,attrs):
ViewcreateViewFromTag(Viewparent,Stringname,Contextcontext,AttributeSetattrs,
booleanignoreThemeAttr){
……
Viewview;
if(mFactory2!=null){
view=mFactory2.onCreateView(parent,name,context,attrs);
}elseif(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){
finalObjectlastContext=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;
}
}
returnview;
……
}
這里就是把從Xml中解析出來(lái)的內(nèi)容根據(jù)變量name生成對(duì)應(yīng)的View對(duì)象,其實(shí)后面的實(shí)現(xiàn)不用看源碼也可以想到了,用反射生成對(duì)應(yīng)的View對(duì)象,然后一級(jí)一級(jí)的向來(lái)時(shí)的路返回給調(diào)用方法。但是為了證實(shí)我們的猜測(cè)還是要仔細(xì)的研究一番,先看第一個(gè)if語(yǔ)句,很簡(jiǎn)單,就是通過(guò)工廠去創(chuàng)建View,Activity實(shí)現(xiàn)了接口Factory2,在Activity源碼中可以看到具體實(shí)現(xiàn),進(jìn)入Activity查看源碼:
Factory2:
publicViewonCreateView(Viewparent,Stringname,Contextcontext,AttributeSetattrs){
if(!"fragment".equals(name)){
returnonCreateView(name,context,attrs);
}
returnmFragments.onCreateView(parent,name,context,attrs);
}
Factory:
publicViewonCreateView(Stringname,Contextcontext,AttributeSetattrs){
returnnull;
}
可以看到如果我們沒有使用fragment,則最后返回的都是null,那么再回到createViewFromTag方法中繼續(xù)看下面的代碼,mPrivateFactory也是Factory2的一個(gè)對(duì)象,所以還是一樣的看Activity中代碼,得出同樣的結(jié)果返回null,這樣的話,真正創(chuàng)建View的代碼就是通過(guò)第三個(gè)if語(yǔ)句實(shí)現(xiàn)的,找到關(guān)鍵地方try包裹的代碼,view=onCreateView(parent,name,attrs)和view=createView(name,null,attrs)兩個(gè)方法最終實(shí)現(xiàn)都會(huì)調(diào)用createView(Stringname,Stringprefix,AttributeSetattrs),這樣我們就可以找到源頭了:
publicfinalViewcreateView(Stringname,Stringprefix,AttributeSetattrs)
throwsClassNotFoundException,InflateException{
Constructor<?extendsView>constructor=sConstructorMap.get(name);
Class<?extendsView>clazz=null;
try{
Trace.traceBegin(Trace.TRACE_TAG_VIEW,name);
if(constructor==null){
//Classnotfoundinthecache,seeifit'sreal,andtrytoaddit
clazz=mContext.getClassLoader().loadClass(
prefix!=null?(prefix+name):name).asSubclass(View.class);
if(mFilter!=null&&clazz!=null){
booleanallowed=mFilter.onLoadClass(clazz);
if(!allowed){
failNotAllowed(name,prefix,attrs);
}
}
constructor=clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//這里是view緩存
sConstructorMap.put(name,constructor);
}else{
//Ifwehaveafilter,applyittocachedconstructor
if(mFilter!=null){
//Haveweseenthisnamebefore?
BooleanallowedState=mFilterMap.get(name);
if(allowedState==null){
//Newclass--rememberwhetheritisallowed
clazz=mContext.getClassLoader().loadClass(
prefix!=null?(prefix+name):name).asSubclass(View.class);
booleanallowed=clazz!=null&&mFilter.onLoadClass(clazz);
mFilterMap.put(name,allowed);
if(!allowed){
failNotAllowed(name,prefix,attrs);
}
}elseif(allowedState.equals(Boolean.FALSE)){
failNotAllowed(name,prefix,attrs);
}
}
}
Object[]args=mConstructorArgs;
args[1]=attrs;
finalViewview=constructor.newInstance(args);
if(viewinstanceofViewStub){
//UsethesamecontextwheninflatingViewStublater.
finalViewStubviewStub=(ViewStub)view;
viewStub.setLayoutInflater(cloneInContext((Context)args[0]));
}
returnview;
……
}
這樣就證實(shí)我們的猜想,確實(shí)是通過(guò)反射來(lái)創(chuàng)建View,然后我們的任務(wù)也就完成了。需要注意的是,通過(guò)反射創(chuàng)建的View對(duì)象返回的都是View類型的對(duì)象,在使用時(shí)需要強(qiáng)制轉(zhuǎn)換?梢钥偨Y(jié)為:在activity中指定的layoutId去找到對(duì)應(yīng)的Xml文件,然后通過(guò)Xml解析生成對(duì)應(yīng)的View然后inflate到窗口頂級(jí)容器DecorView中繪制展現(xiàn)出來(lái)。
回到我們的目錄二問題中,1和3都已經(jīng)清楚了,那么問題2是什么樣的結(jié)果呢,其實(shí)這個(gè)很簡(jiǎn)單,在創(chuàng)建View的時(shí)候已經(jīng)從Xml文件中解析到完整的view屬性attrs,在使用反射創(chuàng)建view時(shí)會(huì)通過(guò)構(gòu)造函數(shù)生成對(duì)應(yīng)的對(duì)象,所以會(huì)用到View的構(gòu)造方法,在View(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr,intdefStyleRes)方法中有一句代碼mID=a.getResourceId(attr,NO_ID),這樣就可以從attribute中把解析到的ID放在mID變量中,然后在findViewByID(id)中,根據(jù)參數(shù)id返回對(duì)應(yīng)的View,需要注意的是,在findViewById時(shí)要用到findViewTraversal(@IdResintid)方法,在這里如果指定find的范圍(比如,在FrameLayout中去找),則使用ViewGroup中的findViewTraversal(@IdResintid)方法,先獲取到ViewGroup的所有子View,然后通過(guò)遍歷子View找到對(duì)應(yīng)的View并返回,查看下面代碼。
protectedViewfindViewTraversal(@IdResintid){
if(id==mID){//如果等于當(dāng)前ViewGroup的Id,則返回該ViewGroup
returnthis;
}
finalView[]where=mChildren;
finalintlen=mChildrenCount;
for(inti=0;i<len;i++){//其它情況則遍歷所有子View,返回對(duì)應(yīng)的View
Viewv=where[i];
if((v.mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE)==0){
v=v.findViewById(id);
if(v!=null){
returnv;
}
}
}
returnnull;
}
note1:記得在使用listView的時(shí)候的最后一個(gè)boolean參數(shù)時(shí),看到很多人都有使用該變量,也沒去詳細(xì)了解,只是習(xí)慣性的去使用。在note1標(biāo)記處給出了合理的解釋,大概大概意思是如果我們沒有為temp設(shè)置params則使用setLayoutParams(temp.setLayoutParams(params))方法設(shè)置,如果設(shè)置過(guò)則使用addView方法為temp添加params(root.addView(temp,params))。
1,優(yōu)化了操作
2,修復(fù)了已知bug
小編簡(jiǎn)評(píng):領(lǐng)航桌面是
下載 安卓手機(jī)系統(tǒng)管理軟件(全能工具箱) 8.1M /小編簡(jiǎn)評(píng):安卓手機(jī)系
下載 安卓手機(jī)系統(tǒng)清理軟件(Naver管家) 3.3M /小編簡(jiǎn)評(píng):安卓手機(jī)系
下載 安卓手機(jī)系統(tǒng)優(yōu)化軟件(CM Speed Booster) 1.7M /小編簡(jiǎn)評(píng):安卓手機(jī)系
下載 真正影子系統(tǒng)2008正式光盤免激活免費(fèi)版 61.5M /小編簡(jiǎn)評(píng):先安裝影子
下載 中小學(xué)成績(jī)管理系統(tǒng) 10.0M /小編簡(jiǎn)評(píng):這是一套專
下載 易通稿件管理系統(tǒng) 5.0M /小編簡(jiǎn)評(píng):易通稿件管
下載 人臉圖像合成系統(tǒng)FaceForge 7M /小編簡(jiǎn)評(píng):安裝過(guò)程是
下載 微軟錯(cuò)誤代碼表查詢系統(tǒng)(MS Windows Error Messages) 68KB /小編簡(jiǎn)評(píng):親你在使用
網(wǎng)友評(píng)論