Android Framework | 资源管理 | 资源初始化

564 阅读17分钟

资源初始化整体架构

Android 应用程序资源初始化/资源加载,主要包括以下的几个系统的组件,如 ContextImplResourcesImpl,Java 层和 Native 层的 AssetManagerApkAssets 等。它们之间的关系如下:

resource_init_1.png

  1. ContextImpl 是应用程序加载资源的入口
  2. ContextImpl 会间接持有 AssetManager,而AssetManager会持有/管理多个 ApkAssets, 其中 app 进程本身所加载的 apk 的 ApkAssets,也会包含系统的资源包 (framework-res.apk) 的 ApkAssets, 如果有 RRO (Runtime Resources Overlay) apk 覆盖这个 apk 的话,还会包含在 RRO Apk 的 ApkAsset
  3. ApkAssets 标识一个 Apk 所有的资源集合, 包括 res/ , assets/ 和 resources.arsc

资源初始化流程分析

创建 ContextImpl

Android App 的资源初始化发生 Activity 和 Service 等组件创建之前,具体发生在 handleBindApplication, 在这个过程中会创建 ContextImpl

private void handleBindApplication(AppBindData data) {
    ...
    // getPackageInfo 会创建一个 LoadedApk 对象,并且赋值给 data.info
    data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
                false /* securityViolation */, true /* includeCode */,
                false /* registerPackage */, isSdkSandbox);
    ...
    // 使用上一步创建的 LoadedApk 对象实例化一个 ContextImpl
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    ...
    
}

handleBindApplication 中, 通过 getPackageInfo 获取到 App 的安装信息,然后再创建 ContextImpl 这个对象,接下来继续分析 getPackageInfo

创建 LoadedApk

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage, boolean isSdkSandbox) {
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            ...
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            ...
            // LoaderApk 对象包含了当前加载的 apk 的所有信息,如 mPackageName,mApplicationInfo,mOverlayPaths, mBaseClassLoader 等等
            packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode
                            && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
            ...
            return packageInfo;
        }
    }

getPackageInfo 中创建了一个 LoadedApk 对象,这个对象包含了当前 apk 的所有安装信息,如 mPackageNamemApplicationInfomOverlayPathsmBaseClassLoader 等等。接下来创建 ContextImpl,并且把 LoadedApk 作为参数传入。

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        // 真正创建 ContextImpl
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
                ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName,
                DEVICE_ID_DEFAULT, false);
        // 通过 LoadedApk 去获取资源,并且由 ContextImpl 持有这个资源
        context.setResources(packageInfo.getResources());
        return context;
    }
  1. 通过 LoadedApk.getResources 来加载 Resources
  2. ContextImpl 会持有加载之后的 Resources

接下来看 LoadedApk.getResources

创建 ResourcesImpl

    public Resources getResources() {
        if (mResources == null) {
        
            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                    splitPaths, mLegacyOverlayDirs, mOverlayPaths,
                    mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(),
                    getClassLoader(), null);
        }
        return mResources;
    }

LoadedApk.getResourcesResourcesManager 的单例调用 getResources

    @Nullable
    public Resources getResources(
            @Nullable IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] legacyOverlayDirs,
            @Nullable String[] overlayPaths,
            @Nullable String[] libDirs,
            @Nullable Integer overrideDisplayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader,
            @Nullable List<ResourcesLoader> loaders) {
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
                    libDirs,
                    overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
                    overrideConfig,
                    compatInfo,
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();

            // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
            // ResourcesManager lock is held.
            final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);

            if (overrideDisplayId != null) {
                rebaseKeyForDisplay(key, overrideDisplayId);
            }

            Resources resources;
            if (activityToken != null) {
                Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
                rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
                resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
                        overrideDisplayId, classLoader, assetsSupplier);
            } else {
                resources = createResources(key, classLoader, assetsSupplier);
            }
            return resources;
    }
  1. 这里的 activityToken 代表是一个 Activity
  2. ResourcesKey封装了资源相关的关键变量,如 resDir(apk资源的路径),overlayPaths, 后续资源的初始化需要用到 ResourcesKey

