Detailed analysis of the resource management mechanism in Android, android Resource Management
Respect Originality:Http://blog.csdn.net/yuanzeyao/article/details/42386549
In Android, all resources are stored in the res directory, including drawable, layout, strings, and anim. When we add any resource to the project, an id will be allocated to the resource in the R class, and we will access the resource through this id in the application. I believe that those who have developed Andorid will not be unfamiliar with this, so this is not what I want to talk about today. What I want to learn today is how Android manages resources. In the Android system, most resources are defined through xml files (drawable is an image), such as layout, string, and anim are all xml files, while xml files such as layout, anim, and strings only Parse xml files, read the specified value, but the parsing of controls in the layout file is complicated. For example, for a Button, you need to parse all its attribute values. How can this be achieved.
Here, we should first consider the question of how to define the attributes of a control? For example, what attributes does TextView have? Why can't I set TextView styles instead of android: theme? Where are the definitions of this information? To find out this question, you must obtain the answer from the source code project. I am using the android4.1 project. If you are using another version, so some access may be used.
First look at the three files
1. d: \ android4.1 \ frameworks \ base \ core \ res \ values \ attrs. xml
I see the attrs. xml file. Do you know what you think? When we customize the control, will we create an attrs. xml file? Use attrs. the purpose of the xml file is to add attributes to our custom control. After opening this directory, you will see a styleable named "Theme", as shown below (I only want to take part of it)
<declare-styleable name="Theme"> <!-- ============== --> <!-- Generic styles --> <!-- ============== --> <eat-comment /> <!-- Default color of foreground imagery. --> <attr name="colorForeground" format="color" /> <!-- Default color of foreground imagery on an inverted background. --> <attr name="colorForegroundInverse" format="color" /> <!-- Color that matches (as closely as possible) the window background. --> <attr name="colorBackground" format="color" />
In this file, most attributes that can be used in Android are defined. Here I am talking about "Definition" rather than "Declaration ", the biggest difference between the syntax with the same name is that the definition must have the format attribute while the Declaration does not have the format attribute.
2. d: \ android4.1 \ frameworks \ base \ core \ res \ values \ attrs_manifest.xml
The name of this file is very similar to the name of the above file, that is, there is an additional manifest, so the name alias defines the attributes in the AndroidManifest. xml file. There is a very important sentence here.
<attr name="theme" format="reference" />
Defines a theme attribute, which is the theme attribute we usually use on the Activity.
3. d: \ android4.1 \ frameworks \ base \ core \ res \ values \ themes. xml
This file begins to define a sytle called "Theme", as shown in the following (Part)
<style name="Theme"> <item name="colorForeground">@android:color/bright_foreground_dark</item> <item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item> <item name="colorBackground">@android:color/background_dark</item> <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item>
This is the Theme we usually use in the Application or Activity. From this we can see that Theme is also a style, so why can we always View/ViewGorup the style, theme can only be used for Activity or Application? Remember this question and we will answer it later
Let's integrate the content of these three files, first in attrs. in the xml file, most attributes of Android are defined. That is to say, attributes of all View/Activity in the future are defined here, then, a theme attribute is defined in attrs_manifest.xml, and its value is Theme defined in the themes file or the style inherited from "Theme.
With the above knowledge, let's analyze the two problems mentioned above:
1. Where are the attributes of the TextView control (the same for other controls) defined.
2. Since Theme is also a style, why can only View styles and Activity can only use theme?
Attribute definitions of all views are in the attrs. xml file, so let's look for the styleable of TextView in the attrs. xml file.
<declare-styleable name="TextView"> <!-- Determines the minimum type that getText() will return. The default is "normal". Note that EditText and LogTextBox always return Editable, even if you specify something less powerful here. --> <attr name="bufferType"> <!-- Can return any CharSequence, possibly a Spanned one if the source text was Spanned. --> <enum name="normal" value="0" /> <!-- Can only return Spannable. --> <enum name="spannable" value="1" /> <!-- Can only return Spannable and Editable. --> <enum name="editable" value="2" /> </attr> <!-- Text to display. --> <attr name="text" format="string" localization="suggested" /> <!-- Hint text to display when the text is empty. --> <attr name="hint" format="string" /> <!-- Text color. --> <attr name="textColor" />
I only intercepted some of the above attributes. Please note that all the attributes here are declared. You can search for this styleable, we will find that the theme attribute declaration is not found in the styleable of TextView, so it is ineffective to set the theme attribute for any view. See the following code to find out why.
Define an attrs. xml
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="MyTextView"> <attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr> </declare-styleable></resources>
Define a MyTextView
Public class MyTextView extends TextView {private static final String TAG = "MyTextView"; public MyTextView (Context context) {super (context);} public MyTextView (Context context, AttributeSet attrs) {super (context, attrs); // use TypeArray to read the custom attribute TypedArray ta = context. obtainStyledAttributes (attrs, R. styleable. myTextView); String value = ta. getString (R. styleable. myTextView_orientation); Log. d ("yzy", "value1 --->" + value); ta. recycle ();}}
In attrs. in xml, I defined an orientation attribute for MyTextView, and then read this attribute in the constructor of MyTextView. The TypeArray class is involved here. We found that TypeArray needs to be passed in to R. style. the value of MyTextView, which is an id provided by the system to access the styleable of MyTextView. When we need to get the value of orientation, we use R. style. myTextView_orientation is obtained. Because the theme attribute is not defined or declared in MyTextView, we cannot find R. styleable. the id of MyTextView_theme, so we cannot parse its theme attribute. Return to the styleable of TextView. Because the theme attribute is not defined in the styleable of TextView, theme is useless for TextView. Therefore, even if you add the theme attribute to TextView, this theme is ignored even if the compiler does not report an error to you.
Let's take a look at how Activity attributes are defined. Because Activity is defined in the AndroidManigest. xml file, we can search for it in attrs_manifest.xml.
<declare-styleable name="AndroidManifestActivity" parent="AndroidManifestApplication"> <!-- Required name of the class implementing the activity, deriving from {@link android.app.Activity}. This is a fully qualified class name (for example, com.mycompany.myapp.MyActivity); as a short-hand if the first character of the class is a period then it is appended to your package name. --> <attr name="name" /> <attr name="theme" /> <attr name="label" /> <attr name="description" /> <attr name="icon" /> <attr name="logo" /> <attr name="launchMode" /> <attr name="screenOrientation" /> <attr name="configChanges" /> <attr name="permission" /> <attr name="multiprocess" /> <attr name="process" /> <attr name="taskAffinity" /> <attr name="allowTaskReparenting" /> <attr name="finishOnTaskLaunch" /> <attr name="finishOnCloseSystemDialogs" /> <attr name="clearTaskOnLaunch" /> <attr name="noHistory" /> <attr name="alwaysRetainTaskState" /> <attr name="stateNotNeeded" /> <attr name="excludeFromRecents" /> <!-- Specify whether the activity is enabled or not (that is, can be instantiated by the system). It can also be specified for an application as a whole, in which case a value of "false" will override any component specific values (a value of "true" will not override the component specific values). --> <attr name="enabled" /> <attr name="exported" /> <!-- Specify the default soft-input mode for the main window of this activity. A value besides "unspecified" here overrides any value in the theme. --> <attr name="windowSoftInputMode" /> <attr name="immersive" /> <attr name="hardwareAccelerated" /> <attr name="uiOptions" /> <attr name="parentActivityName" /> </declare-styleable>
Obviously, theme is declared in styleable of Activity, so it can parse the theme attribute.
The above two questions have been answered. Next we will discuss another topic, that is, the Resources acquisition process.
In my other article, I have discussed this topic for a deeper understanding of Context. Here we will take a look at the resource acquisition process.
In the Android system, there are two main methods to obtain Resources: Get Resources by Context and get Resources by PackageManager.
First, let's take a look at the class diagram we get through Context. The following figure shows the class diagram of Context-related classes.
It can be seen that Context has two sub-classes: ContextWrapper and ContextImpl, while ContextWrapper depends on ContextImpl. Combined with the source code, we will find that Context is an abstract class, and its real implementation class is ContextImpl, while ContextWrapper is just like its name, just a layer of encapsulation of Context, its functions are completed by calling the attribute mBase. The mBase actually points to a variable of the ContextImpl type. When we get Resources, we call the getResources method of Context. Let's take a look at the getResources method of ContextImpl.
@Override public Resources getResources() { return mResources; }
We found that this method is very simple, that is, to return the mResources attribute. Where is this attribute assigned a value? Through searching, we found that we are actually creating ContextImpl, assign values by calling Init (for more information about the logic, see Context). here I will first show the sequence of the getResource method, and then trace the source code.
Start with the init method.
final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, Resources container, String basePackageName) { 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, "loaded context has different scaling. Using container's" + " compatiblity info:" + container.getDisplayMetrics()); } mResources = mainThread.getTopLevelResources( mPackageInfo.getResDir(), container.getCompatibilityInfo()); } mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); setActivityToken(activityToken); }
We found that the mResource value is assigned by calling getResource in LoadedApk and passing in the ActivityThead type parameter.
public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, this); } return mResources; }
In the getResources method, the getTopLevelResources method of ActivityThrad is called, and mResDir is the path of the apk file (for apps installed by users, this path is in an apk under/data/app). From the time sequence diagram, we can know that getTopLevelResources actually calls a method with the same name. Let's look at its method with the same name.
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); Resources r; synchronized (mPackages) { // Resources is app scale dependent. 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, "Returning cached resources " + r + " " + resDir + ": appScale=" + r.getCompatibilityInfo().applicationScale); } return r; } }; //if (r != null) { // Slog.w(TAG, "Throwing away out-of-date resources!!!! " // + r + " " + resDir); //} AssetManager assets = new AssetManager(); if (assets.addAssetPath(resDir) == 0) { return null; } //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(null, false); r = new Resources(assets, metrics, getConfiguration(), compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } synchronized (mPackages) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; if (existing != null && existing.getAssets().isUpToDate()) { // Someone else already created the resources while we were // unlocked; go ahead and use theirs. r.getAssets().close(); return existing; } // XXX need to remove entries when weak references go away mActiveResources.put(key, new WeakReference<Resources>(r)); return r; } }
The logic of this Code is not complex. First, the resource is obtained from mActiveResouuces through the key. If the resource is not null and is the latest, it is returned directly. Otherwise, an AssetManager object is created, call the addAssetPath method of AssetManager, and then use the created AssetManager as the parameter to create a Resources object, save and return. Through the time sequence diagram above, we found that when creating AssetManager, we call the init method in its constructor. Let's see what the init method has done.
private native final void init();
It is actually a local method, so we only have to look at the corresponding Jni code.
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz){ AssetManager* am = new AssetManager(); if (am == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", ""); return; } am->addDefaultAssets(); ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);}
In this example, the addDefaultAssets method of the local AssetManager is called.
bool AssetManager::addDefaultAssets(){ const char* root = getenv("ANDROID_ROOT"); LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); String8 path(root); path.appendPath(kSystemAssets); return addAssetPath(path, NULL);}
In this example, ANDROID_ROOT stores the/system path, while kSystemAssets is
static const char* kSystemAssets = "framework/framework-res.apk";
Do you still remember what framework-res.apk is? It is all the resource files in the system.
The principle is to load system resources.
Next, let's take a look at the addAssetPath method. After Entering the source code, you will find that it is also a local method. You also need to check the jni code.
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, jstring path){ ScopedUtfChars path8(env, path); if (path8.c_str() == NULL) { return 0; } AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } void* cookie; bool res = am->addAssetPath(String8(path8.c_str()), &cookie); return (res) ? (jint)cookie : 0;}
Here, the addAssetPath method of the local AssetManager method is called. Like system resources, these resources are loaded in.
Let's take a look at the PackageManager's process of obtaining resources.
In PackageManager, The getResourcesForApplication method is used to obtain resources. getResourcesForApplication also has a method with the same name. Let's look at the method to handle the problem,
@Override public Resources getResourcesForApplication( ApplicationInfo app) throws NameNotFoundException { if (app.packageName.equals("system")) { return mContext.mMainThread.getSystemContext().getResources(); } Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir, mContext.mPackageInfo); if (r != null) { return r; } throw new NameNotFoundException("Unable to open " + app.publicSourceDir); }
First, determine whether the package name is system. If not, directly call the getTopLevelResources method of ActivityThread. However, the uid of the current application is equal to the process Id. sourceDir. Otherwise, publicSourceDir is passed in, But sourceDir and publicSource are generally the same in the empirical period. The logic behind it is the same as that in Context.