插件化框架實(shí)現(xiàn):基于kotlin的插件化框架
Android 資源訪問(wèn)
AndroidResource.png
- android通過(guò)aapt將資源編譯成R.java索引席噩,在代碼中通過(guò)R文件來(lái)引用具體資源
- 在構(gòu)建成apk時(shí)通過(guò)對(duì)應(yīng)資源和其R文件的id生成resource.arsc跷叉,在app啟動(dòng)時(shí)會(huì)通過(guò)AssetManager的
addAssetPath()
方法添加系統(tǒng)資源和apk資源者铜,并構(gòu)造Resource提供給Context上下文進(jìn)行使用阳柔,所以真正加載資源是通過(guò)AssetManger去加載
AssetManager的創(chuàng)建時(shí)機(jī)
- 從Context的
getResource()
跟蹤到ContextImpl構(gòu)造器:
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration) {
// ...
mResourcesManager = ResourcesManager.getInstance();
// ...
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (activityToken != null
|| displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
overrideConfiguration, compatInfo, activityToken);
}
}
mResources = resources;
}
- 根據(jù)代碼追蹤到ResourcesManager的
getTopLevelResources()
方法:
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (false) {
Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
}
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (false) {
Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
}
return r;
}
}
// ...
// 創(chuàng)建AssertManager
AssetManager assets = new AssetManager();
// ...
// assets.addAssetPath(resDir)
r = new Resources(assets, dm, config, compatInfo, token);
mActiveResources.put(key, new WeakReference<Resources>(r));
return r;
}
- 在這里可以知道AssetManager是在第一次new ContextImpl時(shí)創(chuàng)建的势木,并構(gòu)造并緩存Resource提供給后續(xù)Context
Instant Run 資源替換部分
- Part 1. 創(chuàng)建一個(gè)新的 AssetManager,并通過(guò)反射調(diào)用 addAssetPath 添加 /sdcard 上的新資源包屏歹。
AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance();
Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
mAddAssetPath.setAccessible(true);
if (((Integer) mAddAssetPath.invoke(newAssetManager,externalResourceFile)) == 0) {
throw new IllegalStateException("Could not create newAssetManager");
}
- Part 2. 反射得到 Activities 中 AssetManager 的引用處,全部換成剛才新構(gòu)建的newAssetManager
for (Activity activity : activities) {
Resources resources = activity.getResources();
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().
getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
}
- Part 3. 得到 Resources 的弱引用集合圃庭,把他們的 AssetManager 成員替換成newAssetManager
Collection<WeakReference<Resources>> references;
if (SDK_INT >= KITKAT) {
// Find the singleton instance of ResourcesManager
Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance");
mGetInstance.setAccessible(true);
Object resourcesManager = mGetInstance.invoke(null);
try {
Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
@SuppressWarnings("unchecked")
ArrayMap<?, WeakReference<Resources>> arrayMap = (ArrayMap<?, WeakReference<Resources>>)fMActiveResources.get(resourcesManager);
references = arrayMap.values();
} catch (NoSuchFieldException ignore) {
Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
mResourceReferences.setAccessible(true);
//noinspection unchecked
references = (Collection<WeakReference<Resources>>)
mResourceReferences.get(resourcesManager);
}
} else {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
Object thread = getActivityThread(context, activityThread);
@SuppressWarnings("unchecked")
HashMap<?, WeakReference<Resources>> map = (HashMap<?, WeakReference<Resources>>) fMActiveResources.get(thread);
references = map.values();
}
for (WeakReference<Resources> wr : references) {
Resources resources = wr.get();
if (resources != null) {
// Set the AssetManager of the Resources instance to our brand new one
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
resources.updateConfiguration(resources.getConfiguration(),
resources.getDisplayMetrics());
}
}
AssetManager版本差異
根據(jù)阿里發(fā)布的《深入探索Android熱修復(fù)技術(shù)原理6.29》p114 - 117的描述以及源碼分析可以知道
- Android 4.4 及以下版本,addAssetPath 只是把補(bǔ)丁包的路徑添加到了 mAssetPath 中失晴,而真正解析的資源包的邏輯是在 app 第一次執(zhí)行 AssetManager::getResTable的時(shí)候剧腻。
- 所以,像 Instant Run 這種方案涂屁,一定需要一個(gè)全新的 AssetManager 時(shí)书在,然后再加入完整的新資源包,替換掉原有的 AssetManager拆又。
資源id沖突 問(wèn)題
- 知道AssetManager的構(gòu)造以及怎么加載插件資源儒旬,還有個(gè)問(wèn)題就是資源id沖突問(wèn)題栏账,導(dǎo)致資源獲取錯(cuò)誤問(wèn)題
解決方案
- 修改aapt
- 修改resource.arsc
- 宿主插件之間資源隔離
aapt
- 根據(jù)插件包的唯一id作為資源的package id來(lái)修改aapt生成資源的id,來(lái)保證資源id不沖突
- 但是這種方案痛點(diǎn)是編譯版本的改變的話aapt也需要進(jìn)行修改定制
修改resource.arsc
- 根據(jù)插件包的唯一id作為package id來(lái)修改插件包打包后的resource.arsc的package id值
宿主插件之間資源隔離
- 通過(guò)單獨(dú)構(gòu)造宿主的AssetManager和Resource來(lái)對(duì)宿主資源的加載栈源,但這種方式宿主插件間無(wú)法訪問(wèn)資源
資源id沖突的思考
在解決資源id沖突這方面挡爵,我更傾向于宿主插件之間資源隔離,從以下幾方面考慮
- 首先各大廠商都對(duì)系統(tǒng)做過(guò)定制甚垦,需要做不同平臺(tái)的設(shè)配
- 宿主插件都擁有各自的Resource茶鹃,不必采用像Instant Run方式實(shí)現(xiàn),能更好兼容后續(xù)Android更高版本
- 通過(guò)資源隔離艰亮,插件包可以使一個(gè)單獨(dú)的apk闭翩,可以直接運(yùn)行安裝
Android 7.0 WebView
- Android 7.0創(chuàng)建WebView的時(shí)候會(huì)重新創(chuàng)建AssetManager
- 目前的解決辦法可以是加載插件之前先創(chuàng)建WebView來(lái)避免
注
- Instant Run部分來(lái)自《深入探索Android熱修復(fù)技術(shù)原理6.29》
- AssetManager版本差異部分來(lái)自根據(jù)阿里發(fā)布的《深入探索Android熱修復(fù)技術(shù)原理6.29》p114 - 117