因为 activityToken 一般情况下不为 null ,因此会执行后面的createResourcesForActivity

    private Resources createResourcesForActivity(@NonNull IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
            @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
            @Nullable ApkAssetsSupplier apkSupplier) {
        synchronized (mLock) {
            // 创建 ResourcesImpl
            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
            // 
            return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
                    overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
        }
    }
    
    
    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
        // 通过 ResourceKey 去缓存里面去找
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
     
        // 如果缓存里面没有,那么创建
        if (impl == null || impl.getAppliedSharedLibsHash() != mSharedLibAssetsMap.size()) {
            impl = createResourcesImpl(key, apkSupplier);
            if (impl != null) {
                // 将创建的 ResourcesImpl 添加到缓存里面
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

createResourcesForActivity

  1. createResourcesImpl 创建 ResourcesImpl 对象
  2. createResourcesForActivityLocked 真正创建 Resource 对象

到这里为止,Resource 对象就会被创建,但是这里我们真正需要关注的是 ResourcesImpl,因为资源的加载,真正依赖的是 ResourcesImpl 对象。Resource 对象也会通过 resources.setImpl(impl) 持有 ResourcesImpl。 可以看一下 createResourcesForActivityLocked 这个方法

    private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
            @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
            @NonNull CompatibilityInfo compatInfo) {
        ...
        resources.setImpl(impl);
        ...
        return resources;
    }

我们接着继续分析 ResourcesImpl 这个对象是怎么创建的

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        // 创建 AssetManager
        final AssetManager assets = createAssetManager(key, apkSupplier);

        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        final Configuration config = generateConfig(key);
        final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
        // 这里真正创建了 ResourcesImpl, 并且持有了 AssetManager
        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true);
        return impl;
    }

createResourcesImpl 首先创建了 AssetManager 对象,最后创建了 ResourcesImpl,并持有这个 AssetManager,这里的 Configuration (特定的资源配置,如语言,分辨率)等其它的对象这里不过多分析,不是主要矛盾,跟资源加载相关的最重要的只有这个 AssetManager。 接下来分析 AssetManager 是如何创建的。

创建 AssetManager

    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager.Builder builder = new AssetManager.Builder().setNoInit();

        final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
        for (int i = 0, n = apkKeys.size(); i < n; i++) {
            final ApkKey apkKey = apkKeys.get(i);
            // 这里有两步,第一:通过 apkKey 去 load ApkAssets;第二,把 loaded ApkAssets 添加到 AssetManager 中
            builder.addApkAssets( (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
        }

        if (key.mLoaders != null) {
            for (final ResourcesLoader loader : key.mLoaders) {
                builder.addLoader(loader);
            }
        }
        return builder.build();
    }

createAssetManager 中:

  1. 从前面提到的 ResourcesKey 提取多个 ApkKey
  2. 通过 #1 提取到的 apkKeys加载 ApkAssets
  3. 通过 addApkAssetsApkAssets 添加到 AssetManager
  4. ApkAsset 加载完成之后, AssetManager 执行 build,根据“构建者”设计模式的一般套路,在 build 中肯定会 new AssetManager

我们接下来一步一步分析

Step1:提取 ApkKeys

    private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
        final ArrayList<ApkKey> apkKeys = new ArrayList<>();
        // apk 本身
        if (key.mResDir != null) {
            apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
        }
        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
            }
        }
        // 提取 shared libs apk
        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                // Avoid opening files we know do not have resources, like code-only .jar files.
                if (libDir.endsWith(".apk")) {
                    apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
                }
            }
        }
        // 提取 overlay apk
        if (key.mOverlayPaths != null) {
            for (final String idmapPath : key.mOverlayPaths) {
                apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
            }
        }
        return apkKeys;
    }

