Android6.0之App中的資源管理對象創建

Android與資源管理相關的類Resouces和AssetManager很有必要清楚他們的創建過程。

與資源查找與加載操作相關的類

資源查找與加載主要是靠Android資源管理框架來完成的,而Android資源管理框架實際是由Resources和AssetManager兩個類來實現的。

其中,Resources類可以根據ID來查找資源,而AssetManager類根據文件名來查找資源。

事實上,從資源查找的過程來看,它們可以歸結為兩大類。第一類資源是不對應有文件的,而第二類資源是對應有文件的,例如,字符串資源是直接編譯在resources.arsc文件中的,而界面布局資源是在APK包里面是對應的單獨的文件的。

如果一個資源ID對應的是一個文件,那么Resources類是先根據ID來找到資源文件名稱,然后再將該文件名稱交給AssetManager類來打開對應的文件的。基本流程如下圖:

resources-3.jpg

而且這兩個類中都有相應的緩存機制,用來緩存資源,以便下次在使用的時候,不需要在查找與加載。

App的Resources對象創建過程

既然這兩個類負責管理Android的資源,那么接下就要搞清楚這兩個類的對象在app中的創建過程以及何時創建。

Resources中會包含一個AssetManager對象,先重點關注Resources對象的創建過程,先看一張整體時序圖:

resources-4.jpg

其中在LoadedApk中會緩存創建好的resources對象。,而創建context時,LoadedApk是同一個,所以在同一應用中不同的ContextImpl獲取到的是同一套資源。

ActivityThread類的成員變量mActiveResources指向的是一個HashMap。這個HashMap用來維護在當前應用程序進程中加載的每一個Apk文件及其對應的Resources對象的對應關系。

也就是說,給定一個Apk文件路徑,ActivityThread類的成員函數getTopLevelResources可以在成員變量mActiveResources中檢查是否存在一個對應的Resources對象。如果不存在,那么就會新建一個,并且保存在ActivityThread類的成員變量mActiveResources中。

Context中提供了getResources()方法用來獲取resources對象,所以在Activity中可以方便的獲取該對象:

Resources res =  getResources();

Activity中的context實際是對ComtextImpl的封裝,所以最終是通過ContextImpl.getResources()獲取resources對象的:

public Resources getResources() {
      return mResources;
}

而mResources是ContextImpl的一個屬性成員。且mResources是在ContextImpl的構造方法中被初始化的。

看看ContextImpl的構造方法:

private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
..............
      Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
    // 不會走此分支,因為6.0中還不支持多屏顯示,雖然已經有不少相關代碼了,7.0以及正式支持多屏操作了
    if (displayId != Display.DEFAULT_DISPLAY
            || overrideConfiguration != null
            || (compatInfo != null && compatInfo.applicationScale
                    != resources.getCompatibilityInfo().applicationScale)) {
      ......
    }
}
mResources = resources;
..........

從ContextImpl構造方法中發現,通過傳入的LoadedApk對象的getResources()方法獲取Resources對象:

public Resources getResources(ActivityThread mainThread) {
        // 緩存機制,如果LoadedApk中的mResources已經初始化則直接返回,
        // 否則通過ActivityThread創建resources對象
       if (mResources == null) {
           mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                   mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
       }
       return mResources;
   }

LoadedApk.getResources()方法中首先判斷其mResources是否為null,為null時又是調用ActivityThread.getTopLevelResources()方法來獲取Resources對象的。

這里要說明一下,LoadedApk類有兩個構造方法:一個是給 system app使用的,一個是給普通app使用的。對于給普通app使用的構造方法中并沒有初始化mResources的值,在給system app的LoadedApk使用的構造方法中是初始化mResources值為Resources.getSystem()。

