前言
前面我们学习 App 资源的打包流程, 知晓了 resources.arsc 的结构组成, 我们在开发过程中想要访问资源需要通过如下的方式进行
Resources res = getContext().getResources();
res.getString(R.string.xxx);
我们在应用开发的过程中, 通常通过 Resources 对象来访问 app 的资源文件, 通过 aapt 编译的过程我们知道, R.string.xxx 是资源的 ID, 想通过 ID 获取到对应的资源, 只能够通过资源索引表 resources.arsc
我们在 Application 的 onCreate 中就可以通过 Resources 访问 app 的资源了, 因此本篇文章从 Application 的创建方法 makeApplication 为起点来探索一下资源管理类的创建流程
public final class LoadedApk {
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
// 构建 Application 对象
if (mApplication != null) {
return mApplication;
}
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
// 1. 创建 Application 的 ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 实例化 Application 对象
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
......
}
.....// 回调 onCreate
// 2. 重写 R 文件的 ID 值为运行时 ID
SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
}
return app;
}
}
Application 创建的代码, 我们已经看过很多次了, 这里我们主要关注一下与资源相关的初始化操作
- 首先会创建 Application 的 ContextImpl
- 调用了 rewriteRValues 重写 R 文件的资源 ID 值
这里我们从 ContextImpl 的创建来学习资源管理的创建流程, 关于 R 文件中的资源 ID 为什么需要被重写, 我们到文章的结束再进行揭秘
一. 资源管理的创建
我们在 Activity 启动流程中知道, 在创建 ContextImpl 之后, 紧接着会为它注入 Resource 对象, 如此一来 ContextImpl 才具有资源访问的能力, 这里以创建 Application 的 ContextImpl 为例
class ContextImpl extends Context {
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
......
// 1. 创建 ContextImpl 对象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
// 2. 通过 LoadedApk.getResources 来获取 Resource 对象
// 3. 将 Resource 注入到 ContextImpl 中
context.setResources(packageInfo.getResources());
return context;
}
}
从这里可以看到 Resource 对象是通过 LoadedApk 的 getResources 获取到的, 这个 LoadedApk 我们在 Activity 启动篇中也分析过, 它用来描述一个 apk 的资源信息
接下来我们看看 LoadedApk 的 getResource 方法是如何获取 Apk 对象信息的
public final class LoadedApk {
Resources mResources;
public Resources getResources() {
if (mResources == null) {
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
} catch (NameNotFoundException e) {
......
}
// 通过 ResourceManager 来获取这个 apk 对应的资源
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
}
可以看到每一个 Apk 都有一个 Resource 对象来描述它包内的资源, 而这个 Resource 对象是通过 ResourcesManager 的 getResource 获取到的
- 我们一个应用可能会存在多个 apk, 比如说一个主 base.apk, 然后又动态下载了一些换肤包的 apk 问题, 每一个 apk 都会用 LoadedApk 来描述
下面我们看看 ResourceManager 是如何获取当前 Apk 的资源信息的
一) 通过 ResourcesManager 获取 Resources
public class ResourcesManager {
private static ResourcesManager sResourcesManager;
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
...// systrace 埋点
// 将请求的资源信息构建成 ResourceKey
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
// 获取 LoadedApk 传递过来的 ClassLoader
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
// 调用 getOrCreateResources 获取资源信息
return getOrCreateResources(activityToken, key, classLoader);
} finally {
...// systrace 埋点
}
}
}
ResourcesManager 的 getResource 方法实现如下
- 构建 ResourcesKey
- 获取 ClassLoader
- 优先使用 LoadedApk 中的 ClassLoader
- 不存在使用系统的 ClassLoader, 这是一个进程单例的 PathClassLoader
- 调用 getOrCreateResources 根据 ResourcesKey 获取 Resource 对象
下面我们看看 getOrCreateResources 的实现
public class ResourcesManager {
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
......
// 1. 从缓存中查找 ResourceImpl
// 1.1 在构建 Activity 的 ContextImpl 的 Resource 是走这里
if (activityToken != null) {
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
......// 清除一些失效的引用
// 根据 key 从缓存中查找 ResourceImpl 对象
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
......
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
}
// 1.2 在构建其他 ContextImpl 的 Resource 时走这里, 我们的 Application 会走这里
else {
......// 清除一些失效的引用
// 根据 key 从缓存中查找 ResourceImpl 对象
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
......
// 获取 Resource 对象
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
// 2. 构建新的 ResourceImpl
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
// 3. 添加到缓存
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
// 4. 创建 Resources
final Resources resources;
if (activityToken != null) {
// 4.1 创建 Activity 的 Resources
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
// 4.2 创建其他的 Resource
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
}
ResourcesManager 创建 Resources 的流程如下
- 获取 ResourcesImpl
- 从缓存中查找 ResourcesKey 对应的 ResourcesImpl
- 不存在则创建一个新的 ResourcesImpl 加入缓存
- 调用 getOrCreateResourcesLocked 通过 ResourcesImpl 构建 Resources 对象
从 ResourcesImpl 的命名上上就可以看出它是 Resources 的真正实现, 我们先看看 ResourcesImpl 的构建过程
二) 获取 ResourcesImpl
获取 ResourcesImpl 有两种方式, 优先从缓存中获取 ResourcesKey 对应的 ResourceImpl 对象, 次优先创建新对象, 这里我们先看看从缓存中获取的流程
public class ResourcesManager {
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
new ArrayMap<>();
private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
// 从缓存中获取 ResourcesImpl
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && impl.getAssets().isUpToDate()) {
return impl;
}
return null;
}
}
可以看到 ResourcesManager 维护了一个 ResourcesKey 和 ResourcesImpl 弱引用对象的缓存, 若弱引用的对象存在, 则返回这个 ResourcesImpl
下面我们看看缓存中不存在, ResourceImpl 的创建过程
public class ResourcesManager {
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
// 1. 创建 AssetManager
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
// 获取显示屏幕的相关信息
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
// 2. 创建 ResourcesImpl
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
......
return impl;
}
}
public class ResourcesImpl {
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
// 将 AssetManager 保存到成员变量中
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
}
好的, 可以看到 createResourcesImpl 中主要有两步操作
- 创建 AssetManager
- 创建 ResourcesImpl 对象
这里 ResourceImpl 是一个门面类, 其资源相关的具体实现是由 AssetManager 来完成的, 它也是整个 Android 资源管理框架的核心, 我们先将上层的 Resource 创建分析完之后到后面重点看看 AssetManager 的构建流程
接下来我们看看有了 ResourceImpl 之后, 如何构建 Resource
三) 创建 Resources 对象
public class ResourcesManager {
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
// 1. 遍历 mResourceReferences 集合, 找寻与传入的 ResourcesImpl 和 ClassLoader 都相同的 Resources
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
Resources resources = weakResourceRef.get();
// ClassLoader 和 impl 都相同
if (resources != null && Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
.......
return resources;
}
}
// 2. 创建新的 Resources 对象
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
// 2.1 注入 ResourcesImpl
resources.setImpl(impl);
// 添加到缓存集合
mResourceReferences.add(new WeakReference<>(resources));
......
return resources;
}
}
getOrCreateResourcesLocked 中实现的逻辑也非常的简单
- 优先从引用集合中找寻目标的 Resources
- ResourcesImpl 和 ClassLoader 都相同
- 若缓存中不存在, 则创建一个新的 Resources 对象
- 将 ResourcesImpl 注入
到这里我们一个 Resources 就创建好了
四) 回顾
ResourcesManager 获取 Resources 的步骤如下
- 构建 ResourcesKey
- 根据 ResourcesKey 从缓存 mResourceImpls 中获取 ResourcesImpl 对象
- 缓存不存在则构建 ResourcesImpl 对象, 创建流程如下
- 获取 AssetManager
- 创建 ResourcesImpl 对象
- 内部持有 AseetManager
- 缓存不存在则构建 ResourcesImpl 对象, 创建流程如下
- 优先从 mResourceReferences 缓存中获取 Resources 对象
- 不存在则创建新的 Resources 对象
- 将 ClassLoader 和 ResourcesImpl 注入
- 不存在则创建新的 Resources 对象
他们之间的依赖关系如下
二. AssetManager 的构建
public class ResourcesManager {
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
final AssetManager.Builder builder = new AssetManager.Builder();
// 1. 获取 mResDir 的 ApkAssets 对象, 添加到 AssetManager.Builder 中
if (key.mResDir != null) {
try {
builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
......
}
}
// 2. 获取 mSplitResDirs 的 ApkAssets 对象, 添加到 AssetManager.Builder 中
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
try {
builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
......
}
}
}
// 3. 获取 mOverlayDirs 的 ApkAssets 对象, 添加到 AssetManager.Builder 中
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
try {
builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
true /*overlay*/));
} catch (IOException e) {
......
}
}
}
// 4. 获取 mLibDirs 的 ApkAssets 对象, 添加到 AssetManager.Builder 中
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
......
try {
builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
......
}
}
}
}
// 5. 构建 AssetManager
return builder.build();
}
}
可以看到 AssetManager 的构建主要有如下的步骤
- 通过 loadApkAssets 获取 ApkAssets 对象, 用于描述一个 apk 中的资源数据
- 调用 AssetManager.Builder.addApkAssets 将所有构件的资源包添加到构造者中
- 通过 AssetManager.Builder.build 构建 AssetManager 对象
从这个方法中可知, AssetManager 主要负责管理的是 ApkAssets, 下面我们先看看 ApkAsset 的构建过程, 然后再看看 AssetManager 的构建
一) 构建 ApkAsset
public class ResourcesManager {
/**
* 活跃缓存
*/
private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(3);
/**
* 不活跃缓存
*/
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
throws IOException {
// 1. 构建一个 ApkKey 对象
final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
// 2. 从 mLoadedApkAssets 缓存中获取 ApkAssets
ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
if (apkAssets != null) {
return apkAssets;
}
// 3. 从 mCachedApkAssets 缓存中获取 ApkAssets 对象
final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
if (apkAssetsRef != null) {
apkAssets = apkAssetsRef.get();
if (apkAssets != null) {
// 添加到活跃缓存
mLoadedApkAssets.put(newKey, apkAssets);
return apkAssets;
} else {
// 移除失效的缓存对象
mCachedApkAssets.remove(newKey);
}
}
// 4. 从磁盘中获取 ApkAssets 对象
if (overlay) {
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
false /*system*/);
} else {
// 读取资源包
apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
}
// 5. 加入缓存
mLoadedApkAssets.put(newKey, apkAssets);
mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
// 返回 ApkAssets
return apkAssets;
}
}
loadApkAssets 获取 ApkAssets 的过程还是比较有趣的, 从这里可以看到 ResourcesManager 中维护了两个内存缓存, 其获取流程如下
- 构建 ApkKey
- 从活跃缓存 mLoadedApkAssets 中获取
- 从不活跃缓存 mCachedApkAssets 中获取
- 从磁盘中读取到内存缓存
这里我们主要看看 ApkAssets.loadFromPath 是如何将 apk 的资源包读到内存的
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
@GuardedBy("this") private StringBlock mStringBlock;
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
boolean forceSharedLibrary) throws IOException {
// 创建了 ApkAssets 实例
return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
}
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
throws IOException {
// 1. 调用 nativeLoad 到 Native 层加载资源
mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
// 2. 调用 nativeGetStringBlock 获取资源包中的字符串常量
// 3. 构件 StringBlock 对象
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
}
从这里可以看到构件 ApkAssets 的构造中主要有三步操作,
- 构件 Native 层的 ApkAssets
- 获取字符常量
ApkAssets 的 Native 实现类为 android_content_res_ApkAssets.cpp
下面我们一一分析
1. nativeLoad
// frameworks/base/core/jni/android_content_res_ApkAssets.cpp
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
jboolean force_shared_lib, jboolean overlay) {
// 获取资源路径
ScopedUtfChars path(env, java_path);
......
// 创建 Native 的 ApkAssets 对象
std::unique_ptr<const ApkAssets> apk_assets;
if (overlay) {
apk_assets = ApkAssets::LoadOverlay(path.c_str(), system);
} else if (force_shared_lib) {
apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
} else {
apk_assets = ApkAssets::Load(path.c_str(), system);
}
......
// 转为句柄值返回给 Java 层
return reinterpret_cast<jlong>(apk_assets.release());
}
可以看到 ApkAssets 的 NativeLoad 实现中通过 ApkAssets::load 创建了一个 Native 的 ApkAssets 对象, 下面我们就看看它的创建流程
// frameworks/base/libs/androidfw/ApkAssets.cpp
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
// 1. 打开 .apk 文件, 使用 unmanaged_handle 描述文件句柄
::ZipArchiveHandle unmanaged_handle;
int32_t result;
if (fd >= 0) {
result = ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/);
} else {
// 调用 OpenArchive 打开资源目录
result = ::OpenArchive(path.c_str(), &unmanaged_handle);
}
.......
// 2. 创建一个 Native 的 ApkAssets 对象
std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
// 3. 从 .apk 中找寻资源表 resources.arsc
::ZipString entry_name(kResourcesArsc.c_str());
::ZipEntry entry;
result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
if (result != 0) {
// 不存在 resources.arsc, 创建一个空的 LoadedArsc 对象
loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
return std::move(loaded_apk);
}
......
// 4. 将 apk 中的 resources.arsc 文件 mmap 到内存
// resources_asset_ 为 Asset 对象
loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
......
// 5. 反序列化 resources.arsc 数据到 LoadedArsc 对象中
// 5.1 获取 resources.arsc 文件数据 Buffer
const StringPiece data(
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
// 5.2 调用 LoadedArsc::Load 反序列化 resources.arsc 文件, 构建成 LoadedArsc 对象
loaded_apk->loaded_arsc_ =
LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
......
// 返回 ApkAssets
return std::move(loaded_apk);
}
ApkAssets::LoadImpl 中的流程还是非常清晰的
- 获取 apk 文件的 fd, 保存在 ApkAssets 对象中
- 从 apk 文件中找寻 resources.arsc 资源表
- 将 resources.arsc 文件 mmap 到内存
- mmap 之后, 即可通过内存来访问文件中的数据, 使用这种方式减少与磁盘的直接 IO, 提升文件操作读取性能
- 反序列化 resources.arsc 数据到 LoadedArsc 对象中
ApkAssets 有了 LoadedArsc 对象之后就具有资源查找的能力了, 其内部结构如下
关于 ApkAssets 反序列化 resources.arsc 的过程由于篇幅原因这里就不赘述了, 感兴趣可以打开源码查看
下面我们看看 nativeGetStringBlock 的实现
2. nativeGetStringBlock
// frameworks/base/core/jni/android_content_res_ApkAssets.cpp
static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
// 获取 ApkAssets 对象
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
// 调用 LoadedArsc 的 GetStringPool 获取字符串资源池
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
// frameworks/base/libs/androidfw/include/androidfw/LoadedArsc.h
inline const ResStringPool* GetStringPool() const {
return &global_string_pool_;
}
NativeGetStringBlock 中主要是获取到了 LoadedArsc 中的 global_string_pool_
由 App 资源打包的知识可知, global_string_pool_ 存储的是资源项值的字符串资源池, 如下图所示
到这里 ApkAssets 就构建完成了, 下面我们看看 AssetsManager 的构建过程
二) AssetsManager 的构建
public final class AssetManager implements AutoCloseable {
public static class Builder {
private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
public AssetManager build() {
// 1. 合并系统 Apk 资源数据 和 刚刚解析的 Apk 资源数据
// 1.1 获取系统 Apk 资源数据集合
final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
// 1.2 合并数据到 apkAssets数组
final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
final int userApkAssetCount = mUserApkAssets.size();
for (int i = 0; i < userApkAssetCount; i++) {
apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
}
// 2. 构建 AssetManaager
final AssetManager assetManager = new AssetManager(false /*sentinel*/);
// 3. 将资源数组 apkAssets 保存到 Java 的 AssetManaager
assetManager.mApkAssets = apkAssets;
// 4. 将资源数组 apkAssets 保存到 Native 的 AssetManaager
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
false /*invalidateCaches*/);
return assetManager;
}
}
@GuardedBy("this") private long mObject;
private AssetManager(boolean sentinel) {
// 2.1 创建 Native 层的 AssetManager
mObject = nativeCreate();
......
}
private static native long nativeCreate();
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
boolean invalidateCaches);
}
AssetsManager 的构建流程如下
- 合并 系统 Apk 的资源数据 和 上面解析的资源数据 到 apkAssets 数组
- 获取系统的 AssetManager, 进而获取系统资源数据数组
- 进行合并操作
- 创建 Java 的 AssetsManager
- 调用 nativeCreate 创建 Native 的 AssetsManager
- 将 apkAssets 数组保存到 Java 的 AssetsManager
- 将 apkAssets 数组保存到 Native 的 AssetsManager
接下来我们先看看获取系统 Apk 资源数据的过程
1. 获取系统资源
public final class AssetManager implements AutoCloseable {
public static AssetManager getSystem() {
synchronized (sSync) {
// 获取系统的 AssetManager
createSystemAssetsInZygoteLocked();
return sSystem;
}
}
private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
@GuardedBy("sSync") static AssetManager sSystem = null;
/**
* This must be called from Zygote so that system assets are shared by all applications.
*/
@GuardedBy("sSync")
private static void createSystemAssetsInZygoteLocked() {
if (sSystem != null) {
return;
}
......
try {
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
// 1. 加载系统资源 framework-res.apk 的数据
apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
loadStaticRuntimeOverlays(apkAssets);
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
// 2. 构建系统资源的 AssetManager
sSystem = new AssetManager(true /*sentinel*/);
// 3. 将数据保存到系统的 AssetManager
sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
} catch (IOException e) {
......
}
}
}
从这里可以看到系统资源是以进程间单例 sSystem 的形式存在的, 它的 AssetManager 在 Zygote 进程启动时会进行加载, 也就是说我们应用进程 fork 之后就自带了系统的资源
ps: 这里我们可以想到一个性能优化的思路, 我们在创建不需要使用系统资源的后台进程时, 可以通过释放系统资源的数据, 来减少后台服务进程的的内存
下面我们看看 Native 层 AssetsManager 的创建流程
2. 创建 Native 层的 AssetsManager
nativeCreate 的 JNI 接口定义在 android_util_AssetManager.cpp 中, 下面看看它的实现
// frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
// 创建了一个 GuardedAssetManager 对象
return reinterpret_cast<jlong>(new GuardedAssetManager());
}
struct GuardedAssetManager : public ::AAssetManager {
// 内部持有 AssetManager2 对象
Guarded<AssetManager2> guarded_assetmanager;
};
Native 层 AssetsManager 的构建过程比较简单, 最终构建了一个 AssetManager2 对象, 这才是 Native 的 AssetManager 的真正实现
下面看看 将 ApkAsset 资源数组注入 Native 的过程
3. 将 ApkAsset 资源数组注入 Native
// frameworks/base/core/jni/android_util_AssetManager.cpp
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
jobjectArray apk_assets_array, jboolean invalidate_caches) {
// 1. 将 Java 的 ApkAssets 数组构建成 C++ 的 vetor 集合
const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
std::vector<const ApkAssets*> 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);
if (obj == nullptr) {
std::string msg = StringPrintf("ApkAssets at index %d is null", i);
jniThrowNullPointerException(env, msg.c_str());
return;
}
jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
if (env->ExceptionCheck()) {
return;
}
apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
}
// 2. 注入 Native 层的 AssetManager2 中
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
// frameworks/base/libs/androidfw/AssetManager2.cpp
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
bool invalidate_caches) {
// 2.1 保存资源集合
apk_assets_ = apk_assets;
// 2.2 动态构建引用表
BuildDynamicRefTable();
// 2.3 重新构建缓存集合
RebuildFilterList();
// 2.4 刷新缓存
if (invalidate_caches) {
InvalidateCaches(static_cast<uint32_t>(-1));
}
return true;
}
可以看到将资源集合 ApkAssets 注入 AssetManager2 的过程中做了如下的操作
- 保存资源集合
- 构建动态引用表
- 重新构建缓存集合
- 清空缓存
这里我们主要关注一下 动态引用表的构建过程
// frameworks/base/libs/androidfw/include/androidfw/AssetManager2.h
std::vector<PackageGroup> package_groups_;
// 将包 ID 映射到索引到 package_groups 的数组
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
// frameworks/base/libs/androidfw/AssetManager2.cpp
void AssetManager2::BuildDynamicRefTable() {
// 清空 PackageGroup 集合
package_groups_.clear();
// 填充 packageId 数组
package_ids_.fill(0xff);
// 0x01 保留给 framework-res.apk 中的 android 资源包使用
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
// 1. 遍历 ApkAssets 资源包, 构建 PacakgeGroup
for (size_t i = 0; i < apk_assets_count; i++) {
const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
// 遍历 LoadedArsc 资源包中的 LoadedPackage 集合
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// 为共享库动态分配运行时 ID
// 系统的资源为 0x01, 我们 App 的为 0x7f, 他们不需要动态分配, 运行时 ID 和编译时一致
int package_id;
// 判断是否需要动态分配 ID
if (package->IsDynamic()) {
// 需要动态分配
package_id = next_package_id++;
} else {
// 不需要动态分配
package_id = package->GetPackageId();
}
// 获取 运行时 ID 对应的 PacakgeGroup 对象在 package_groups_ 中的索引
uint8_t idx = package_ids_[package_id];
// 获取运行时 id 对应的 PacakgeGroup
// idx 为 0xff, 说明当前运行时 ID 还未创建对应的 PackgeGroup
if (idx == 0xff) {
// 获取索引值并保存
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
// 添加一个 PacakgeGroup 对象
package_groups_.push_back({});
// 新添加的 PacakgeGroup 的资源表 DynamicRefTable
DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
// 记录运行时 ID
ref_table.mAssignedPackageId = package_id;
// 判断是否为应用程序的资源包, app 资源包 id 一般以 0x7f 开头
ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
// 获取 package_groups_ 在 idx 处的 PackageGroup 对象
PackageGroup* package_group = &package_groups_[idx];
// 将当前资源包 LoadedPackage 添加到 PackageGroup 中的 packages_ 缓存中
// 因为运行时 ID 是递增的, 所以 PacakgeGroup 与 LoadedPackage 是一一对应的关系
// 也就是说 packages_ 只有一条数据
package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// 将 LoadedPacakge 的中所有的 包名 与 编译时 ID 映射关系保存在 PackageGroup 的 mEntries 中
for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
// 获取当前 pacakge 的包名
String16 package_name(entry.package_name.c_str(), entry.package_name.size());
// 添加 包名 和 编译时 ID 的键值对
// key: package_name
// value: LoadedPackage 的静态 ID
package_group->dynamic_ref_table.mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
}
}
// 2. 为所有的 PackageGroup 动态引用表 DynamicRefTable 注入运行时 ID
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
// 2.1 获取 iter 中 LoadedPacakge 的包名
// 上面提到过, 一个 PackageGroup 只存在一个 LoadedPacakge, 只取第 0 个元素即可
const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
// 2.2 为引用了 iter 资源的所有 PacakgeGroup.DynamicRefTable 引用表中注入 iter 的运行时 ID mAssignedPackageId
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table.mAssignedPackageId);
}
}
}
// frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId)
{
// 2.2.1 获取 packageName 对应的编译时 ID
ssize_t index = mEntries.indexOfKey(packageName);
if (index < 0) {
return UNKNOWN_ERROR;
}
// 2.2.2 将运行时 ID packageId 存储在, 以编译时 ID 为索引的槽内
mLookupTable[mEntries.valueAt(index)] = packageId;
return NO_ERROR;
}
动态引用表的构建是相当复杂的, 代码中的注释也非常的详细, 总结下来主要有两步
- 为共享资源库分配运行时 ID
- framework-res.apk 为 0x01, 我们的 App 为 0x7f, 他们的运行时 ID 与编译时 ID 一致
- 为所有的 LoadedPackage 创建对应的 PacakgeGroup
- 生成运行时 ID, 保存在 PacakgeGroup 的 mAssignedPackageId 字段中
- 将 LoadedPackage 的编译时 ID, 注入到 PacakgeGroup 动态引用表的 mEntry 中
- 为所有的 PackageGroup 动态引用表 DynamicRefTable 注入运行时 ID
动态引用表的构建, 是为了解决共享资源库在我们 resources.arsc 中的编译时 ID 和运行时动态分配的 ID 不一致的问题
三) 回顾
AssetManager 的构建算是资源管理的核心内容了, 它主要处理的事务如下
- 构建 ApkAsset
- 创建 Native 的 ApkAsset, 解析 apk 中的 resources.arsc 数据到 LoadedArsc
- 保存 resources.arsc 中的资源项值的字符串资源池
- 构建 AssetManager
- 合并 framework-res.apk 的系统资源 ApkAsset 集合
- 构建 Native 层的 AssetManager2
- 将合并后的资源集合保存在 AssetManager2 中
- 保存资源集合
- 重新构建引用表
- 重新构建缓存集合
- 清空缓存
需要注意的是, 虽然我们应用 apk 内在编译时只会生成一个 resource.arsc 文件, 但是这个文件里至少存在两个 LoadedPackage, 一个是我们自己的资源生成的 LoadedPackage, 还有有一个是我们引用的 android 资源, 即 framework-res.apk 中的资源
整个编译时 ID 与运行时 ID 的映射关系如下所示
到这里我们就清楚为什么资源管理类初始化完成之后, 需要进行 R 文件资源 ID 的重写了
为了在运行时能够正确的定位资源, 我们需要将 R 文件中所有的 ID 的 packageId 替换成运行时 ID, 这样才能够通过 package_ids_ 表正确的定位到最终的目标资源包
总结
构建 Application 的 ContextImpl 中通过 ResourcesManager 获取 Resources 的步骤如下
- 构建 ResourcesKey
- 根据 ResourcesKey 从缓存 mResourceImpls 中获取 ResourcesImpl 对象
- 缓存不存在则构建 ResourcesImpl 对象, 创建流程如下
- 获取 AssetManager
- 构建 ApkAsset
- 创建 Native 的 ApkAsset, 解析 apk 中的 resources.arsc 数据到 LoadedArsc
- 保存 resources.arsc 中的资源项值的字符串资源池
- 构建 AssetManager
- 合并 framework-res.apk 的系统资源 ApkAsset 集合
- 构建 Native 层的 AssetManager2
- 将合并后的资源集合保存在 AssetManager2 中
- 保存资源集合
- 重新构建引用表
- 重新构建缓存集合
- 刷新缓存
- 构建 ApkAsset
- 创建 ResourcesImpl 对象
- 内部持有 AseetManager
- 获取 AssetManager
- 缓存不存在则构建 ResourcesImpl 对象, 创建流程如下
- 优先从 mResourceReferences 缓存中获取 Resources 对象
- 不存在则创建新的 Resources 对象
- 将 ClassLoader 和 ResourcesImpl 注入
- 不存在则创建新的 Resources 对象
当 Application 创建完成之后, 会通过 rewriteRValues 来重新复写 R 文件中的 ID 为运行时 ID
资源管理的创建我们就分析到这里, 后面一篇文章我们着重分析一下资源的查找流程, 看看我们是如何通过资源表找到对应的资源的