从 ResourceKey 中,提取多个资源包的路径,并传入 ApkKey,其中就包括本身进程所加载的 apk,shared lib apk 和 rro apk。接下来会根据这些 ApkKey 来加载对应的 ApkAssets

Step2:根据 ApkKey 加载 ApkAssets

    public @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
        ApkAssets apkAssets;
        // 优先从缓存读取,代码省略
        ...
        // 如果 apk 是一个 Overlay apk,那么从 /data/resource-cache/ 文件夹下读取对应的 @idmap 文件
        // idmap 文件类似于: vendor@overlay@rro.apk@idmap
        if (key.overlay) {
            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), flags);
        } else {
            // 如果是普通的 apk,那么就从保存 apk 的路径去读取 apkAsset 
            apkAssets = ApkAssets.loadFromPath(key.path, flags);
        }

        synchronized (mCachedApkAssets) {
            mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
        }

        return apkAssets;
    }
    
    
    // 对于 overlay apk,调用的是 ApkAssets.loadOverlayFromPath
    public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
            @PropertyFlags int flags) throws IOException {
        return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
    }
  
    
    // 对于普通的 apk,  调用的是 ApkAssets.loadFromPath
    public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)  // 这里创建了一个 ApkAssets
        return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
    }
    
    // 最终以上的两个方法都会调用相同的 ApkAssets 的构造函数如下
    private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
        this(format, flags, assets);
        mNativePtr = nativeLoad(format, path, flags, assets);
        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
    }

loadApkAssets中会创建一个 ApkAssets java 对象,在 ApkAssets 构造函数中会执行 nativeLoad (从方法命名以及参数,可以猜测应该是通 native 方法加载 App 加载资源), 并创建 StringBlock 对象。

这里的 idmap 是一个映射文件,记录了 Target apk 和 Overlay apk 之间资源 ID 的映射关系。idmap 文件是在 PMS 安装的时候生成的,每个 overlay apk 都有一个 idmap 文件在 /data/resource-cache/ 目录下

创建 ApkAssets

我们先看 nativeLoad 的实现

nativeLoad 的实现位于 frameworks/base/core/jni/android_content_res_ApkAssets.cpp

static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
                        jstring java_path, const jint property_flags, jobject assets_provider) {

  auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
  AssetManager2::ApkAssetsPtr apk_assets;
  switch (format) {
    // 对于普通的 apk
    case FORMAT_APK: {
        auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
                                                  ZipAssetsProvider::Create(path.c_str(),
                                                                            property_flags));
        apk_assets = ApkAssets::Load(std::move(assets), property_flags);
        break;
    }
    // 对于 overlay apk
    case FORMAT_IDMAP:
      apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
      break;
    ...
  }
  ...
  return CreateGuardedApkAssets(std::move(apk_assets));
}

// 以普通的 apk 为例,我们接下来继续分析 ApkAssets::Load
// 在 ApkAssets::Load 中真正实现的是 ApkAssets::LoadImpl
// 在 ApkAssets::LoadImpl 中会首先 open 打包到 apk 里面的 "resources.arsc" 的文件
ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
                                 package_property_t property_flags,
                                 std::unique_ptr<Asset>&& idmap_asset,
                                 std::unique_ptr<LoadedIdmap>&& loaded_idmap) {

  // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
  bool resources_asset_exists = false;
  // 打开 "resources.arsc" 文件
  auto resources_asset = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
                                      &resources_asset_exists);
  ...
  return LoadImpl(std::move(resources_asset), std::move(assets), property_flags,
                  std::move(idmap_asset), std::move(loaded_idmap));
}


ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset>&& resources_asset,
                                 std::unique_ptr<AssetsProvider>&& assets,
                                 package_property_t property_flags,
                                 std::unique_ptr<Asset>&& idmap_asset,
                                 std::unique_ptr<LoadedIdmap>&& loaded_idmap) {

  std::unique_ptr<LoadedArsc> loaded_arsc;
  // 加载正常 apk 的资源包
  if (resources_asset != nullptr) {
    const auto data = resources_asset->getIncFsBuffer(true /* aligned */);
    const size_t length = resources_asset->getLength();
    loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
    // loaded_idmap 不为 null,说明这个是 overlay 的资源包
  } else if (loaded_idmap != nullptr && IsFabricatedOverlay(loaded_idmap->OverlayApkPath())) {
    loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
  } 
  ...
  return ApkAssetsPtr::make(PrivateConstructorUtil{}, std::move(resources_asset),
                            std::move(loaded_arsc), std::move(assets), property_flags,
                            std::move(idmap_asset), std::move(loaded_idmap));
}



