概述
我们在Activity
中访问资源(图片,字符串,颜色等)是非常方便的,只需要getResources()
获取一个Resources
对象,然后就可以访问各种资源了,那这些资源到底是怎么被加载的呢?下面我们就分析一下资源加载机制
App启动流程
首先我们回顾一下App启动流程,还不了解的可以看我之前写的这篇文章
- 首先是点击App图标,此时是运行在
Launcher
进程,通过ActivityManagerService
Binder IPC的形式向system_server
进程发起startActivity
的请求 system_server
进程接收到请求后,通过Process.start
方法向zygote
进程发送创建进程的请求zygote
进程fork
出新的子进程,即App
进程- 然后进入
ActivityThread.main
方法中,这时运行在App
进程中,通过ActivityManagerService
Binder IPC的形式向system_server
进程发起attachApplication
请求 system_server
接收到请求后,进行一些列准备工作后,再通过Binder IPC向App
进程发送scheduleLaunchActivity
请求App
进程binder线程(ApplicationThread)
收到请求后,通过Handler
向主线程发送LAUNCH_ACTIVITY
消息- 主线程收到Message后,通过反射机制创建目标
Activity
,并回调Activity
的onCreate
首先我们看第四步,attachApplication
方法,最终会调用thread#bindApplication
然后调用ActivityThread#handleBindApplication
方法,我们从这个方法开始看
ActivityThread#handleBindApplication
private void handleBindApplication(AppBindData data) {
...
final InstrumentationInfo ii;
...
// 创建 mInstrumentation 实例
if (ii != null) {
final ApplicationInfo instrApp = new ApplicationInfo();
ii.copyTo(instrApp);
instrApp.initForUser(UserHandle.myUserId());
final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
...
}
...
} else {
mInstrumentation = new Instrumentation();
}
...
Application app;
...
// 创建 Application 实例
try {
...
app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
...
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
...
}
} finally {
...
}
...
}
// http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/app/LoadedApk.java#959
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
try {
...
//注释1
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
...
}
...
return app;
}
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
这个方法我们只留下了最核心的内容,我们看下注释1, ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
这个方法会直接new
一个新的ContextImpl
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
....
//LoadApk赋值
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
...
//通过LoadApk.getResources获取Resources对象
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
if (container != null) {
// This is a nested Context, so it can't be a base Activity context.
// Just create a regular Resources object associated with the Activity.
resources = mResourcesManager.getResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
} else {
// This is not a nested Context, so it must be the root Activity context.
// All other nested Contexts will inherit the configuration set here.
resources = mResourcesManager.createBaseActivityResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
}
}
}
//为mResources变量赋值
mResources = resources;
...
}
packageInfo.getResources
,packageInfo
是LoadApk
类型的,我们看下这个方法
LoadApk#getResources
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
}
return mResources;
}
其中调用了ActivityThread
的getTopLevelResources
方法,我们继续看一下
ActivityThread#getTopLevelResources
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, LoadedApk pkgInfo) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
}
继续调用了mResourcesManager
的getResources
方法,我么继续跟下去
ResourcesManager#getResources
public @NonNull 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 {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
...
// 创建ResourcesImpl
ResourcesImpl resourcesImpl = createResourcesImpl(key);
....
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
}
return resources;
}
}
private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
final AssetManager assets = createAssetManager(key);
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
AssetManager assets = new AssetManager();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (key.mResDir != null) {
if (assets.addAssetPath(key.mResDir) == 0) {
throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
throw new Resources.NotFoundException(
"failed to add split asset path " + splitResDir);
}
}
}
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;
}
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl) {
// Find an existing Resources that has this ResourcesImpl set.
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.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;
}
}
// Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
首先调用createResourcesImpl
,创建ResourcesImpl
,我们看下这个方法内部创建了AssetManager assets = new AssetManager();
,然后调用assets.addAssetPath
添加资源地址,最后返回final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
,最后查看是否有缓存,如果有则返回缓存的resources
,如果没有就重新构建Resources
,然后返回
平时使用
Resources resources = getResources();
resources.getString();
resources.getAssets();
resources.getColor();
resources.getDrawable()
这个就是我们平时使用的代码,通过resources
获取资源,其中getResources
方法返回的就是上方ContextImpl
创建的mResources
变量,然后我们分析一下getString
方法的实现
## Resources.java
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
@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));
}
最后交给了mResourcesImpl.getAssets().getResourceText(id)
;,我们继续看下这个方法
## ResourcesImpl.java
public AssetManager getAssets() {
return mAssets;
}
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
最后交给了mAssets
变量处理,mAssets
变量就是创建ResourcesImpl
时传入的AssetsManager
,其实最后就是委托给了AssetsManager
去处理
总结
我们发现Apk的资源是通过AssetManager.addAssetPath
方法来完成加载,那么我们就可以通过反射构建自己的AssetManager
对象,然后把调用addAssetPath
加载自己的资源,然后把自己构建的AssetManager
通过反射设置给mAssets
变量,这样下次加载资源就是用的我们AssetManager
,也就是用的我们更换后的资源,这个就是热修复的资源修复的原理