Android中资源管理机制详细分析

来源:互联网 发布:女士斜挎包 淘宝 编辑:程序博客网 时间:2024/05/16 22:49

转载地址:http://blog.csdn.net/yuanzeyao/article/details/42386549

在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,如下(我只截取部分)

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <declare-styleable name="Theme">  
  2.         <!-- ============== -->  
  3.         <!-- Generic styles -->  
  4.         <!-- ============== -->  
  5.         <eat-comment />  
  6.   
  7.         <!-- Default color of foreground imagery. -->  
  8.         <attr name="colorForeground" format="color" />  
  9.         <!-- Default color of foreground imagery on an inverted background. -->  
  10.         <attr name="colorForegroundInverse" format="color" />  
  11.         <!-- Color that matches (as closely as possible) the window background. -->  
  12.         <attr name="colorBackground" format="color" />  


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

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

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

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <attr name="theme" format="reference" />  

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

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

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

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <style name="Theme">  
  2.   
  3.         <item name="colorForeground">@android:color/bright_foreground_dark</item>  
  4.         <item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item>  
  5.         <item name="colorBackground">@android:color/background_dark</item>  
  6.         <item name="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吧

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <declare-styleable name="TextView">  
  2.        <!-- Determines the minimum type that getText() will return.  
  3.             The default is "normal".  
  4.             Note that EditText and LogTextBox always return Editable,  
  5.             even if you specify something less powerful here. -->  
  6.        <attr name="bufferType">  
  7.            <!-- Can return any CharSequence, possibly a  
  8.             Spanned one if the source text was Spanned. -->  
  9.            <enum name="normal" value="0" />  
  10.            <!-- Can only return Spannable. -->  
  11.            <enum name="spannable" value="1" />  
  12.            <!-- Can only return Spannable and Editable. -->  
  13.            <enum name="editable" value="2" />  
  14.        </attr>  
  15.        <!-- Text to display. -->  
  16.        <attr name="text" format="string" localization="suggested" />  
  17.        <!-- Hint text to display when the text is empty. -->  
  18.        <attr name="hint" format="string" />  
  19.        <!-- Text color. -->  
  20.        <attr name="textColor" />  


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

定义一个attrs.xml

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="MyTextView">  
  4.         <attr name="orientation">  
  5.             <enum name="horizontal" value="0" />  
  6.             <enum name="vertical" value="1" />  
  7.         </attr>  
  8.     </declare-styleable>  
  9. </resources>  
定义一个MyTextView

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. public class MyTextView extends TextView {  
  2.   private static final String TAG = "MyTextView";  
  3.   public MyTextView(Context context)   
  4.   {  
  5.     super(context);  
  6.   }  
  7.   public MyTextView(Context context, AttributeSet attrs)   
  8.   {  
  9.     super(context, attrs);  
  10.     //利用TypeArray读取自定义的属性  
  11.     TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MyTextView);  
  12.     String value=ta.getString(R.styleable.MyTextView_orientation);  
  13.     Log.d("yzy""value1--->"+value);  
  14.     ta.recycle();  
  15.   }  
  16. }  


在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中查找。

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <declare-styleable name="AndroidManifestActivity" parent="AndroidManifestApplication">  
  2.     <!-- Required name of the class implementing the activity, deriving from  
  3.         {@link android.app.Activity}.  This is a fully  
  4.         qualified class name (for example, com.mycompany.myapp.MyActivity); as a  
  5.         short-hand if the first character of the class  
  6.         is a period then it is appended to your package name. -->  
  7.     <attr name="name" />  
  8.     <attr name="theme" />  
  9.     <attr name="label" />  
  10.     <attr name="description" />  
  11.     <attr name="icon" />  
  12.     <attr name="logo" />  
  13.     <attr name="launchMode" />  
  14.     <attr name="screenOrientation" />  
  15.     <attr name="configChanges" />  
  16.     <attr name="permission" />  
  17.     <attr name="multiprocess" />  
  18.     <attr name="process" />  
  19.     <attr name="taskAffinity" />  
  20.     <attr name="allowTaskReparenting" />  
  21.     <attr name="finishOnTaskLaunch" />  
  22.     <attr name="finishOnCloseSystemDialogs" />  
  23.     <attr name="clearTaskOnLaunch" />  
  24.     <attr name="noHistory" />  
  25.     <attr name="alwaysRetainTaskState" />  
  26.     <attr name="stateNotNeeded" />  
  27.     <attr name="excludeFromRecents" />  
  28.     <!-- Specify whether the activity is enabled or not (that is, can be instantiated by the system).  
  29.          It can also be specified for an application as a whole, in which case a value of "false"  
  30.          will override any component specific values (a value of "true" will not override the  
  31.          component specific values). -->  
  32.     <attr name="enabled" />  
  33.     <attr name="exported" />  
  34.     <!-- Specify the default soft-input mode for the main window of  
  35.          this activity.  A value besides "unspecified" here overrides  
  36.          any value in the theme. -->  
  37.     <attr name="windowSoftInputMode" />  
  38.     <attr name="immersive" />  
  39.     <attr name="hardwareAccelerated" />  
  40.     <attr name="uiOptions" />  
  41.     <attr name="parentActivityName" />  
  42. </declare-styleable>  


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


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

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


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

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


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

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.    public Resources getResources() {  
  3.        return mResources;  
  4.    }  


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