std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
                                             const size_t length,
                                             const LoadedIdmap* loaded_idmap,
                                             const package_property_t property_flags) {

 
  std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());

  ChunkIterator iter(data, length);
  while (iter.HasNext()) {
    const Chunk chunk = iter.Next();
    switch (chunk.type()) {
      case RES_TABLE_TYPE:
        if (!loaded_arsc->LoadTable(chunk, loaded_idmap, property_flags)) {
          return {};
        }
        ...
    }
  }
  ...
  return loaded_arsc;
}


bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
                           package_property_t property_flags) {

  
  ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
  // resources.arsc 是通过一个个的 Chunk 组织成的,整体来说就是由 Head + String pool + N 个 Package 组成,其中 Package 又包括了多个子 Chunk
  // 下面的代码就是遍历 resources.arsc 里面的 Chunk,然后进行解析
  while (iter.HasNext()) {
    const Chunk child_chunk = iter.Next();
    switch (child_chunk.type()) {
      // 解析 String pool Chunk
      case RES_STRING_POOL_TYPE:
        // Only use the first string pool. Ignore others.
        if (global_string_pool_->getError() == NO_INIT) {
          status_t err = global_string_pool_->setTo(child_chunk.header<ResStringPool_header>(),
                                                    child_chunk.size());
        } 
        break;
      
      // 解析 Package Chunk,由于 Package 相对比较复杂,Package Chunk 的解析单独由 LoadedPackage::Load 来解析
      // Package Chunk 的解析流程不是我们的重点,如果以后碰到了可以再仔细看代码
      case RES_TABLE_PACKAGE_TYPE: {
        if (packages_seen + 1 > package_count) {
          LOG(ERROR) << "More package chunks were found than the " << package_count
                     << " declared in the header.";
          return false;
        }
        packages_seen++;

        std::unique_ptr<const LoadedPackage> loaded_package =
            LoadedPackage::Load(child_chunk, property_flags);
        if (!loaded_package) {
          return false;
        }
        packages_.push_back(std::move(loaded_package));
      } break;
  }

  if (iter.HadError()) {
    LOG(ERROR) << iter.GetLastError();
    if (iter.HadFatalError()) {
      return false;
    }
  }
  return true;
}

到这里 nativeload 的分析就结束了,我们总结一下,这部分代码可以大概分为三部分

  1. 加载 apk 里面的 resources.arsc 文件; 如果是 overlay apk,那么会加载 idmap 文件 + apk 本身的 resources.arsc 文件
  2. 对 resources.arsc 和 idmap 文件进行解析 (这里没有详细展开 resources.arsc 是如何一步步解析的)
  3. 返回一个包含 apk_assets 的指针给 Java 层的 mNativePtr

相关数据结构如下

Resource_Management_Init_2.png

ApkAssets 持有 LoadedArscLoaderArsc 持有多个 LoadedPackage 这里主要,这里出现了三个 string pool,global_string_pool, type_string_pool, 和 key_string_pool

type_string_pool

type_string_pool 存放的是该包内所有资源的类型名称,如 "layout", "drawable" 等。

key_string_pool

key_string_pool 存放的是该包内所有资源条目的名称,如 "app_name", "ic_launcher" 等。

global_string_pool

global_string_pool 存储的是资源的实际内容值,是资源在运行时要展示或使用的文字和数据。

举个简单的例子,比如在 xml 文件中定义

<string name="app_name">Target App</string>

其中, string 存在 type_string_pool 中, app_name 存储在 key_string_pool 中, Target App 存储在 global_string_pool

接下来继续分析 new StringBlock 的行为

    private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
        ...
        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
    }
    
    // nativeGetStringBlock 对应的 native 方法为
    static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
        auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
        auto apk_assets = scoped_apk_assets->get();
        return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
    }