這里我們只關心普通app的LoadedApk對象創建時時沒有初始化mResources對象的。然后當第一次調用LoadedApk對象的getResources()方法時,便會通過調用下面的方法創建一個Resources對象,并緩存起來,以后再通過LoadedApk.getResources()獲取時,不需要重新創建Resources對象了,直接返回之前創建的即可。

   /**
    * Creates the top level resources for the given package.
    */
   Resources getTopLevelResources(
           String resDir,//app資源文件夾路徑,實際上是apk文件的路徑,如/data/app/包名/base.apk
           String[] splitResDirs, //針對一個app由多個apk組成(將原本一個apk切片為若干apk)時,每個子apk中的資源文件夾
           String[] overlayDirs,
           String[] libDirs, //app依賴的共享jar/apk路徑
           int displayId,
           Configuration overrideConfiguration,
           LoadedApk pkgInfo //代表運行的app
           ) {
       return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
               displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
   }

ActivityThread.getTopLevelResources()方法又是通過ResourcesManager類的getTopLevelResources()方法創建Resources對象的:

/**
 * Creates the top level Resources for applications with the given compatibility info.
 *
 * @param resDir the resource directory.
 * @param splitResDirs split resource directories.
 * @param overlayDirs the resource overlay directories.
 * @param libDirs the shared library resource dirs this app references.
 * @param displayId display Id.
 * @param overrideConfiguration override configurations.
 * @param compatInfo the compatibility info. Must not be null.
 */
Resources getTopLevelResources(String resDir, String[] splitResDirs,
        String[] overlayDirs, String[] libDirs, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
          ....................
          // 以apk路徑為參數創建key
          ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
          Resources r;
          synchronized (this) {
            // Resources is app scale dependent.
            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);

            // 檢查該apk對應的resources對象是否已經存在
            WeakReference<Resources> wr = mActiveResources.get(key);
            r = wr != null ? wr.get() : null;
            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
            if (r != null && r.getAssets().isUpToDate()) {
                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale
                        + " key=" + key + " overrideConfig=" + overrideConfiguration);
                return r;
            }
        }
          // 創建一個AssetManager對象
          AssetManager assets = new AssetManager();

          // 將app中的資源路徑都加入到AssetManager對象中
       if (resDir != null) {
           if (assets.addAssetPath(resDir) == 0) {
               return null;
           }
       }

       if (splitResDirs != null) {
           for (String splitResDir : splitResDirs) {
               if (assets.addAssetPath(splitResDir) == 0) {
                   return null;
               }
           }
       }

       if (overlayDirs != null) {
           for (String idmapPath : overlayDirs) {
               assets.addOverlayPath(idmapPath);
           }
       }

       if (libDirs != null) {
           for (String libDir : libDirs) {
              // 僅僅選擇共享依賴中的apk,因為jar中不會有資源文件
               if (libDir.endsWith(".apk")) {
                   if (assets.addAssetPath(libDir) == 0) {
                       Log.w(TAG, "Asset path '" + libDir +
                               "' does not exist or contains no resources.");
                   }
               }
           }
       }
................
        r = new Resources(assets, dm, config, compatInfo);
...............
 mActiveResources.put(key, new WeakReference<>(r));
return r
}

創建Resources對象時,會創建AssetManager對象并向其添加app資源路徑的過程。

現在可以回答何時創建Resources對象了:

當普通app對應的LoadedApk對象第一次調用LoadedApk.getResources()方法時,由于LoadedApk中還沒有緩存,會創建這個對象并緩存。后續再次調用LoadedApk.getResources()方法時,因為緩存了,不會再創建Resources對象。

創建ContextImpl對象時,并不一定會新創建Resources對象,通常一個運行著的app的Resources對象是只會創建一次的。并且緩存到LoadedApk對象中。一個運行著的app可以有多個Context,但是每個Context中都包含了同一個LoadedApk對象。

管理系統資源的Resources對象

Resources類中的關鍵數據成員有:

// 緩存zygote中預加載的資源
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;

private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
          = new LongSparseArray<>();

private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
          sPreloadedColorStateLists = new LongSparseArray<>();



// 對系統Resources實例的引用
static Resources mSystem = null;

// drawable和ColorStateList的緩存
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
        new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
        new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
        new ConfigurationBoundResourceCache<>(this);

// xml文件的緩存
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];

// AssetManager實例的引用,很關鍵.
final AssetManager mAssets;

