A detailed analysis of resource management mechanism in Android

Keywords: xml Android Attribute Java

Reprint address: http://blog.csdn.net/yuanzeyao/article/details/42386549

stay Android All resources are stored in the res directory, including drawable, layout, strings,anim and so on. When we add any resource to the project, it will correspond to that in the R class. Resource allocation is an id that we use to access resources in our applications. I believe that friends who have developed Andorid will not be unfamiliar with these, so this is not what I want to say today. What I want to learn with you today is how Android manages resources. In Android system, there are a lot of resources. Some of them are defined by xml files (drawable is image), such as layout, string, anim are xml files, while xml files such as layout, anim and strings are just parsing xml files and reading specified values, but the analysis of controls in layout files is more complex, such as for a Button, which needs to be solved. Analyzing all its attribute values, how does this work?


Here we first need to consider a question, is a control what attributes are defined? For example, what attributes does TextView have? Why can I only use style instead of android:theme when I set TextView? Where are all these information defined? To find out this problem, I have to get answers from source engineering. I use Android 4.1 project. If you use other versions, you may use some discrepancies.

Look at three documents first.

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

Looking at the attrs.xml file, I wonder if you think of anything? When we customize the control, will we create an attrs.xml file? The purpose of using attrs.xml file is to add attributes to our customized control. When you open this directory, you will see that a styleable named "Theme" is defined, as follows (I only intercept part)

  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" />  


In this file, most of the attributes that can be used in Android are defined. I'm talking about "definition" instead of "declaration". The biggest difference in the syntax of the same name is that the definition has format attributes, while the declaration has no format attributes.

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

The name of this file is similar to the name of the file above, that is, an additional manifest, so the idea is to define the attributes in the Android Manifest. XML file. Here is an important sentence.

  1. <attr name="theme" format="reference" />  

Define a theme attribute, which is the theme attribute we usually use on Activity

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

This file begins by defining a sytle called "Theme" as follows (screenshot)

  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>  

This is the Theme that we usually use in Application or Activity. From this point of view, Theme is also a style. Why can style only be used in View/ViewGorup forever, while Theme can only be used in Activity or Application? Keep this in mind, and we'll answer it later.


Let's integrate the contents of these three files. Firstly, most of the attributes in Android are defined in attrs.xml file. That is to say, most of the attributes in all View/Activity are defined here later. Then, in attrs_manifest.xml, an attribute called theme is defined. Its value is then themes text. The Theme defined in the file or inherited from the style of "Theme".


With the above knowledge, let's analyze the two problems mentioned above.

1. Where are the properties of the TextView control (and other controls as well) defined?

2. Since Theme is also style, why can View only use style and Activity only use theme?


All View attribute definitions are in the attrs.xml file, so let's go to the attrs.xml file to find the styleable of TextView.

  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" />  


I only intercepted part of the above attributes. Please note that all the attributes here are "declarations". If you search for this styleable, you will find that the declaration of theme will not be found in the styleable of TextView, so it is ineffective to set theme attributes for any view. See the following code to see why.

Define an attrs.xml

  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>  
Define a MyTextView

  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.     //Use TypeArray to read custom attributes  
  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. }  


In attrs.xml, I defined an orientation attribute for MyTextView, and then read it in the constructor of MyTextView. Here we refer to the class TypeArray. We find that we need to pass in the value of R.style.MyTextView, which is provided by the system for us to access the MyTextView styleable. An id, when we need to get the value of orientation, we get it through R.style.MyTextView_orientation. Because there is no definition or declaration of the theme attribute in MyTextView, we can not find the ID of R.styleable.MyTextView_theme, so we can not parse its theme attribute. Again, back to the TextView styleable, because there is no definition of the theme attribute in the textView styleable, the theme is useless for the TextView. So even if you add the theme attribute to TextView, even if the compiler doesn't error you, the theme is ignored.


Let's look at how the properties of Activity are defined. Since Activity is defined in the AndroidManigest.xml file, we look in attrs_manifest.xml.

  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>  


Obviously, Activity declares theme in styleable, so it can parse the theme attribute.


The above two questions have been answered, and the next topic to discuss is the acquisition process of Resources.

This topic was discussed in another article of mine. Deeper understanding of Context Here we will learn about the acquisition process of Resources.


In Android system, there are two main ways to obtain Resources, through Context and Package Manager.

First, let's look at what we get from Context. Here's a class diagram of Context related classes.


As you can see from the figure, Context has two subclasses, one is ContextWrapper, the other is ContextImpl, and ContextWrapper depends on ContextImpl. Combining with the source code, we will find that Context is an abstract class, its real implementation class is ContextImpl, and ContextWrapper, like his name, is just a layer of packaging for Contextext. Its function is to call the attribute mBase, which is essentially a variable of ContextImpl type. When we get Resources, we call the getResources method of Context, so let's look directly at the getResources method of ContextImpl.

  1. @Override  
  2.    public Resources getResources() {  
  3.        return mResources;  
  4.    }  


We found that this method is very simple, that is to return the mResources attribute, then where does this attribute assign value? By looking for it, we found that it is actually creating ContextImpl and assigning value by calling Init. (Specific logic refers to "Deeper Understanding Context"). Here I first give the sequence diagram of getResource s method. Then track the source code.


Let's start with the init method.

  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.     }  


We found that the assignment of mResource was done by calling getResource in LoadedApk, passing in parameters of ActivityThead type.

  1. public Resources getResources(ActivityThread mainThread) {  
  2.       if (mResources == null) {  
  3.           mResources = mainThread.getTopLevelResources(mResDir, this);  
  4.       }  
  5.       return mResources;  
  6.   }  

In the getResources method, we call the getTopLevelResources method of ActivityThrad, where mResDir is the path of the APK file (for the app installed by the user, this path is under / data/app apk). As you can see from the sequence diagram, getTopelResources actually calls a method with the same name. Let's look directly at the method of its homonym.

  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.     }  

The logic of this code is not complicated. First, we get the resource from mActiveResouuces by key. If the resource is not null and is up-to-date, we return it directly. Otherwise, we create an AssetManager object and call the addAssetPath method of AssetManager. Then we create a Resour using the created AssetManager as a parameter. CES object, saved and returned. From the above sequence diagram, we find that when we create AssetManager, we call init method in its constructor. Let's see what the init method does.

  1. private native final void init();  


It's a local method, so we have to look at the corresponding Jni 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. }  

This calls the addDefaultAssets method of the local AssettManager.

  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. }  


In this case, ANDROID_ROOT saves the / system path, while kSystem Assets saves the / system path.

  1. static const char* kSystemAssets = "framework/framework-res.apk";  


Remember what framework-res.apk is, all the resource files in the system.

At last, I understand that the principle is to load the resources of the system.


Next, look at the addAssetPath method. After entering the source code, you will find that it is also a local method and you need to look at the jni 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. }  

The addAssetPath method of the local AssetManager method is called here. Like system resources, they are loaded in.


Let's take a look at the process for PackageManager to get resources.

Getting resources in Package Manager calls the getResourcesForApplication method. getResourcesForApplication also has a method of the same name. Let's see the one doing business.

[java] view plain copy
 print?
  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. }  
The first step is to determine whether the package name is a system, if not to call the getTopLevelResources method of ActivityThread directly. However, it will pass in app.sourceDir based on the uid and process Id of the current application, if they are equal, or publicSourceDir based on the experience period, but sourceDir and publicSource are generally the same. The logic behind is the same as that in Context, so I won't talk about it here.

Posted by ialsoagree on Sat, 13 Jul 2019 11:29:27 -0700