Android加载View中Background详解
对大多数Android的开发者来说,最经常的操作莫过于对界面进行布局,View中背景图片的加载是最经常做的。但是我们很少关注这个过程,这篇文章主要解析view中背景图片加载的流程。了解view中背景图片的加载(资源的加载)可以让我们对资源加载的过程进行一些优化,另外当需要进行整个应用的换肤时,也可以更得心应手。
View图片的加载,我们最常见的就是通过在XML文件当中进行drawable的设置,然后让Android系统帮我们完成,或者手动写代码加载成Bitmap,然后加载到View上。这篇文章主要分析Android在什么时候以及怎么帮我们完成背景图片的加载的,那么我们就从Activity.setContentView还是LayoutInflater.inflate(...)方法开始分析。
不管是从Activity.setContentView(...)还是LayoutInflater.inflate(...)方法进行View的初始化,最终都会到达LayoutInflater.inflate(XmlPullParserparser,ViewGrouproot,booleanattachToRoot)这个方法中。在这里我们主要关注View的背景图片加载,对于XML如何解析和加载就放过了。
publicViewinflate(XmlPullParserparser,ViewGrouproot,booleanattachToRoot){
synchronized(mConstructorArgs){
finalAttributeSetattrs=Xml.asAttributeSet(parser);
ContextlastContext=(Context)mConstructorArgs[0];
mConstructorArgs[0]=mContext;
Viewresult=root;
try{
//Lookfortherootnode.
inttype;
while((type=parser.next())!=XmlPullParser.START_TAG&&
type!=XmlPullParser.END_DOCUMENT){
//Empty
}
if(type!=XmlPullParser.START_TAG){
thrownewInflateException(parser.getPositionDescription()
+":Nostarttagfound!");
}
finalStringname=parser.getName();
if(DEBUG){
System.out.println("**************************");
System.out.println("Creatingrootview:"
+name);
System.out.println("**************************");
}
if(TAG_MERGE.equals(name)){
if(root==null||!attachToRoot){
thrownewInflateException("<merge/>canbeusedonlywithavalid"
+"ViewGrouprootandattachToRoot=true");
}
rInflate(parser,root,attrs,false);
}else{
//Tempistherootviewthatwasfoundinthexml
Viewtemp;
if(TAG_1995.equals(name)){
temp=newBlinkLayout(mContext,attrs);
}else{
temp=createViewFromTag(root,name,attrs);
}
ViewGroup.LayoutParamsparams=null;
if(root!=null){
if(DEBUG){
System.out.println("Creatingparamsfromroot:"+
root);
}
//Createlayoutparamsthatmatchroot,ifsupplied
params=root.generateLayoutParams(attrs);
if(!attachToRoot){
//Setthelayoutparamsfortempifwearenot
//attaching.(Ifweare,weuseaddView,below)
temp.setLayoutParams(params);
}
}
if(DEBUG){
System.out.println("----->startinflatingchildren");
}
//Inflateallchildrenundertemp
rInflate(parser,temp,attrs,true);
if(DEBUG){
System.out.println("----->doneinflatingchildren");
}
//Wearesupposedtoattachalltheviewswefound(inttemp)
//toroot.Dothatnow.
if(root!=null&&attachToRoot){
root.addView(temp,params);
}
//Decidewhethertoreturntherootthatwaspassedinorthe
//topviewfoundinxml.
if(root==null||!attachToRoot){
result=temp;
}
}
}catch(XmlPullParserExceptione){
InflateExceptionex=newInflateException(e.getMessage());
ex.initCause(e);
throwex;
}catch(IOExceptione){
InflateExceptionex=newInflateException(
parser.getPositionDescription()
+":"+e.getMessage());
ex.initCause(e);
throwex;
}finally{
//Don'tretainstaticreferenceoncontext.
mConstructorArgs[0]=lastContext;
mConstructorArgs[1]=null;
}
returnresult;
}
}
上面这么长一串代码,其实思路很清晰,就是针对XML文件进行解析,然后根据XML解析出的每一个节点进行View的初始化,紧接着将View的Layout参数设置到View上,然后将View添加到它的父控件上。
为了了解View是怎么被加载出来的,我们只需要了解
temp=createViewFromTag(root,name,attrs);
跟进去看看。
/*
*defaultvisibilitysotheBridgeInflatercanoverrideit.
*/
ViewcreateViewFromTag(Viewparent,Stringname,AttributeSetattrs){
if(name.equals("view")){
name=attrs.getAttributeValue(null,"class");
}
if(DEBUG)System.out.println("********Creatingview:"+name);
try{
Viewview;
if(mFactory2!=null)view=mFactory2.onCreateView(parent,name,mContext,attrs);
elseif(mFactory!=null)view=mFactory.onCreateView(name,mContext,attrs);
elseview=null;
if(view==null&&mPrivateFactory!=null){
view=mPrivateFactory.onCreateView(parent,name,mContext,attrs);
}
if(view==null){
if(-1==name.indexOf('.')){
view=onCreateView(parent,name,attrs);
}else{
view=createView(name,null,attrs);
}
}
if(DEBUG)System.out.println("Createdviewis:"+view);
returnview;
}catch(InflateExceptione){
throwe;
}catch(ClassNotFoundExceptione){
InflateExceptionie=newInflateException(attrs.getPositionDescription()
+":Errorinflatingclass"+name);
ie.initCause(e);
throwie;
}catch(Exceptione){
InflateExceptionie=newInflateException(attrs.getPositionDescription()
+":Errorinflatingclass"+name);
ie.initCause(e);
throwie;
}
}
上面代码的重点在于try...Catch里的内容。try包起来的东西就是对View进行初始化,注意到上面代码中有几个Factory,这些Factory可以在View进行初始化,也就是说其实我们可以在这里干预View的初始化。从上面代码我们可以知道,如果我们自定义了一个Factory,那么当前要初始化的View会优先被我们自定义的Factory初始化,而不通过系统默认的Factory初始化。那么如果我们要自定义Factory,应该在哪里定义呢?容易想到,Factory必须要赶在资源加载前自定义完成,所以我们应该在onCreate(...)的this.setContentView(...)之前设置LayoutInflater.Factory。
getLayoutInflater().setFactory(factory);
接下来我们看到上面函数里面的
if(-1==name.indexOf('.')){
view=onCreateView(parent,name,attrs);
}else{
view=createView(name,null,attrs);
}
这段函数就是对View进行初始化,有两种情况,一种是系统自带的View,它在
if(-1==name.indexOf('.'))
这里面进行初始化,因为如果是系统自带的View,传入的那么一般不带系统的前缀"android.view."。另一个分支初始化的是我们自定义的View。我们跟进onCreateView看看。
protectedViewonCreateView(Stringname,AttributeSetattrs)
throwsClassNotFoundException{
returncreateView(name,"android.view.",attrs);
}
publicfinalViewcreateView(Stringname,Stringprefix,AttributeSetattrs)
throwsClassNotFoundException,InflateException{
Constructor<?extendsView>constructor=sConstructorMap.get(name);
Class<?extendsView>clazz=null;
try{
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);
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){
//alwaysuseourselveswheninflatingViewStublater
finalViewStubviewStub=(ViewStub)view;
viewStub.setLayoutInflater(this);
}
returnview;
}catch(NoSuchMethodExceptione){
InflateExceptionie=newInflateException(attrs.getPositionDescription()
+":Errorinflatingclass"
+(prefix!=null?(prefix+name):name));
ie.initCause(e);
throwie;
}catch(ClassCastExceptione){
//IfloadedclassisnotaViewsubclass
InflateExceptionie=newInflateException(attrs.getPositionDescription()
+":ClassisnotaView"
+(prefix!=null?(prefix+name):name));
ie.initCause(e);
throwie;
}catch(ClassNotFoundExceptione){
//IfloadClassfails,weshouldpropagatetheexception.
throwe;
}catch(Exceptione){
InflateExceptionie=newInflateException(attrs.getPositionDescription()
+":Errorinflatingclass"
+(clazz==null?"<unknown>":clazz.getName()));
ie.initCause(e);
throwie;
}
}
从onCreateView(...)中我们知道,其实createViewFromTag(...)中对View的初始化最终都是通过createView(...)这个函数进行初始化的,不同只在于系统控件需要通过onCreateView(...)加上前缀,以便类加载器(ClassLoader)正确地通过类所在的包初始化这个类。createView(...)这个函数的思路很清晰,不看catch里面的内容,try里面开头的两个分支就是用来将所要用的类构造函数提取出来,Android系统会对使用过的类构造函数进行缓存,因为像TextView这些常用的控件可能会被使用很多次。接下来,就是通过类构造函数对View进行初始化了。我们注意到传入构造函数的mConstructorArgs是一个包含两个元素的数组。
finalObject[]mConstructorArgs=newObject[2];
那么我们就很清楚了,它就是调用系统控件中对应两个参数的构造函数。为了方便,我们就从最基础的View进行分析。
publicView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicView(Contextcontext,AttributeSetattrs,intdefStyle){
this(context);
TypedArraya=context.obtainStyledAttributes(attrs,com.android.internal.R.styleable.View,
defStyle,0);
Drawablebackground=null;
intleftPadding=-1;
inttopPadding=-1;
intrightPadding=-1;
intbottomPadding=-1;
intstartPadding=UNDEFINED_PADDING;
intendPadding=UNDEFINED_PADDING;
intpadding=-1;
intviewFlagValues=0;
intviewFlagMasks=0;
booleansetScrollContainer=false;
intx=0;
inty=0;
floattx=0;
floatty=0;
floatrotation=0;
floatrotationX=0;
floatrotationY=0;
floatsx=1f;
floatsy=1f;
booleantransformSet=false;
intscrollbarStyle=SCROLLBARS_INSIDE_OVERLAY;
intoverScrollMode=mOverScrollMode;
booleaninitializeScrollbars=false;
booleanleftPaddingDefined=false;
booleanrightPaddingDefined=false;
booleanstartPaddingDefined=false;
booleanendPaddingDefined=false;
finalinttargetSdkVersion=context.getApplicationInfo().targetSdkVersion;
finalintN=a.getIndexCount();
for(inti=0;i<N;i++){
intattr=a.getIndex(i);
switch(attr){
casecom.android.internal.R.styleable.View_background:
background=a.getDrawable(attr);
break;
casecom.android.internal.R.styleable.View_padding:
padding=a.getDimensionPixelSize(attr,-1);
mUserPaddingLeftInitial=padding;
mUserPaddingRightInitial=padding;
leftPaddingDefined=true;
rightPaddingDefined=true;
break;
//省略一大串无关的函数
}
由于我们只关注View中的背景图是怎么加载的,注意这个函数其实就是遍历AttributeSetattrs这个东西,然后对View的各个属性进行初始化。我们直接进入
background=a.getDrawable(attr);
这里看看(TypedArray.getDrawable)。
publicDrawablegetDrawable(intindex){
finalTypedValuevalue=mValue;
if(getValueAt(index*AssetManager.STYLE_NUM_ENTRIES,value)){
if(false){
System.out.println("******************************************************************");
System.out.println("Gotdrawableresource:type="
+value.type
+"str="+value.string
+"int=0x"+Integer.toHexString(value.data)
+"cookie="+value.assetCookie);
System.out.println("******************************************************************");
}
returnmResources.loadDrawable(value,value.resourceId);
}
returnnull;
}
我们发现它调用mResources.loadDrawable(...),进去看看。
/*package*/DrawableloadDrawable(TypedValuevalue,intid)
throwsNotFoundException{
if(TRACE_FOR_PRELOAD){
//Logonlyframeworkresources
if((id>>>24)==0x1){
finalStringname=getResourceName(id);
if(name!=null)android.util.Log.d("PreloadDrawable",name);
}
}
booleanisColorDrawable=false;
if(value.type>=TypedValue.TYPE_FIRST_COLOR_INT&&
value.type<=TypedValue.TYPE_LAST_COLOR_INT){
isColorDrawable=true;
}
finallongkey=isColorDrawable?value.data:
(((long)value.assetCookie)<<32)|value.data;
Drawabledr=getCachedDrawable(isColorDrawable?mColorDrawableCache:mDrawableCache,key);
if(dr!=null){
returndr;
}
Drawable.ConstantStatecs=isColorDrawable
?sPreloadedColorDrawables.get(key)
:(sPreloadedDensity==mConfiguration.densityDpi
?sPreloadedDrawables.get(key):null);
if(cs!=null){
dr=cs.newDrawable(this);
}else{
if(isColorDrawable){
dr=newColorDrawable(value.data);
}
if(dr==null){
if(value.string==null){
thrownewNotFoundException(
"ResourceisnotaDrawable(colororpath):"+value);
}
Stringfile=value.string.toString();
if(TRACE_FOR_MISS_PRELOAD){
//Logonlyframeworkresources
if((id>>>24)==0x1){
finalStringname=getResourceName(id);
if(name!=null)android.util.Log.d(TAG,"Loadingframeworkdrawable#"
+Integer.toHexString(id)+":"+name
+"at"+file);
}
}
if(DEBUG_LOAD)Log.v(TAG,"Loadingdrawableforcookie"
+value.assetCookie+":"+file);
if(file.endsWith(".xml")){
try{
XmlResourceParserrp=loadXmlResourceParser(
file,id,value.assetCookie,"drawable");
dr=Drawable.createFromXml(this,rp);
rp.close();
}catch(Exceptione){
NotFoundExceptionrnf=newNotFoundException(
"File"+file+"fromdrawableresourceID#0x"
+Integer.toHexString(id));
rnf.initCause(e);
throwrnf;
}
}else{
try{
InputStreamis=mAssets.openNonAsset(
value.assetCookie,file,AssetManager.ACCESS_STREAMING);
// System.out.println("Openedfile"+file+":"+is);
dr=Drawable.createFromResourceStream(this,value,is,
file,null);
is.close();
// System.out.println("Createdstream:"+dr);
}catch(Exceptione){
NotFoundExceptionrnf=newNotFoundException(
"File"+file+"fromdrawableresourceID#0x"
+Integer.toHexString(id));
rnf.initCause(e);
throwrnf;
}
}
}
}
if(dr!=null){
dr.setChangingConfigurations(value.changingConfigurations);
cs=dr.getConstantState();
if(cs!=null){
if(mPreloading){
if(verifyPreloadConfig(value,"drawable")){
if(isColorDrawable){
sPreloadedColorDrawables.put(key,cs);
}else{
sPreloadedDrawables.put(key,cs);
}
}
}else{
synchronized(mTmpValue){
//Log.i(TAG,"Savingcacheddrawable@#"+
// Integer.toHexString(key.intValue())
// +"in"+this+":"+cs);
if(isColorDrawable){
mColorDrawableCache.put(key,newWeakReference<Drawable.ConstantState>(cs));
}else{
mDrawableCache.put(key,newWeakReference<Drawable.ConstantState>(cs));
}
}
}
}
}
returndr;
}
就是这个函数了,所有View的背景的加载都在这里了。这个函数的逻辑就比较复杂了,大体说来就是根据背景的类型(纯颜色、定义在XML文件中的,或者是一张静态的背景),如果缓存里面有,就直接用缓存里的。
总结一下,经过上面的分析,我们知道了,Android就是在Activity.setContentView(...)中为我们进行资源文件的加载,精确到具体的函数的话,资源文件的加载就是在每一个被初始化的View的构造函数中进行加载的。
以上就是本文的全部内容了,希望对大家能够有所帮助。