概述
我們在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
,也就是用的我們更換後的資源,這個就是熱修復的資源修復的原理
參考:http://www.apkbus.com/blog-822415-78080.html
https://blog.csdn.net/singwhatiwanna/article/details/24532419