这里就比较简单了,在上一步返回了 apk_assets,这里就通过 apk_assets 的 loaded_arsc 拿到 global_string_pool。

到这里为止,ApkAssets 的加载就已经完成了,主要工作就是记载并解析 Apk 里的 resources.arsc,并返回 java 层由 ApkAsset.mNativePtr 持有。

Step3 : Add ApkAssets

Apk 的 ApkAsset 加载完成之后,接下来 AssetManager 会 addApkAssets

        // 首先调用 addApkAssets
        public Builder addApkAssets(ApkAssets apkAssets) {
            mUserApkAssets.add(apkAssets);
            return this;
        } 

addApkAssets 会把多个 ApkAsset 保存到 AssetManager 中并由 AssetManager.mUserApkAssets 这个数组持有

Step4 : AssetManager build()

        public AssetManager build() {
            // 加载 Framework 的资源,如 "/system/framework/framework-res.apk"
            final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
            // 如果多个加载器提供了相同的资源,这些资源只会被包含一次,避免重复;最后一个出现的资源将决定其在 `AssetManager` 中的加载顺序
            final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
            final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
            for (int i = mLoaders.size() - 1; i >= 0; i--) {
                final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
                for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
                    final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
                    if (uniqueLoaderApkAssets.add(apkAssets)) {
                        loaderApkAssets.add(0, apkAssets);
                    }
                }
            }

            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
                    + loaderApkAssets.size();
            final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];

            System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);

            // Append user ApkAssets after system ApkAssets.
            for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
                apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
            }

            // Append ApkAssets provided by loaders to the end.
            for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
                apkAssets[i + systemApkAssets.length  + mUserApkAssets.size()] =
                        loaderApkAssets.get(i);
            }
            
            // 真正创建 AssetManager 对象,并且会创建一个 native 对象 AssetManager2
            final AssetManager assetManager = new AssetManager(false /*sentinel*/);
            assetManager.mApkAssets = apkAssets;
            
            // assetManager.mObject 就是上一步创建的 native 对象 AssetManager2
            AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                    false /*invalidateCaches*/, mNoInit /*preset*/);
            assetManager.mLoaders = mLoaders.isEmpty() ? null
                    : mLoaders.toArray(new ResourcesLoader[0]);

            return assetManager;
        }
  1. build 中创建了 AssetManager 对象,并且创建了 Native 对象 AssetManager2
  2. 通过 nativeSetApkAssets set 所有的 ApkAsset,包括了
  • Apk 本身的 ApkAssets
  • shared libs 的 ApkAssets (如果存在)
  • framework-res.apk 的 ApkAssets
  • RRO apk 的 ApkAssets (如果存在)

接下来我们看看 AssetManager 的构造函数里面做了什么

创建 Native 对象 AssetManager2
    private AssetManager(boolean sentinel) {
        创建了一个 native 对象
        mObject = nativeCreate();
        ...
    }
    
    // android_util_AssetManager.cpp
    static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
        // 创建了 AssetManager2 对象,所谓的 GuardedAssetManager,其实就是对 AssetManager2 访问加锁
        return reinterpret_cast<jlong>(new GuardedAssetManager());
    }
    

在 AssetManager 的构造函数中,就会生成 AssetManager2 这个 native 对象,并且由 Java 层的AssetManager 的 mObject 持有,并且作为 nativeSetApkAssets 中参数。接下来看 AssetManager.nativeSetApkAssets 如何设置 ApkAssets

