作为一名合格的Android开发者,相信大家对于资源加载操作已经很熟悉了,一般情况下我们都是通过getResources()方法来获取资源。举个简单的栗子,在这里我们需要给TextView设置文字,我们只需要这么操作:
TextView mTitle = findViewById(R.id.tv_title);
mTitle.setText(getResources().getString(R.string.app_name));
今天我就带领大家从源码的角度一起来学习下Android 资源加载过程。谈到Android资源加载,和Activity的创建是密不可分的。想必大家都知道,Android中Activity的创建是通过IPC机制来实现的,最终会调用到ActivityThread类中的handleLaunchActivity方法,在handleLaunchActivity方法中接着调用到performLaunchActivity方法,performLaunchActivity方法中完成了Activity对象的创建以及后续onCreate、onStart生命周期方法的回调。下面我们一起来看下ActivityThread类中的 performLaunchActivity 方法(为方便分析,源码有所删减。今天我们的分析重点在于资源加载过程,如果大家对于Activity的创建流程感兴趣的话,可以移至笔者的文章 Activity 启动流程源码解析:https://www.jianshu.com/p/621ae18547b0。)
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//1.重点 创建 ContextImpl 实例对象,并将ContextImpl 与 Resources进行关联
ContextImpl appContext = createBaseContextForActivity(r);
//创建Activity实例
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
if (activity != null) {
//2.重点 将 Activity 与 ContextImpl 实例进行关联
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
...
}
上述代码重要部分已经做了标注,我们先看下 1 处的 createBaseContextForActivity 方法:
#ActivityThread
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
...
return appContext;
}
可以看到在 createBaseContextForActivity 方法中直接调用到 ContextImpl.createActivityContext方法,最后将创建好的ContextImpl 实例直接return掉。我们接着跟进去ContextImpl 的createActivityContext方法看下:
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
//1.创建 ContextImpl 实例对象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
//2. 通过 ResourcesManager.getInstance()方法,获取到 ResourcesManager 单例对象
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Create the base resources for which all configuration contexts for this Activity
// will be rebased upon.
//3. 重点:创建当前Activity对应的Resources对象并将该Resources对象赋值给ContextImpl 的mResources成员变量
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
在这里我们重点看下 3 处,可以看到,3 处调用到 ResourcesManager 的createBaseActivityResources方法,创建了当前Activity对应的Resources对象,我们跟进去看下:
#ResourcesManager
public @Nullable Resources createBaseActivityResources(@NonNull 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 {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#createBaseActivityResources");
//1.创建ResourcesKey对象,其中resDir为APP资源路径
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
//当前classLoader为PathClassLoader
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
if (DEBUG) {
Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
+ " with key=" + key);
}
synchronized (this) {
//2.获取到当前Activity对应的ActivityResources对象
getOrCreateActivityResourcesStructLocked(activityToken);
}
// Update any existing Activity Resources references.
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
// Now request an actual Resources object.
//3. 完成Resources对象的获取或创建操作
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
我们首先看下 2 处 getOrCreateActivityResourcesStructLocked方法:
#ResourcesManager
private final WeakHashMap mActivityResourceReferences =
new WeakHashMap<>();
private ActivityResources getOrCreateActivityResourcesStructLocked(
@NonNull IBinder activityToken) {
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
if (activityResources == null) {
activityResources = new ActivityResources();
mActivityResourceReferences.put(activityToken, activityResources);
}
return activityResources;
}
通过上述代码,很显然mActivityResourceReferences主要是用来做缓存操作的,mActivityResourceReferences本质为HashMap,以activityToken为key,以ActivityResources 对象为value。ActivityResources 是个什么东东呢?我们看下ActivityResources 类的定义:
/**
* Resources and base configuration override associated with an Activity.
*/
private static class ActivityResources {
public final Configuration overrideConfig = new Configuration();
public final ArrayList> activityResources = new ArrayList<>();
}
简单来讲ActivityResources 中主要存储当前activity的资源和基本配置信息,可以看到在 ActivityResources 中存在ArrayList集合activityResources ,activityResources 中存储了持有弱引用的Resources对象。
好了,我们回到ResourcesManager 的createBaseActivityResources方法中 3 处getOrCreateResources方法接着看下:
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
if (activityToken != null) {
//1.获取当前activity对应的ActivityResources对象
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(activityResources.activityResources,
sEmptyReferencePredicate);
// Rebase the key's override config on top of the Activity's base override.
if (key.hasOverrideConfiguration()
&& !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
final Configuration temp = new Configuration(activityResources.overrideConfig);
temp.updateFrom(key.mOverrideConfiguration);
key.mOverrideConfiguration.setTo(temp);
}
//2.调用findResourcesImplForKeyLocked方法,获取到对应的resourcesImpl对象
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
} else {
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
// Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
}
}
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
//3.重点,创建ResourcesImpl对象
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
//缓存操作:从mResourceImpls中获取对应的ResourcesImpl对象existingResourcesImpl,
//若存在,则将existingResourcesImpl赋值给resourcesImpl,若不存在,则将resourcesImpl 添加到mResourceImpls缓存中
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ " new impl=" + resourcesImpl);
}
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
if (activityToken != null) {
//4.重点,创建Resources对象
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
我们首先看下 3处ResourcesImpl对象的创建:
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对象,通过构造方法将assets赋值给ResourcesImpl的成员变量mAssets
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
我们接着跟进去createAssetManager方法,看下AssetManager对象的创建:
#ResourcesManager
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
//创建AssetManager实例对象
AssetManager assets = new AssetManager();
//添加资源路径操作
if (key.mResDir != null) {
if (assets.addAssetPath(key.mResDir) == 0) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
}
return assets;
}
好了,我们分析完ResourcesImpl对象的创建,接着看下 4 处getOrCreateResourcesForActivityLocked方法,分析下Resources对象的创建:
/**
* Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
* or the class loader is different.
*/
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
//1.获取当前Activity对应的ActivityResources对象
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
//2.由上述ActivityResources的分析可知,activityResources为ArrayList集合,存储了持有弱引用的Resources对象
//对activityResources进行遍历操作,从activityResources缓存中获取当前Activity对应的Resources对象
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference weakResourceRef = activityResources.activityResources.get(i);
Resources resources = weakResourceRef.get();
if (resources != null
&& Objects.equals(resources.getClassLoader(), classLoader)
&& resources.getImpl() == impl) {
if (DEBUG) {
Slog.d(TAG, "- using existing ref=" + resources);
}
return resources;
}
}
//若activityResources缓存中不存在相应的Resources对象则执行 new 创建操作
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
//ResourcesImpl 与Resources 进行关联操作,将impl赋值给Resources对象的成员变量mResourcesImpl
resources.setImpl(impl);
//添加到activityResources缓存中
activityResources.activityResources.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
好了好了,我们顺着代码的执行顺序回到最初ActivityThread类的performLaunchActivity方法中,接着看下 2 处Activity的attach方法:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
//重点
attachBaseContext(context);
...
}
---
#ContextThemeWrapper
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
---
#ContextWrapper
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
可以看到最终将Activity创建过程中的ContextImpl对象赋值给当前Activity的mBase 成员变量。
好了,Activity创建过程中对应Resources的创建流程就分析完毕了,大家还记得文章开始的举例吗?
TextView mTitle = findViewById(R.id.tv_title);
mTitle.setText(getResources().getString(R.string.app_name));
相信看到这里你肯定会对它的源码感兴趣,我们跟进去看下:
#ContextThemeWrapper
@Override
public Resources getResources() {
return getResourcesInternal();
}
---
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
//重点
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
跟进去super.getResources()看下:
#ContextWrapper
@Override
public Resources getResources() {
return mBase.getResources();
}
哈哈,还记得我们刚分析过的mBase的赋值吗?对的,就是当前activity创建过程中对应的ContextImpl对象,我们跟进去ContextImpl中的getResources方法看下:
#ContextImpl
@Override
public Resources getResources() {
return mResources;
}
等等,mResources不正是我们之前分析过的当前Activity对应的Resources对象嘛,接着我们跟进去看下Resources中的getString方法:
#Resources
@NonNull
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
接着跟:
#Resources
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
可以看到在Resources的getText方法中最终调用到AssetManager的getResourceText方法,其实无论是文字还是图片等资源文件的加载,最终都是通过AssetManager来完成的,我们跟进去AssetManager的getResourceText方法看下:
#AssetManager
@Nullable
final CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
final TypedValue outValue = mValue;
//重点
if (getResourceValue(resId, 0, outValue, true)) {
return outValue.coerceToString();
}
return null;
}
}
---
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
//重点
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
if (block < 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mStringBlocks[block].get(outValue.data);
}
return true;
}
}
---
/** Returns true if the resource was found, filling in mRetStringBlock and
* mRetData. */
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
可以看到,在AssetManager中最终调用到了native方法,这里就不继续分析了。
到这里,Android 资源加载相关的分析就结束了,欢迎大家互相交流。