對于上述static類型的關鍵成員,在內存中只存在一份,所以一定要知道這些static成員是何時何地被初始化的.

以mSystem為例,如下所示:

在ZygoteInit的preloadResources()方法中初始化的mSystem值,也就是說mSystem是由Zygote進程初始化的,那么由其孵化的App進程肯定就繼承了這個東東了.

mSystem主要負責預加載Android系統本身提供的資源(framework-res.apk).加載完之后的資源存放在其他static成員變量中,以后App可以通過他們訪問系統資源.

framework-res.apk系統資源在zygote進程啟動時被加載的只是其中的一部分,不是加載所有資源。會加載的資源是在Framework里的res/values/arrays.xml中定義的,例如Leaner等布局資源。

對于那些非“預加載”的系統資源則認為不會被緩沖到靜態列表變量中,在這種情況下,多個應用進程如果需要一個非預裝載的資源,則會在各自的進程中保持一個資源的緩沖。

其實在App創建Resources對象時,會將framework-res.apk加入到其中,這樣能夠訪問沒有預加載的資源.

AssetManager對象的創建過程

通過前面的分析可知,Android系統中實際對資源的管理是AssetManager類.每個Resources對象都會關聯一個AssetManager對象,Resources將對資源的操作大多數委托給了AssetManager。

另外還會存在一個native層的AssetManager對象與java層的這個AssetManager對象相對應,而這個native層AssetManager對象在內存的地址存儲在java層的AssetManager.mObject中。所以在java層AssetManager的jni方法中可以快速找到它對應的native層的AssetManager對象。

創建普通app的AssetManager對象的過程如下所示:

創建AssetManager對象時,默認會把system/framework/framework-res.apk通過addAssetPath()方法加入到native層的AssetManager對象中。

AssetManager類有兩個構造方法:

一個是App創建Resources對象時,用到的public類型的構造方法:

public AssetManager() {
      synchronized (this) {
          if (DEBUG_REFS) {
              mNumRefs = 0;
              incRefsLocked(this.hashCode());
          }
          init(false);
          if (localLOGV) Log.v(TAG, "New asset manager: " + this);
          ensureSystemAssets();
      }
  }

一個是創建管理預加載的系統資源的Resources對象時,用到的private類型的構造方法:

private AssetManager(boolean isSystem) {
   if (DEBUG_REFS) {
       synchronized (this) {
           mNumRefs = 0;
           incRefsLocked(this.hashCode());
       }
   }
   init(true);
   if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}

構造方法中都會調用init()方法,這是一個native方法:

源碼路徑:

 frameworks/base/core/jni/android_util_AssetManager.cpp
 frameworks/base/libs/androidfw/AssetManager.cpp
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
    if (isSystem) {
        verifySystemIdmaps();
    }
    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->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

在init()方法中創建一個native層中的AssetManager對象.

其中addDefaultAssets()方法將system/framework/framework-res.apk通過addAssetPath()方法加入到native層的AssetManager對象中.


bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    // framework/framework-res.apk
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

addAssetPath()方法其實很簡單主要是將要加入的資源路徑加入到 AssetManager類的成員mAssetPaths中,當mAssetPaths中包含這個資源路徑時,不會再次加入。也就是說同一種資源是不會被重復加載的。

通過以上代碼可知在創建一個Java層的AssetManager對象時,會創建一個native層的AssetManager對象,并把system/framework/framework-res.apk加入到資源路徑集合mAssetPaths中去。

而且在前面介紹getTopLevelResources()時,也可以看到,當創建AssetManager對象之后,還會把app的資源路徑,也就是apk路徑通過ddAssetPath()方法加入到native層的AssetManager對象的mAssetPaths中去。

但是到這里為止,卻還沒有發現android對resources.arsc有任何操作,不要著急,繼續往下看。

在看Resources類的構造方法:

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
         CompatibilityInfo compatInfo) {
     mAssets = assets;
     mMetrics.setToDefaults();
     if (compatInfo != null) {
         mCompatibilityInfo = compatInfo;
     }
     updateConfiguration(config, metrics);
     assets.ensureStringBlocks();
 }