Native Set ApkAssets
// java 层调用 nativeSetApkAssets
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                    false /*invalidateCaches*/, mNoInit /*preset*/);
                    
// native 层对应的代码为
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                               jobjectArray apk_assets_array, jboolean invalidate_caches,
                               jboolean preset) {

  const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
  std::vector<AssetManager2::ApkAssetsPtr> apk_assets;
  apk_assets.reserve(apk_assets_len);
  for (jsize i = 0; i < apk_assets_len; i++) {
    jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
    ...
    auto scoped_assets = ScopedLock(ApkAssetsFromLong(apk_assets_native_ptr));
    apk_assets.emplace_back(*scoped_assets);
  }

  // 通过 Java 层传过来的 AssetManager2 的指针,获取对应的 AssetManager2 对象
  auto assetmanager = LockAndStartAssetManager(ptr);
  ...
  assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
         

接下来看 AssetManager2 如何设置这些 ApkAssets,但是在分析如何设置 ApkAssets 之前,写介绍重要且相关的数据结构,方便后续的分析

class AssetManager2 {


  // These are ordered according to apk_assets_. The mappings may change depending on what is
  // in apk_assets_, therefore they must be stored in the AssetManager and not in the
  // immutable ApkAssets class.
  std::vector<PackageGroup> package_groups_;
  
  // The current configurations set for this AssetManager. When this changes, cached resources
  // may need to be purged.
  ftl::SmallVector<ResTable_config, 1> configurations_;
  
  
    // An array mapping package ID to index into package_groups. This keeps the lookup fast
  // without taking too much memory.
  std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_ = {};
  
  struct PackageGroup {
      // ConfiguredPackage 内部持有 LoadedPackage 和 配置相关的信息
      std::vector<ConfiguredPackage> packages_;

      // ApkAssetsCookie 标识 package 所在的 ApkAssets 的对象的索引, 所以指向的是一个 ApkAssets
      std::vector<ApkAssetsCookie> cookies_;

      // Runtime Resource Overlays that overlay resources in this package group.
      std::vector<ConfiguredOverlay> overlays_;

      // 保存资源共享库编译时 id 和运行时 id 的一个映射关系
      std::shared_ptr<DynamicRefTable> dynamic_ref_table = std::make_shared<DynamicRefTable>();
  };
  
   struct ConfiguredPackage {
      // A pointer to the immutable, loaded package info.
      const LoadedPackage* loaded_package_;

      // A mutable AssetManager-specific list of configurations that match the AssetManager's
      // current configuration. This is used as an optimization to avoid checking every single
      // candidate configuration when looking up resources.
      ByteBucketArray<FilteredConfigGroup> filtered_configs_;
  };

}

LoadedPackage

LoadedPackage 在上面描述 ApkAsset 已经描述过,它承载了 Apk 一整套资源的命名和索引信息。一个 Apk 的 resources.arsc 通常情况下会包含 1 个 LoadedPackage,但是 Apk 在编译时候链接了多个其他的资源库,那么就会存在多个 LoadedPackage。

PackageGroup

  1. PackageGroup 包含了多个来自于不同 Apk 的 LoadedPackage, 它把 target Apk 和 Overlay Apk 的资源都整合到同一个 PackageGroup
  2. cookies_ 描述对应的 LoadedPackage 是来自于哪一个 Apk
  3. overlays_ 描述了针对这个 target apk 的所有 Overlay Apk 的资源

package_groups_

由一组 PackageGroup 组成

package_ids_

存储 LoadedPackage idpackage_groups_PackageGroup 的映射关系,所以通过 LoadedPackage id 能够找到与之一一对应的 PackageGroup

configurations_

当前 AssetManger2 的配置信息,如屏幕分辨率,语言等。如果发生改变,需要丢弃缓存的资源

了解了基本的数据结构之后,就可以分析 SetApkAssets 的具体过程,

AssetManager2.SetApkAssets
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
  BuildDynamicRefTable(apk_assets);
  ...
  return true;
}

