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阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 插件化-资源处理 写的比较长,可以选择跳过前面2节,直接从0x03实例分析开始。如有错误,请不吝指正。 0x00 ...
    唐一川阅读 5,312评论 2 22
  • 给定一个相同的资源ID,在不同的设备配置之下,查找到的可能是不同的资源。这个资源查找过程对应用程序来说,是完全透明...
    小爨阅读 2,504评论 1 9
  • 每个apk有一个Resources getTopLevelResources synchronized (thi...
    第八区阅读 1,002评论 0 0
  • 周六的日子啊!每个人都会懂得~ 昨天下了一夜雨,早上还在下,杨不舒服,简单吃了早饭后,我们整个上午都是在床上度过的...
    冰点晚安阅读 93评论 0 0
  • 最近几次回老家办事,常常只赶得上晚上的绿皮火车。 现在人们出行,不是飞机就是高铁。快速出行、节约时间、高效运转,这...
    云紫烟阅读 229评论 0 1