Android中資源管理機制詳細分析,android資源管理

來源:互聯網
上載者:User

Android中資源管理機制詳細分析,android資源管理

尊重原創: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,如下(我只截取部分)

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


在這個檔案中,定義了Android中大部分可以使用的屬性,這裡我說的是“定義”而不是“聲明”,同名在文法上面最大的區別就是定義要有format屬性,而聲明沒有format屬性。

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

這個檔案的名字和上面的檔案的名字很像,就是多了一個manifest,故名思議就是定義了AndroidManifest.xml檔案中的屬性,這裡面有一個很重要的一句話

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

定義了一個theme屬性,這個就是我們平時在Activity上面使用的theme屬性

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

這個檔案開始定義了一個叫做"Theme" 的sytle,如下(部分)

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

這個就是我們平時在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-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" />


上面的屬性我只截取了部分,請注意,這裡所有的屬性都是進行“聲明”,你去搜尋這個styleable,會發現在TextView的styleable中不會找到theme這個屬性的聲明,所以你給任何一個view設定theme屬性是沒有效果的。請看下面一段代碼就知道為什麼了。

定義一個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>
定義一個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);    //利用TypeArray讀取自訂的屬性    TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MyTextView);    String value=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-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>


很明顯,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方法吧

 @Override    public Resources getResources() {        return mResources;    }


我們發現這個方法很簡單,就是返回mResources屬性,那麼這個屬性是在哪裡 賦值的呢,通過尋找發現,其實就是在建立ContextImpl,通過調用Init進行賦值的(具體邏輯參照《更深層次理解Context》).這裡我先給出getResource方法的時序圖,然後跟蹤源碼。


先從init方法開始吧

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);    }


我們發現,對mResource進行賦值,是通過調用LoadedApk中的getResource進行的,傳入了ActivityThead類型的參數

  public Resources getResources(ActivityThread mainThread) {        if (mResources == null) {            mResources = mainThread.getTopLevelResources(mResDir, this);        }        return mResources;    }

在getResources方法中,其實就是調用了ActivityThrad的getTopLevelResources方法,其中mResDir就是apk檔案的路徑(對於使用者安裝的app,此路徑就在/data/app下面的某一個apk),從時序圖中可以知道,getTopLevelResources其實就是調用了一個同名方法,我們直接看它的同名方法吧

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

這段代碼的邏輯不複雜,首先從mActiveResouuces中通過key拿到資源,如果資源不為null,並且是最新的,那麼直接返回,否則建立一個AssetManager對象,並調用AssetManager的addAssetPath方法,然後使用建立的AssetManager為參數,建立一個Resources對象,儲存並返回。通過上面的時序圖,我們發現在建立AssetManager的時候,在其建構函式中調用init方法,我們看看init方法做了什麼吧

private native final void init();


居然是一個本地方法,那麼我們只有看看對應的Jni代碼了

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);}

這個裡面調用了本地的AssetManager的addDefaultAssets方法

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);}


這例的ANDROID_ROOT儲存的就是/system路徑,而kSystemAssets是 

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


還記得framework-res.apk是什麼嗎,就是系統所有的資源檔。

到這裡終於明白了,原理就是將系統的資源載入進來。


接下來看看addAssetPath方法吧,進入源碼後,你會發現它也是一個本地方法,也需要看jni代碼

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

這裡調用了本地AssetManager方法的addAssetPath方法。和系統資源一樣,都被載入進來了。


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

在PackageManager裡面擷取資源調用的是getResourcesForApplication方法,getResourcesForApplication也有一個同名方法,我們看辦正事的那個吧,

    @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);    }
首先判斷包名是否是system,如果不是那麼直接調用ActivityThread的getTopLevelResources方法。不過這裡會根據當前應用的應用的uid和進程Id相等,如果相等則傳入app.sourceDir,否則傳入publicSourceDir,但是根據經驗時期sourceDir和publicSource一般情況下是相同的。後面的邏輯和Context中的是一樣的,這裡就不在說了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.