Resources類的構造函數首先將參數assets所指向的一個AssetManager對象保存在成員變量mAssets中,以便以后可以通過它來訪問應用程序的資源,接下來調用另外一個成員函數updateConfiguration來設置設備配置信息,最后調用參數assets所指向的一個AssetManager對象的成員函數ensureStringBlocks來創建字符串資源池。

AssetManager類的成員函數ensureStringBlocks首先檢查成員變量mStringBlocks的值是否等于null。如果等于null的話,那么就說明當前應用程序使用的資源表中的資源項值字符串資源池還沒有讀取出來,這時候就會調用另外一個成員函數makeStringBlocks來進行讀取。

整個過程大致過程如下所示:

在上圖中第九步中就會處理mAssetPaths路徑中apk內的resources.arsc,并把結果緩存起來。其中ResTable類負責資源管理框架中加載resources.arsc,一個ResTable可以管理app中所有的resources.arsc。

先來看java層AssetManager中的ensureStringBlocks()方法:

final void ensureStringBlocks() {
        if (mStringBlocks == null) {
            synchronized (this) {
                if (mStringBlocks == null) {
                    makeStringBlocks(sSystem.mStringBlocks);
                }
            }
        }
    }

其中sSystem是AssetManager中的一個靜態屬性成員:

static AssetManager sSystem = null;
private StringBlock mStringBlocks[] = null;

前面介紹了,zygote啟動的時候,會初始化該變量,該變量指向的AssetManager對象是用來管理系統資源的。而且mStringBlocks會被初始化為系統資源中字符串值池的個數。因為系統資源為framework-res.apk,其內部只有一個resources.arsc,所以只有一個字符串資源值池。StringBlocks個數也可以理解為加載的resources.arsc的個數。

 final void makeStringBlocks(StringBlock[] seed) {
       // 系統預加載的resources.arsc的數量
       final int seedNum = (seed != null) ? seed.length : 0;
       // 這是個jni方法,該方法很重要
       // 該方法中回去打開前面加入到native層AssetManager.mAssetPaths中的apk中的resources.arsc
       // 至少返回2,系統資源+app自己的資源 的resources.arsc
       // 返回的個數包含了seedNum
       final int num = getStringBlockCount();
       mStringBlocks = new StringBlock[num];
       if (localLOGV) Log.v(TAG, "Making string blocks for " + this
               + ": " + num);
       for (int i=0; i<num; i++) {
           if (i < seedNum) {
                // 系統預加載資源時,已經解析過framework-res.apk中的resources.arsc,并且存儲在AssetManager類中靜態變量sSystem中的mStringBlocks
                // 所以直接 賦值
               mStringBlocks[i] = seed[i];
           } else {
                // 除了系統預加載的之外,剩下的都是沒加載的,
                // 所以 getNativeStringBlock
               mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
           }
       }
   }

這里要重點分析getStringBlockCount()和getNativeStringBlock()這兩個jni方法。

首先分析getStringBlockCount():

static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
{
    // 得到與java層AssetManager對象對應的natvie層AssetManager對象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    return am->getResources().getTableCount();
}

代碼很簡單,調用natvie層AssetManager的getResources()方法:

const ResTable& AssetManager::getResources(bool required) const
{
    const ResTable* rt = getResTable(required);
    return *rt;
}

先看看這個ResTable類中的屬性成員:

class ResTable
{
  mutable Mutex               mLock;

  status_t                    mError;、
  // 配置相關
  ResTable_config             mParams;

  // 這個Header可以理解為resources.arsc中的資源索引表頭部+字符串值池
  // 也就是resources.arsc中package之前的數據
  // 那么這里的mHeaders是一個數組,也就是ResTable是可以包含多個resources.arsc.(至少兩個嘛)
  // 所有的resources.arsc的package之前的數據都存儲在這數組里
  Vector<Header*>             mHeaders;

  // 每一個resources.arsc里面的所有Pacakge形成一個PackageGroup
  // 這個數組中存儲ResTable中加載的所有的resources.arsc中的PackageGroup
  Vector<PackageGroup*>       mPackageGroups;

