在Android中,所有的资源都在res目录下存放,包括drawable,layout,strings,anim等等,当我们向工程中加入任何一个资源时,会在R类中相应会为该 资源分配一个id,我们在应用中就是通过这个id来访问资源的,相信做过Andorid开发的朋友对于这些肯定不会陌生,所以这个也不是我今天想要说的,我今天想和大家一起学习的是Android是如何管理资源的,在Android系统中,资源大部分都是通过xml文件定义的(drawable是图片),如layout,string,anim都是xml文件,而对于layout,anim和strings等xml文件仅仅是解析xml文件,读取指定的值而已,但是对于layout文件中控件的解析就比较复杂了,例如对于一个Button,需要解析它所有的属性值,这个是如何实现的呢。

这里我们首先要考虑一个问题,就是一个控件有哪些属性是如何定义的?比如TextView具有哪些属性?为什么我设置TextView的样式只能用style而不能用android:theme?这些信息都是在哪里定义的,想要弄清楚这个问题,就必须从源码工程招答案,我使用的是android4.1工程,如果你使用的是其他版本的,那么可能用些出入。

先看三个文件

1、d:\android4.1\frameworks\base\core\res\res\values\attrs.xml

看到attrs.xml文件,不知道你有没有想起什么?当我们在自定义控件的时候,是不是会创建一个attrs.xml文件?使用attrs.xml文件的目的其实就是给我们自定义的控件添加属性,打开这个目录后,你会看到定义了一个叫"Theme"的styleable,如下(我只截取部分)

<declare-styleablename="Theme"><!--==============--><!--Genericstyles--><!--==============--><eat-comment/><!--Defaultcolorofforegroundimagery.--><attrname="colorForeground"format="color"/><!--Defaultcolorofforegroundimageryonaninvertedbackground.--><attrname="colorForegroundInverse"format="color"/><!--Colorthatmatches(ascloselyaspossible)thewindowbackground.--><attrname="colorBackground"format="color"/>

在这个文件中,定义了Android中大部分可以使用的属性,这里我说的是“定义”而不是“声明”,同名在语法上面最大的区别就是定义要有format属性,而声明没有format属性。

2、d:\android4.1\frameworks\base\core\res\res\values\attrs_manifest.xml

这个文件的名字和上面的文件的名字很像,就是多了一个manifest,故名思议就是定义了AndroidManifest.xml文件中的属性,这里面有一个很重要的一句话

<attrname="theme"format="reference"/>

定义了一个theme属性,这个就是我们平时在Activity上面使用的theme属性

3、d:\android4.1\frameworks\base\core\res\res\values\themes.xml

这个文件开始定义了一个叫做"Theme" 的sytle,如下(截图部分)

<stylename="Theme"><itemname="colorForeground">@android:color/bright_foreground_dark</item><itemname="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item><itemname="colorBackground">@android:color/background_dark</item><itemname="colorBackgroundCacheHint">?android:attr/colorBackground</item>

这个就是我们平时在Application或者Activity中使用的Theme,从这里可以看出,Theme也是一种style,那为什么style只能永远View/ViewGorup,而Theme只能用于Activity或者Application呢?先记住此问题,我们后续会为你解答

我们再来整合这三个文件的内容吧,首先在attrs.xml文件中,定义了Android中大部分的属性,也就是说以后所有View/Activity中大部分的属性就是在这里定义的,然后在attrs_manifest.xml中定义了一个叫做theme的属性,它的值就是再themes文件中定义的Theme或者继承自“Theme”的style。

有了上面的知识后,我们再来分析上面说过的两个问题:

1、TextView控件(其他控件也一样)的属性在哪里定义的。

2、既然Theme也是style,那为什么View只能用style,Activity只能使用theme?

所有View的属性定义都是在attrs.xml文件中的,所以我们到attrs.xml文件中寻找TextView的styleable吧

<declare-styleablename="TextView"><!--DeterminestheminimumtypethatgetText()willreturn.Thedefaultis"normal".NotethatEditTextandLogTextBoxalwaysreturnEditable,evenifyouspecifysomethinglesspowerfulhere.--><attrname="bufferType"><!--CanreturnanyCharSequence,possiblyaSpannedoneifthesourcetextwasSpanned.--><enumname="normal"value="0"/><!--CanonlyreturnSpannable.--><enumname="spannable"value="1"/><!--CanonlyreturnSpannableandEditable.--><enumname="editable"value="2"/></attr><!--Texttodisplay.--><attrname="text"format="string"localization="suggested"/><!--Hinttexttodisplaywhenthetextisempty.--><attrname="hint"format="string"/><!--Textcolor.--><attrname="textColor"/>