SetApkAssets 的主要实现是 BuildDynamicRefTable

BuildDynamicRefTable 过程比较复杂,拆解分析

void AssetManager2::BuildDynamicRefTable(ApkAssetsList apk_assets) {
  apk_assets_.resize(apk_assets.size());
  for (size_t i = 0; i != apk_assets.size(); ++i) {
    apk_assets_[i].first = apk_assets[i];
    // Let's populate the locked assets right away as we're going to need them here later.
    apk_assets_[i].second = apk_assets[i];
  }
  // package_groups_ 是 PackageGroup 的集合
  package_groups_.clear();
  
  // package_ids_ 的 key: ApkAssets 的 id
  //                 value: PackageGroup 在 package_groups_ 的索引
  package_ids_.fill(0xff);

  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
  
  std::vector<const ApkAssets*> sorted_apk_assets;
  sorted_apk_assets.reserve(apk_assets.size());
  for (auto& asset : apk_assets) {
    sorted_apk_assets.push_back(asset.get());
  }
  // sorted_apk_assets 是对输入的 apk_assets 的排序,把 rro 的 ApkAssets 放在最后
  std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(),
                        [](auto a) { return !a->IsOverlay(); });

  std::unordered_map<const ApkAssets*, ApkAssetsCookie> apk_assets_cookies;
  apk_assets_cookies.reserve(apk_assets.size());
  for (size_t i = 0, n = apk_assets.size(); i < n; i++) {
    apk_assets_cookies[apk_assets[i].get()] = static_cast<ApkAssetsCookie>(i);
  }

  // 0x01 is reserved for the android package.
  int next_package_id = 0x02;
  ...
  
 }
  1. 把从 java 层传过来的 apk_assets 保存到 apk_assets_ 中
  2. 对 apk_assets_ 中的 ApkAssets 进行排序,把 RRO 的 ApkAssets 排在后面 (排序对于 RRO 机制非常重要,在后面提到的资源加载中这个排序决定了资源最终来自于哪一个 ApkAssets)
// 遍历每一个 ApkAssets
for (const ApkAssets* apk_assets : sorted_apk_assets) {
    std::shared_ptr<OverlayDynamicRefTable> overlay_ref_table;
    // 处理 rro apk 的 ApkAssets
    if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
      // 找到对应的 target apk
      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
      if (iter == target_assets_package_ids.end()) {
         LOG(INFO) << "failed to find target package for overlay "
                   << loaded_idmap->OverlayApkPath();
      } else {
        uint8_t target_package_id = iter->second;
        // 处理 Overlay 资源
        overlay_ref_table = std::make_shared<OverlayDynamicRefTable>(
            loaded_idmap->GetOverlayDynamicRefTable(target_package_id));

        // 将 Overlay 资源添加到 target apk 的 Overlay 列表 target_package_group.overlays_ 中
        const uint8_t target_idx = package_ids_[target_package_id];
        PackageGroup& target_package_group = package_groups_[target_idx];
        // 把 Overlay 资源添加到 target 的 PackageGroup 的 overlays_ 中
        // 这样 Overlay 资源和 target 资源放在了同一个 PackageGroup 中
        target_package_group.overlays_.push_back(
            ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
                                                                  overlay_ref_table.get()),
                              apk_assets_cookies[apk_assets]});
      }
    }
    // 处理 普通 apk 的 ApkAssets
    // 获取 ApkAssets 的 LoadedArsc 对象,
    // ApkAssets LoadedArsc 和 LoadedPackage 之间的关系在上面以及提到过,不清楚的可以本节上面关于 ApkAssets 的内容
    const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
    for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
      // Get the package ID or assign one if a shared library.
      int package_id;
      if (package->IsDynamic()) {
        package_id = next_package_id++;
      } else {
        package_id = package->GetPackageId();
      }

      uint8_t idx = package_ids_[package_id];
      if (idx == 0xff) {
        // Add the mapping for package ID to index if not present.
        package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
        // 新建一个 PackageGroup
        PackageGroup& new_group = package_groups_.emplace_back();

        if (overlay_ref_table != nullptr) {
          // If this package is from an overlay, use a dynamic reference table that can rewrite
          // overlay resource ids to their corresponding target resource ids.
          new_group.dynamic_ref_table = std::move(overlay_ref_table);
        }

        DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
        ref_table->mAssignedPackageId = package_id;
        ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
      }

      // Add the package to the set of packages with the same ID.
      PackageGroup* package_group = &package_groups_[idx];
      package_group->packages_.emplace_back().loaded_package_ = package.get();
      package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
    }
    
      // Add the package name -> build time ID mappings.
      for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
        String16 package_name(entry.package_name.c_str(), entry.package_name.size());
        package_group->dynamic_ref_table->mEntries.replaceValueFor(
            package_name, static_cast<uint8_t>(entry.package_id));
      }

      if (auto apk_assets_path = apk_assets->GetPath()) {
        // Overlay target ApkAssets must have been created using path based load apis.
        target_assets_package_ids.emplace(*apk_assets_path, package_id);
      }
  }
  ...
}

