更新:2017-02-13 10:18
大小:3.3M
下載地址掃描二維碼安裝到手機
為安卓系統建立聯系是自由教育資源全世界貯藏庫被出版根據一個創造性的共同性歸屬執照。保存項目對您的喜愛并且觀看連接使所有滿意在機器人的連接App。
1.在應用程序視圖的教科書。
2.在圖書搜索
3.返回你在本書前面的位置
4.搜索OpenStaxCNX您需要的內容。
5.記筆記的應用程序,它們通過電子郵件或文本導出為文本文件到您的手機或共享。
一、問題
1.Xml文件是布局基礎,但是它是怎么樣和Activity建立聯系的,作為視圖展示到手機屏幕上的?
2.findViewById()是怎樣找到對應的Xml文件中的元素并把Xml文件中的元素展示成一個View?
3.怎樣把一個Xml文件解析成View展示出來的呢?
二、幾個關鍵的對象
DecorViewmDecor;//Thisisthetop-levelviewofthewindow,containingthewindowdecor.
ViewGroupmContentParent;//Thisistheviewinwhichthewindowcontentsareplaced.ItiseithermDecoritself,orachildofmDecorwherethecontentsGo.
ViewGroupmContentRoot;//Thisistheviewinwhichthewindowcontents
LayoutInflatermLayoutInflater;//
三、Window展示視圖的結構
在手機上展示出來的內容結構是下圖中這樣的,在最外層有一個頂級容器DecorView,然后是我們的內容的根視圖mContentRoot(ViewGroup),然后才是我們Xml或者new出來的View。
四、從源碼了解
從setContentView(layoutResID)著手,一般我們設置Activity的Layout時都是通過該方法設置對應的layoutId,然后把layoutId對應的Xml文件解析成我們看到的視圖界面,所以入手點就是我們熟知并且使用過千百遍的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是否是空的,如果是空的則執行installDecor(),初始化后第一次打開頁面mContentParent肯定是空的,所以執行installDecor()方法,先不用管對應的elseif判斷條件中的內容,這不是我們要了解的重點,那么接下來看一下installDecor()是干什么的呢:
privatevoidinstallDecor(){
if(mDecor==null){
mDecor=generateDecor();
……
}
if(mContentParent==null){
mContentParent=generateLayout(mDecor);
……
}
}
這個方法的比較長,大部分是和本文的主題不相關的,關鍵的也就那么幾行,去掉不重要的代碼讓我們的思路更清晰。只要找準這幾個關鍵的地方就可以明白這個所表達的真正含義了,其它的都是附屬品。從方法名的字面意思可以看出這個方法的目的就是install展示內容的Decor(DecorView),這就是我們在目錄二中提到的關鍵對象之一,這個對象是做什么的呢,它就是手機上看到的應用視圖的頂級View,所有的在手機上呈現出來的view的頂級容器,它繼承自FrameLayout,每一個打開的手機窗口首先都是有一個頂級的容器來裝載我們要展示的內容。從第一個if語句開始,如果mDecor是null則mDecor=generateDecor(),generateDecor()的目的是生成一個沒有feature的DecorView。再看第二個if語句,它的目的是生成mContentParent,也是目錄二中提到的關鍵對象之一(它是這是窗口內容被放置的視圖,它可以是mDecor本身,也可以是一個子mDecor的內容,這里就要視情況而論了,當作為子view(inflate一個view的時候)就是mDecor本身),這個方法的內容也是非常多,關鍵的內容也還是那么幾行,其它的都是針對設置的feature做相應的配置信息,例如,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,這就是內容展示的主要view
……
returncontentParent;
}
這樣1千多行的代碼就被很好的分解了,得到希望看到的內容,剔除掉和目的不相關的干擾項剩下的就是真相。這個過程大體就可以清楚了,通過findViewById找到google定義的一個內部view,賦給contentParent作為返回內容,然后再回到setContentView(layoutResID)中看關鍵代碼:
if(hasFeature(FEATURE_CONTENT_TRANSITIONS)){
finalScenenewScene=Scene.getSceneForLayout(mContentParent,layoutResID,
getContext());
transitionTo(newScene);
}else{
mLayoutInflater.inflate(layoutResID,mContentParent);//這是重點
}
繞了半天終于用到了我們最關心的一個變量layoutResID,這一句代碼是不是很熟悉,在使用listview的時候,getView中常會用到的或者使用fragment時常用到的,當然還有很多地方我們都會用到,例如,here。
接下來,查看方法inflate:
publicViewinflate(@LayoutResintresource,@NullableViewGrouproot){
returninflate(resource,root,root!=null);
}
然后再進入到方法inflate(resource,root,root!=null):
publicViewinflate(@LayoutResintresource,@NullableViewGrouproot,booleanattachToRoot){
finalResourcesres=getContext().getResources();
……
finalXmlResourceParserparser=res.getLayout(resource);
try{
returninflate(parser,root,attachToRoot);
}finally{
parser.close();
}
}
現在離我們的目的已經不遠了,其實已經很明了了,就是通過一個Xml解析器解析我們的Xml文件,然后返回解析后的View,我們繼續往下看:
publicViewinflate(XmlPullParserparser,@NullableViewGrouproot,booleanattachToRoot){
synchronized(mConstructorArgs){
Trace.traceBegin(Trace.TRACE_TAG_VIEW,"inflate");//記錄解析日志
finalContextinflaterContext=mContext;
finalAttributeSetattrs=Xml.asAttributeSet(parser);//通過parser中得到layout中的所有view的屬性集保存在attrs中
ContextlastContext=(Context)mConstructorArgs[0];
mConstructorArgs[0]=inflaterContext;
Viewresult=root;
try{
//Lookfortherootnode.
……
finalStringname=parser.getName();//得到layout的節點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;
}
}
在這個方法中,通過resource在parser解析出layout中所有元素的屬性然后放在變量attrs中,然后在上述代碼紅色標記的entrence處調用createViewFromTag方法根據attrs屬性集中的屬性創建出對應的view,到這兒,基本上已經可以大概知道view創建的流程,為了更詳細的去了解過程,我們有必要看剩下最后一個關鍵的方法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中解析出來的內容根據變量name生成對應的View對象,其實后面的實現不用看源碼也可以想到了,用反射生成對應的View對象,然后一級一級的向來時的路返回給調用方法。但是為了證實我們的猜測還是要仔細的研究一番,先看第一個if語句,很簡單,就是通過工廠去創建View,Activity實現了接口Factory2,在Activity源碼中可以看到具體實現,進入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方法中繼續看下面的代碼,mPrivateFactory也是Factory2的一個對象,所以還是一樣的看Activity中代碼,得出同樣的結果返回null,這樣的話,真正創建View的代碼就是通過第三個if語句實現的,找到關鍵地方try包裹的代碼,view=onCreateView(parent,name,attrs)和view=createView(name,null,attrs)兩個方法最終實現都會調用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;
……
}
這樣就證實我們的猜想,確實是通過反射來創建View,然后我們的任務也就完成了。需要注意的是,通過反射創建的View對象返回的都是View類型的對象,在使用時需要強制轉換。可以總結為:在activity中指定的layoutId去找到對應的Xml文件,然后通過Xml解析生成對應的View然后inflate到窗口頂級容器DecorView中繪制展現出來。
回到我們的目錄二問題中,1和3都已經清楚了,那么問題2是什么樣的結果呢,其實這個很簡單,在創建View的時候已經從Xml文件中解析到完整的view屬性attrs,在使用反射創建view時會通過構造函數生成對應的對象,所以會用到View的構造方法,在View(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr,intdefStyleRes)方法中有一句代碼mID=a.getResourceId(attr,NO_ID),這樣就可以從attribute中把解析到的ID放在mID變量中,然后在findViewByID(id)中,根據參數id返回對應的View,需要注意的是,在findViewById時要用到findViewTraversal(@IdResintid)方法,在這里如果指定find的范圍(比如,在FrameLayout中去找),則使用ViewGroup中的findViewTraversal(@IdResintid)方法,先獲取到ViewGroup的所有子View,然后通過遍歷子View找到對應的View并返回,查看下面代碼。
protectedViewfindViewTraversal(@IdResintid){
if(id==mID){//如果等于當前ViewGroup的Id,則返回該ViewGroup
returnthis;
}
finalView[]where=mChildren;
finalintlen=mChildrenCount;
for(inti=0;i<len;i++){//其它情況則遍歷所有子View,返回對應的View
Viewv=where[i];
if((v.mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE)==0){
v=v.findViewById(id);
if(v!=null){
returnv;
}
}
}
returnnull;
}
note1:記得在使用listView的時候的最后一個boolean參數時,看到很多人都有使用該變量,也沒去詳細了解,只是習慣性的去使用。在note1標記處給出了合理的解釋,大概大概意思是如果我們沒有為temp設置params則使用setLayoutParams(temp.setLayoutParams(params))方法設置,如果設置過則使用addView方法為temp添加params(root.addView(temp,params))。
1,優化了操作
2,修復了已知bug
小編簡評:領航桌面是
小編簡評:安卓手機系
小編簡評:安卓手機系
小編簡評:安卓手機系
小編簡評:先安裝影子
小編簡評:這是一套專
小編簡評:易通稿件管
小編簡評:安裝過程是
小編簡評:親你在使用
網友評論