  // package  ID 對應的pacakage所在的PackageGroup 在mPackageGroups數組中的索引
  uint8_t                     mPackageMap[256];

  uint8_t                     mNextPackageId;
}

再來看看Header:

struct ResTable::Header
{
    Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
        resourceIDMap(NULL), resourceIDMapSize(0) { }

    ~Header()
    {
        free(resourceIDMap);
    }

    // 所在的ResTable對象
    const ResTable* const           owner;
    void*                           ownedData;
    // 資源索引表頭部
    const ResTable_header*          header;
    size_t                          size;
    const uint8_t*                  dataEnd;
    size_t                          index;
    int32_t                         cookie;
    // 用來操作字符串值池
    ResStringPool                   values;
    uint32_t*                       resourceIDMap;
    size_t                          resourceIDMapSize;
};

再看PackageGroup:

struct ResTable::PackageGroup{

      // 所在的Restable對象
      const ResTable* const           owner;
      String16 const                  name;
      uint32_t const                  id;

      // 這個resources.arsc中包含的package,一般來說只有一個
      Vector<Package*>                packages;

      ........
}
struct ResTable::Package
{
    // 所在ResTable對象
    const ResTable* const           owner;
    // 他的header
    const Header* const             header;
    // resources.arsc中的數據塊起始處
    const ResTable_package* const   package;

    // 類型字符串池
    ResStringPool                   typeStrings;
    // 資源項名稱字符串池
    ResStringPool                   keyStrings;

    size_t                          typeIdOffset;

也就是說Restable類負責管理app要使用的所有的resouces.arsc.后續的操作都是對Restable的mHeades和mPackageGroups的操作,將解析的resoiurces.arsc相應數據分別存儲在mHeades和mPackageGroups。

那么接下來看getResTable()方法,其傳入的參數為true:


const ResTable* AssetManager::getResTable(bool required) const
{
    // 一個native層的AssetManager對象只包含一個ResTable對象,保存在mResources中
    // 如果已經創建,那么直接返回
    ResTable* rt = mResources;
    if (rt) {
        return rt;
    }

    ............
    // 創建ResTable對象
    mResources = new ResTable();
    updateResourceParamsLocked();
    bool onlyEmptyResources = true;
    const size_t N = mAssetPaths.size();
    for (size_t i=0; i<N; i++) {
        // mAssetPaths中存儲了此app中用到的資源包路徑,包括系統資源包路徑
        // 現在開始打開這些資源包,也就是apk中的resources.arsc
        // 與resources.arsc關聯的是Asset對象,會將Asset對象加入到mResources中,
        // 加入的過程中會解析resources.arsc
        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
        onlyEmptyResources = onlyEmptyResources && empty;
    }

    if (required && onlyEmptyResources) {
        ALOGW("Unable to find resources file resources.arsc");
        delete mResources;
        mResources = NULL;
    }

    return mResources;
}

核心邏輯在appendPathToResTable()方法中,不過這里要注意一點,因為mAssetPaths包括系統資源包路徑,而系統資源包已經在zygote啟動時加載了,所以其reources.arsc不需要再此被加載了。

appendPathToResTable()肯定會對系統資源包做特殊處理,參數是資源包路徑。

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // skip those ap's that correspond to system overlays
    if (ap.isSystemOverlay) {
        return true;
    }