遍历每一个 ApkAssets, 对于普通 Apk 的 ApkAssets

  1. 为每一个 ApkAssets 创建一个新的 PackageGroup
  2. 填充 PackageGroup 的 packages_ 和 cookies_
  3. 把 PackageGroup 添加到 package_groups_ 中,并且建立 package_id 和 package_groups_ 的映射关系,方便后续通过 package_id 找到对应的 PackageGroup

对于 RRO Apk 的 ApkAssets

  1. target_assets_package_ids 中找到需要覆盖的 Apk,并且在 package_groups_ 中找到对应的 PackageGroup
  2. 填充 PackageGroup 的 overlays_, 把 Target Apk的资源和 RRO Apk 的资源绑定在一个 PackageGroup 中
  // Now assign the runtime IDs so that we have a build-time to runtime ID map.
  DynamicRefTable::AliasMap aliases;
  for (const auto& group : package_groups_) {
    const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
    const auto name_16 = String16(package_name.c_str(), package_name.size());
    for (auto&& inner_group : package_groups_) {
      inner_group.dynamic_ref_table->addMapping(name_16,
                                                group.dynamic_ref_table->mAssignedPackageId);
    }

    for (const auto& package : group.packages_) {
      const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
    }
  }

  if (!aliases.empty()) {
    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });

    // Add the alias resources to the dynamic reference table of every package group. Since
    // staging aliases can only be defined by the framework package (which is not a shared
    // library), the compile-time package id of the framework is the same across all packages
    // that compile against the framework.
    for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
      group.dynamic_ref_table->setAliases(aliases);
    }
    package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
  }

最后就是收尾阶段了,把 build-time 的资源 ID 和 runtime 的资源 ID 建立映射关系,比如你的 App 在 build-time 的时候引用了一个 shard library 的资源,比如 com.google.android.material, 在编译的时候这个资源 ID 可能是 0x7f01xxxx, 但是在运行时,这个 shared library 被系统分配的 ID 是 0x0201xxxx,所以必须要建立 build-time 和 runtime ID 之间的映射。

到这里 BuildDynamicRefTable就分析完了 BuildDynamicRefTable 主要的作用就是把多个 ApkAssets 的资源解析到 PackageGroup package_groups_package_ids_ 这些变量中,后续资源的加载的过程就可以依赖于这些变量匹配到对应的资源。

资源初始化总结

Android 系统对于资源的初始化,这个过程主要的工作就是加载并解析多个 ApkAssets,建立 Target apk 和 Overlay apk 之间的映射关系,最终由 AssetManager 来管理这些资源方便后续的资源加载。

resource_init_1.png