上面的属性我只截取了部分,请注意,这里所有的属性都是进行“声明”,你去搜索这个styleable,会发现在TextView的styleable中不会找到theme这个属性的声明,所以你给任何一个view设置theme属性是没有效果的。请看下面一段代码就知道为什么了。

定义一个attrs.xml

<?xmlversion="1.0"encoding="utf-8"?><resources><declare-styleablename="MyTextView"><attrname="orientation"><enumname="horizontal"value="0"/><enumname="vertical"value="1"/></attr></declare-styleable></resources>

定义一个MyTextView

publicclassMyTextViewextendsTextView{privatestaticfinalStringTAG="MyTextView";publicMyTextView(Contextcontext){super(context);}publicMyTextView(Contextcontext,AttributeSetattrs){super(context,attrs);//利用TypeArray读取自定义的属性TypedArrayta=context.obtainStyledAttributes(attrs,R.styleable.MyTextView);Stringvalue=ta.getString(R.styleable.MyTextView_orientation);Log.d("yzy","value1--->"+value);ta.recycle();}}

在attrs.xml我为MyTextView定义了一个orientation属性,然后再MyTextView的构造函数中去读取这个属性,这里就涉及到TypeArray这个类,我们发现得到TypeArray需要传入R.style.MyTextView这个值,这个就是系统为我们访问MyTextView这个styleable提供的一个id,当我们需要拿到orientation这个属性的值时,我们通过R.style.MyTextView_orientation拿到,由于MyTextView中没有定义或者声明theme属性,所以我们找不到R.styleable.MyTextView_theme这个id,所以导致我们无法解析它的theme属性。同样回到TextView这个styleable来,由于TextView的styleable中没有定义theme属性,所以theme对于TextView是没有用的。所以即使你在TextView里面加入theme属性,即使编译器不会给你报错,这个theme也是被忽略了的。

我们再来看看Activity的属性是如何定义的,由于Activity是在AndroidManigest.xml文件中定义的,所以我们到attrs_manifest.xml中查找。

<declare-styleablename="AndroidManifestActivity"parent="AndroidManifestApplication"><!--Requirednameoftheclassimplementingtheactivity,derivingfrom{@linkandroid.app.Activity}.Thisisafullyqualifiedclassname(forexample,com.mycompany.myapp.MyActivity);asashort-handifthefirstcharacteroftheclassisaperiodthenitisappendedtoyourpackagename.--><attrname="name"/><attrname="theme"/><attrname="label"/><attrname="description"/><attrname="icon"/><attrname="logo"/><attrname="launchMode"/><attrname="screenOrientation"/><attrname="configChanges"/><attrname="permission"/><attrname="multiprocess"/><attrname="process"/><attrname="taskAffinity"/><attrname="allowTaskReparenting"/><attrname="finishOnTaskLaunch"/><attrname="finishOnCloseSystemDialogs"/><attrname="clearTaskOnLaunch"/><attrname="noHistory"/><attrname="alwaysRetainTaskState"/><attrname="stateNotNeeded"/><attrname="excludeFromRecents"/><!--Specifywhethertheactivityisenabledornot(thatis,canbeinstantiatedbythesystem).Itcanalsobespecifiedforanapplicationasawhole,inwhichcaseavalueof"false"willoverrideanycomponentspecificvalues(avalueof"true"willnotoverridethecomponentspecificvalues).--><attrname="enabled"/><attrname="exported"/><!--Specifythedefaultsoft-inputmodeforthemainwindowofthisactivity.Avaluebesides"unspecified"hereoverridesanyvalueinthetheme.--><attrname="windowSoftInputMode"/><attrname="immersive"/><attrname="hardwareAccelerated"/><attrname="uiOptions"/><attrname="parentActivityName"/></declare-styleable>

很明显,Activity对于的styleable中是声明了theme的,所以它可以解析theme属性。

上面两个问题都已经解答完了,下面来讨论另一个话题,就是Resources的获取过程。

在我的另外一篇文章曾经讨论过这个话题更深层次理解Context 这里我们再来学习一下Resources的获取过程。

在Android系统中,获取Resources主要有两种方法,通过Context获取和PackageManager获取

首先,我们看看我们通过Context获取,下面这张图是Context相关类的类图

Android中资源管理机制详解

从图中可以看出,Context有两个子类,一个是ContextWrapper,另一个是ContextImpl,而ContextWrapper依赖于ContextImpl。结合源码,我们会发现,Context是一个抽象类,它的真正实现类就是ContextImpl,而ContextWrapper就像他的名字一样,仅仅是对Context的一层包装,它的功能都是通过调用属性mBase完成,该mBase实质就是指向一个ContextImpl类型的变量。我们获取Resources时就是调用Context的getResources方法,那么我们直接看看ContextImpl的getResources方法吧

@OverridepublicResourcesgetResources(){returnmResources;}

我们发现这个方法很简单,就是返回mResources属性,那么这个属性是在哪里 赋值的呢,通过寻找发现,其实就是在创建ContextImpl,通过调用Init进行赋值的(具体逻辑参照《更深层次理解Context》).这里我先给出getResource方法的时序图,然后跟踪源码。

Android中资源管理机制详解

先从init方法开始吧

finalvoidinit(LoadedApkpackageInfo,IBinderactivityToken,ActivityThreadmainThread,Resourcescontainer,StringbasePackageName){mPackageInfo=packageInfo;mBasePackageName=basePackageName!=null?basePackageName:packageInfo.mPackageName;mResources=mPackageInfo.getResources(mainThread);if(mResources!=null&&container!=null&&container.getCompatibilityInfo().applicationScale!=mResources.getCompatibilityInfo().applicationScale){if(DEBUG){Log.d(TAG,"loadedcontexthasdifferentscaling.Usingcontainer's"+"compatiblityinfo:"+container.getDisplayMetrics());}mResources=mainThread.getTopLevelResources(mPackageInfo.getResDir(),container.getCompatibilityInfo());}mMainThread=mainThread;mContentResolver=newApplicationContentResolver(this,mainThread);setActivityToken(activityToken);}

我们发现,对mResource进行赋值,是通过调用LoadedApk中的getResource进行的,传入了ActivityThead类型的参数

publicResourcesgetResources(ActivityThreadmainThread){if(mResources==null){mResources=mainThread.getTopLevelResources(mResDir,this);}returnmResources;}

在getResources方法中,其实就是调用了ActivityThrad的getTopLevelResources方法,其中mResDir就是apk文件的路径(对于用户安装的app,此路径就在/data/app下面的某一个apk),从时序图中可以知道,getTopLevelResources其实就是调用了一个同名方法,我们直接看它的同名方法吧

ResourcesgetTopLevelResources(StringresDir,CompatibilityInfocompInfo){ResourcesKeykey=newResourcesKey(resDir,compInfo.applicationScale);Resourcesr;synchronized(mPackages){//Resourcesisappscaledependent.if(false){Slog.w(TAG,"getTopLevelResources:"+resDir+"/"+compInfo.applicationScale);}WeakReference<Resources>wr=mActiveResources.get(key);r=wr!=null?wr.get():null;//if(r!=null)Slog.i(TAG,"isUpToDate"+resDir+":"+r.getAssets().isUpToDate());if(r!=null&&r.getAssets().isUpToDate()){if(false){Slog.w(TAG,"Returningcachedresources"+r+""+resDir+":appScale="+r.getCompatibilityInfo().applicationScale);}returnr;}}<spanstyle="font-family:Arial,Helvetica,sans-serif;">;</span>//if(r!=null){//Slog.w(TAG,"Throwingawayout-of-dateresources!!!!"//+r+""+resDir);//}AssetManagerassets=newAssetManager();if(assets.addAssetPath(resDir)==0){returnnull;}//Slog.i(TAG,"Resource:key="+key+",displaymetrics="+metrics);DisplayMetricsmetrics=getDisplayMetricsLocked(null,false);r=newResources(assets,metrics,getConfiguration(),compInfo);if(false){Slog.i(TAG,"Createdappresources"+resDir+""+r+":"+r.getConfiguration()+"appScale="+r.getCompatibilityInfo().applicationScale);}synchronized(mPackages){WeakReference<Resources>wr=mActiveResources.get(key);Resourcesexisting=wr!=null?wr.get():null;if(existing!=null&&existing.getAssets().isUpToDate()){//Someoneelsealreadycreatedtheresourceswhilewewere//unlocked;goaheadandusetheirs.r.getAssets().close();returnexisting;}//XXXneedtoremoveentrieswhenweakreferencesgoawaymActiveResources.put(key,newWeakReference<Resources>(r));returnr;}}

这段代码的逻辑不复杂,首先从mActiveResouuces中通过key拿到资源,如果资源不为null,并且是最新的,那么直接返回,否则创建一个AssetManager对象,并调用AssetManager的addAssetPath方法,然后使用创建的AssetManager为参数,创建一个Resources对象,保存并返回。通过上面的时序图,我们发现在创建AssetManager的时候,在其构造函数中调用init方法,我们看看init方法做了什么吧

privatenativefinalvoidinit();

居然是一个本地方法,那么我们只有看看对应的Jni代码了

staticvoidandroid_content_AssetManager_init(JNIEnv*env,jobjectclazz){AssetManager*am=newAssetManager();if(am==NULL){jniThrowException(env,"java/lang/OutOfMemoryError","");return;}am->addDefaultAssets();ALOGV("CreatedAssetManager%pforJavaobject%p\n",am,clazz);env->SetIntField(clazz,gAssetManagerOffsets.mObject,(jint)am);}

这个里面调用了本地的AssetManager的addDefaultAssets方法

boolAssetManager::addDefaultAssets(){constchar*root=getenv("ANDROID_ROOT");LOG_ALWAYS_FATAL_IF(root==NULL,"ANDROID_ROOTnotset");String8path(root);path.appendPath(kSystemAssets);returnaddAssetPath(path,NULL);}

这例的ANDROID_ROOT保存的就是/system路径,而kSystemAssets是

staticconstchar*kSystemAssets="framework/framework-res.apk";

还记得framework-res.apk是什么吗,就是系统所有的资源文件。

到这里终于明白了,原理就是将系统的资源加载进来。

接下来看看addAssetPath方法吧,进入源码后,你会发现它也是一个本地方法,也需要看jni代码

staticjintandroid_content_AssetManager_addAssetPath(JNIEnv*env,jobjectclazz,jstringpath){ScopedUtfCharspath8(env,path);if(path8.c_str()==NULL){return0;}AssetManager*am=assetManagerForJavaObject(env,clazz);if(am==NULL){return0;}void*cookie;boolres=am->addAssetPath(String8(path8.c_str()),&cookie);return(res)?(jint)cookie:0;}

这里调用了本地AssetManager方法的addAssetPath方法。和系统资源一样,都被加载进来了。

下面看看PackageManager获取Resource的流程吧

在PackageManager里面获取资源调用的是getResourcesForApplication方法,getResourcesForApplication也有一个同名方法,我们看办正事的那个吧

@OverridepublicResourcesgetResourcesForApplication(ApplicationInfoapp)throwsNameNotFoundException{if(app.packageName.equals("system")){returnmContext.mMainThread.getSystemContext().getResources();}Resourcesr=mContext.mMainThread.getTopLevelResources(app.uid==Process.myUid()?app.sourceDir:app.publicSourceDir,mContext.mPackageInfo);if(r!=null){returnr;}thrownewNameNotFoundException("Unabletoopen"+app.publicSourceDir);}

首先判断包名是否是system,如果不是那么直接调用ActivityThread的getTopLevelResources方法。不过这里会根据当前应用的应用的uid和进程Id相等,如果相等则传入app.sourceDir,否则传入publicSourceDir,但是根据经验时期sourceDir和publicSource一般情况下是相同的。后面的逻辑和Context中的是一样的,这里就不在说了。

更多相关文章

  1. android 自定义标题栏和自定义下拉选项PopupWindow
  2. Android:绘制自定义视图
  3. AndroidManifest--定义android清单
  4. RelativeLayout部分属性介绍
  5. android解析XML文件的三方法之SAX
  6. android中自定义控件的属性

随机推荐

  1. android 通过ContentResolver获得联系人
  2. Android(安卓)开发常用代码片段
  3. android UI控件之webview控件使用实例:加
  4. android textview 显示 文本 .txt
  5. Android常用功能代码块
  6. Android拍照上传代码样例
  7. android标题栏去除和全屏
  8. android studio详细的编译错误提示
  9. Android(安卓)Ble连接,Ble133异常处理,写入
  10. Termux镜像在阿里云镜像站首发上线