    Asset* ass = NULL;
    ResTable* sharedRes = NULL;
    bool shared = true;
    bool onlyEmptyResources = true;
    MY_TRACE_BEGIN(ap.path.string());
    // 資源覆蓋機制,暫不考慮
    Asset* idmap = openIdmapLocked(ap);
    size_t nextEntryIdx = mResources->getTableCount();
    ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
    // 資源包路徑不是一個文件夾,那就是一個apk文件了
    if (ap.type != kFileTypeDirectory) {
        // 對于app來說,第一次執行時,肯定為0,因為mResources剛創建,還沒對其操作
        // 下面的分支 指揮在參數是系統資源包路徑時,才執行,
        // 而且系統資源包路徑是首次被解析的
        // 第二次執行appendPathToResTable,nextEntryIdx就不會為0了
        if (nextEntryIdx == 0) {
            // mAssetPaths中存儲的第一個資源包路徑是系統資源的路徑,
            // 即framework-res.apk的路徑,它在zygote啟動時已經加載了
            // 可以通過mZipSet.getZipResourceTable獲得其ResTable對象
            sharedRes = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTable(ap.path);
            // 對于APP來說,肯定不為NULL
            if (sharedRes != NULL) {
                // 得到系統資源包路徑中resources.arsc個數
                nextEntryIdx = sharedRes->getTableCount();
            }
        }
        // 當參數是mAssetPaths中除第一個以外的其他資源資源包路徑,
        // 比如app自己的資源包路徑時,走下面的邏輯
        if (sharedRes == NULL) {
            // 檢查該資源包是否被其他進程加載了,這與ZipSet數據結構有關,后面在詳細介紹
            ass = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTableAsset(ap.path);
            // 對于app自己的資源包來說,一般都會都下面的邏輯
            if (ass == NULL) {
                ALOGV("loading resource table %s\n", ap.path.string());
                // 創建Asset對象,就是打開resources.arsc
                ass = const_cast<AssetManager*>(this)->
                    openNonAssetInPathLocked("resources.arsc",
                                             Asset::ACCESS_BUFFER,
                                             ap);
                if (ass != NULL && ass != kExcludedAsset) {
                    ass = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                }
            }
            // 只有在zygote啟動時,才會執行下面的邏輯
            // 為系統資源創建 ResTable,并加入到mZipSet里。
            if (nextEntryIdx == 0 && ass != NULL) {
                // If this is the first resource table in the asset
                // manager, then we are going to cache it so that we
                // can quickly copy it out for others.
                ALOGV("Creating shared resources for %s", ap.path.string());
                // 創建ResTable對象,并把前面與resources.arsc關聯的Asset對象,加入到這個ResTabl中
                sharedRes = new ResTable();
                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
                const char* data = getenv("ANDROID_DATA");
                LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
                String8 overlaysListPath(data);
                overlaysListPath.appendPath(kResourceCache);
                overlaysListPath.appendPath("overlays.list");
                addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else {
        ALOGV("loading resource table %s\n", ap.path.string());
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
        shared = false;
    }

    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
        // 系統資源包時
        if (sharedRes != NULL) {
            ALOGV("Copying existing resources for %s", ap.path.string());
            mResources->add(sharedRes);
        } else {
          // 非系統資源包時,將與resources.arsc關聯的Asset對象加入到Restable中
          // 此過程會解析resources.arsc文件。
            ALOGV("Parsing resources for %s", ap.path.string());
            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
        }
        onlyEmptyResources = false;

        if (!shared) {
            delete ass;
        }
    } else {
        ALOGV("Installing empty resources in to table %p\n", mResources);
        mResources->addEmpty(nextEntryIdx + 1);
    }

    if (idmap != NULL) {
        delete idmap;
    }
    MY_TRACE_END();

    return onlyEmptyResources;
}

從上述代碼可以看出,實際解析resources.arsc文件的是ResTable的add()方法。

status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
   // 得到resources.arsc在mmap之后內存中的地址
    const void* data = asset->getBuffer(true);
    ...........
    return addInternal(data, static_cast<size_t>(asset->getLength()),
            idmapData, idmapSize, cookie, copyData);
}

解析大體過程如下:

ResTable類的成員函數add在增加一個Asset對象(非系統資源包)時,會對該Asset對象所描述的resources.arsc文件的內容進行解析,結果就是得到一個系列的Package信息。每一個Package又包含了一個資源類型字符串資源池和一個資源項名稱字符串資源池,以及一系列的資源類型規范數據塊和一系列的資源項數據塊。

還要注意的是,每一個資源包里面的所有Pacakge形成一個PackageGroup,保存ResTable對象的成員變量mPackageGroups中。

通過前面分析可知,ResTable類的成員函數add首先添加的是系統資源包,由于系統資源包已經解析過了,也就是說有Restable對象與之關聯,那么這個add方法簡單了,直接將系統資源的Restable對象中的mHeaders和mPackageGroups拷貝到這個新創建的Restable對象中即可。不需要解析了。

status_t ResTable::add(ResTable* src)
{
    mError = src->mError;

    // 將系統資源的Restable對象中的mHeader數組中的header都拷貝出來
    for (size_t i=0; i<src->mHeaders.size(); i++) {
        mHeaders.add(src->mHeaders[i]);
    }
      // 將系統資源的Restable對象中的mPackageGroups數組中的PackageGroup也都拷貝出來
    for (size_t i=0; i<src->mPackageGroups.size(); i++) {
        PackageGroup* srcPg = src->mPackageGroups[i];
        PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
        for (size_t j=0; j<srcPg->packages.size(); j++) {
            pg->packages.add(srcPg->packages[j]);
        }

        for (size_t j = 0; j < srcPg->types.size(); j++) {
            if (srcPg->types[j].isEmpty()) {
                continue;
            }

            TypeList& typeList = pg->types.editItemAt(j);
            typeList.appendVector(srcPg->types[j]);
        }
        pg->dynamicRefTable.addMappings(srcPg->dynamicRefTable);
        pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
        mPackageGroups.add(pg);
    }

    memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));
    return mError;
}