先从init方法开始吧

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. final void init(LoadedApk packageInfo,  
  2.                 IBinder activityToken, ActivityThread mainThread,  
  3.                 Resources container, String basePackageName) {  
  4.         mPackageInfo = packageInfo;  
  5.         mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;  
  6.         mResources = mPackageInfo.getResources(mainThread);  
  7.   
  8.         if (mResources != null && container != null  
  9.                 && container.getCompatibilityInfo().applicationScale !=  
  10.                         mResources.getCompatibilityInfo().applicationScale) {  
  11.             if (DEBUG) {  
  12.                 Log.d(TAG, "loaded context has different scaling. Using container's" +  
  13.                         " compatiblity info:" + container.getDisplayMetrics());  
  14.             }  
  15.             mResources = mainThread.getTopLevelResources(  
  16.                     mPackageInfo.getResDir(), container.getCompatibilityInfo());  
  17.         }  
  18.         mMainThread = mainThread;  
  19.         mContentResolver = new ApplicationContentResolver(this, mainThread);  
  20.   
  21.         setActivityToken(activityToken);  
  22.     }  


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

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. public Resources getResources(ActivityThread mainThread) {  
  2.       if (mResources == null) {  
  3.           mResources = mainThread.getTopLevelResources(mResDir, this);  
  4.       }  
  5.       return mResources;  
  6.   }  

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

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {  
  2.         ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);  
  3.         Resources r;  
  4.         synchronized (mPackages) {  
  5.             // Resources is app scale dependent.  
  6.             if (false) {  
  7.                 Slog.w(TAG, "getTopLevelResources: " + resDir + " / "  
  8.                         + compInfo.applicationScale);  
  9.             }  
  10.             WeakReference<Resources> wr = mActiveResources.get(key);  
  11.             r = wr != null ? wr.get() : null;  
  12.             //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());  
  13.             if (r != null && r.getAssets().isUpToDate()) {  
  14.                 if (false) {  
  15.                     Slog.w(TAG, "Returning cached resources " + r + " " + resDir  
  16.                             + ": appScale=" + r.getCompatibilityInfo().applicationScale);  
  17.                 }  
  18.                 return r;  
  19.             }  
  20.         }<span style="font-family: Arial, Helvetica, sans-serif;">;</span>  
  21.   
  22.   
  23.         //if (r != null) {  
  24.         //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "  
  25.         //            + r + " " + resDir);  
  26.         //}  
  27.   
  28.         AssetManager assets = new AssetManager();  
  29.         if (assets.addAssetPath(resDir) == 0) {  
  30.             return null;  
  31.         }  
  32.   
  33.         //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);  
  34.         DisplayMetrics metrics = getDisplayMetricsLocked(nullfalse);  
  35.         r = new Resources(assets, metrics, getConfiguration(), compInfo);  
  36.         if (false) {  
  37.             Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "  
  38.                     + r.getConfiguration() + " appScale="  
  39.                     + r.getCompatibilityInfo().applicationScale);  
  40.         }  
  41.           
  42.         synchronized (mPackages) {  
  43.             WeakReference<Resources> wr = mActiveResources.get(key);  
  44.             Resources existing = wr != null ? wr.get() : null;  
  45.             if (existing != null && existing.getAssets().isUpToDate()) {  
  46.                 // Someone else already created the resources while we were  
  47.                 // unlocked; go ahead and use theirs.  
  48.                 r.getAssets().close();  
  49.                 return existing;  
  50.             }  
  51.               
  52.             // XXX need to remove entries when weak references go away  
  53.             mActiveResources.put(key, new WeakReference<Resources>(r));  
  54.             return r;  
  55.         }  
  56.     }  

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

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. private native final void init();  


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

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)  
  2. {  
  3.     AssetManager* am = new AssetManager();  
  4.     if (am == NULL) {  
  5.         jniThrowException(env, "java/lang/OutOfMemoryError""");  
  6.         return;  
  7.     }  
  8.   
  9.     am->addDefaultAssets();  
  10.   
  11.     ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);  
  12.     env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);  
  13. }  

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

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. bool AssetManager::addDefaultAssets()  
  2. {  
  3.     const char* root = getenv("ANDROID_ROOT");  
  4.     LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");  
  5.   
  6.     String8 path(root);  
  7.     path.appendPath(kSystemAssets);  
  8.   
  9.     return addAssetPath(path, NULL);  
  10. }  


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

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. static const char* kSystemAssets = "framework/framework-res.apk";  


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

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


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

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,  
  2.                                                        jstring path)  
  3. {  
  4.     ScopedUtfChars path8(env, path);  
  5.     if (path8.c_str() == NULL) {  
  6.         return 0;  
  7.     }  
  8.   
  9.     AssetManager* am = assetManagerForJavaObject(env, clazz);  
  10.     if (am == NULL) {  
  11.         return 0;  
  12.     }  
  13.   
  14.     void* cookie;  
  15.     bool res = am->addAssetPath(String8(path8.c_str()), &cookie);  
  16.   
  17.     return (res) ? (jint)cookie : 0;  
  18. }  

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


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

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

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. @Override public Resources getResourcesForApplication(  
  2.     ApplicationInfo app) throws NameNotFoundException {  
  3.     if (app.packageName.equals("system")) {  
  4.         return mContext.mMainThread.getSystemContext().getResources();  
  5.     }  
  6.     Resources r = mContext.mMainThread.getTopLevelResources(  
  7.         app.uid == Process.myUid() ? app.sourceDir  
  8.         : app.publicSourceDir, mContext.mPackageInfo);  
  9.     if (r != null) {  
  10.         return r;  
  11.     }  
  12.     throw new NameNotFoundException("Unable to open " + app.publicSourceDir);  
  13. }  
首先判断包名是否是system,如果不是那么直接调用ActivityThread的getTopLevelResources方法。不过这里会根据当前应用的应用的uid和进程Id相等,如果相等则传入app.sourceDir,否则传入publicSourceDir,但是根据经验时期sourceDir和publicSource一般情况下是相同的。后面的逻辑和Context中的是一样的,这里就不在说了。
0 0
原创粉丝点击