那么現在再次來分析getStringBlockCount()就很簡單了:

static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
{
    // 得到與java層AssetManager對象對應的natvie層AssetManager對象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    // 獲得ResTable對象
    // 然后調用ResTable的getTableCount()
    // 返回 mHeaders.size(),也就是resources.arsc的個數
    // 包括系統資源對應的resources.arsc
    return am->getResources().getTableCount();
}

再次來看makeStringBlocks:

final void makeStringBlocks(StringBlock[] seed) {
        final int seedNum = (seed != null) ? seed.length : 0;
        // num包括了seedNum
        final int num = getStringBlockCount();
        // StringBlock實際上用來操作resources.arsc的字符串值池
        mStringBlocks = new StringBlock[num];
        if (localLOGV) Log.v(TAG, "Making string blocks for " + this
                + ": " + num);
        for (int i=0; i<num; i++) {
            if (i < seedNum) {
                // 系統已經加載了,直接賦值
                mStringBlocks[i] = seed[i];
            } else {
                // 其他的調用getNativeStringBlock
                mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
            }
        }
    }
static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
                                                           jint block)
{
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block));
}

const ResStringPool* ResTable::getTableStringBlock(size_t index) const
{
    return &mHeaders[index]->values;
}

getTableStringBlock很簡單就是獲得reouces.arsc的字符串池。然后用來構造StringBlock對象。

最后總結下,當Resources對象創建后,就解析好了其所需要的所有的資源包的resources.arsc,并且這些resources.arsc的所有字符串池已經被保存在了與Resources對象關聯的 java層AssetManager對象的mStringBlocks數組中。

到現在為止已經搞清楚Resources和AssetManager對象的創建過程,何時解析resouces.arsc,以及解析后的數據存放在哪里了。

這為理解通過資源ID查找資源就打下了堅實基礎。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容

  • 插件化-資源處理 寫的比較長,可以選擇跳過前面2節,直接從0x03實例分析開始。如有錯誤,請不吝指正。 0x00 ...
    唐一川閱讀 5,368評論 2 22
  • 給定一個相同的資源ID,在不同的設備配置之下,查找到的可能是不同的資源。這個資源查找過程對應用程序來說,是完全透明...
    小爨閱讀 2,531評論 1 9
  • 每個apk有一個Resources getTopLevelResources synchronized (thi...
    第八區閱讀 1,028評論 0 0
  • 周六的日子啊!每個人都會懂得~ 昨天下了一夜雨,早上還在下,楊不舒服,簡單吃了早飯后,我們整個上午都是在床上度過的...
    冰點晚安閱讀 104評論 0 0
  • 最近幾次回老家辦事,常常只趕得上晚上的綠皮火車。 現在人們出行,不是飛機就是高鐵??焖俪鲂?、節約時間、高效運轉,這...
    云紫煙閱讀 240評論 0 1