插件化和熱修復(fù)對(duì)資源和類(lèi)加載的管理
1 插件化為什么宿主可以解析插件資源
2 熱修復(fù)為什么可以解析補(bǔ)丁資源
3 插件化為什么宿主可以加載插件代碼
4 熱修復(fù)為什么可以加載補(bǔ)丁代碼
插件化框架以VirtualApk為例子
熱修復(fù)以Tinker為例子
1 插件化分析加載流程
插件化加載插件插件時(shí)候,在VirtualApk中每一次調(diào)用加載插件使用如下方法
File plugin = new File(pluginPath);
PluginManager.getInstance(this).loadPlugin(plugin);
拆開(kāi)來(lái)看分為L(zhǎng)oadedPlugin對(duì)象初始化,然后反射Application的onCreate
LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk)
plugin.invokeApplication();
在構(gòu)造插件LoaderPlugin對(duì)象,構(gòu)造函數(shù)初始化很多成員變量偎箫,很多類(lèi)似系統(tǒng)framework做的事情怠蹂,比如四大組件信息存儲(chǔ)讯榕。包括apk包解析流程蚊惯。
this.mPluginManager = pluginManager;
this.mHostContext = context;
this.mLocation = apk.getAbsolutePath();
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
this.mPackageInfo = new PackageInfo();
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
this.mPackageInfo.versionName = this.mPackage.mVersionName;
this.mPackageInfo.permissions = new PermissionInfo[0];
this.mPackageManager = new PluginPackageManager();
this.mPluginContext = new PluginContext(this);
this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
this.mResources = createResources(context, apk);
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
這里面把很多apk的信息基本挖掘一個(gè)遍吆倦,
包括四大組件信息说订,同時(shí)還要關(guān)注2個(gè)成員變量
this.mResources = createResources(context, apk);
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
這也是加載代碼和資源的核心所在
對(duì)于資源加載抄瓦,主要是是反射AssetManager.class的addAssetPath方法然后有了這個(gè)assetManager就可以再new 一個(gè)Resource了 潮瓶,反射Resource對(duì)象內(nèi)部assetsManager對(duì)象addAssetPath方法如下ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);
if (Constants.COMBINE_RESOURCES) {
Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
ResourcesManager.hookResources(context, resources);
return resources;
} else {
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
再看類(lèi)加載器,使用DexClassLoader良瞧。如果是合并模式似炎,是直接把插件的dex文件合并到宿主的dexElements數(shù)組中去
File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {
try {
DexUtil.insertDex(loader);
} catch (Exception e) {
e.printStackTrace();
}
}
return loader;
public static void insertDex(DexClassLoader dexClassLoader) throws Exception {
Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
Object newDexElements = getDexElements(getPathList(dexClassLoader));
Object allDexElements = combineArray(baseDexElements, newDexElements);
Object pathList = getPathList(getPathClassLoader());
ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements);
insertNativeLibrary(dexClassLoader);
}
繼續(xù)看
invokeApplication
this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
instrumentation.callApplicationOnCreate(this.mApplication);
return this.mApplication;
通過(guò)模擬framework加載application,這段代碼不是無(wú)中生有展东,在framework代碼里面的ActivityThread中performLaunchActivity時(shí)候可以找到調(diào)用的邏輯梗摇。 Application app = r.packageInfo.makeApplication(false, mInstrumentation);
這里面makeApplication就是懶加載,如果沒(méi)有加載去new一個(gè)袭景,當(dāng)然也是反射來(lái)構(gòu)造對(duì)象
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
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);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
...
instrumentation.callApplicationOnCreate(app);
感覺(jué)代碼幾乎一樣
插件化小結(jié):加載插件時(shí)候去初始化Resources和ClassLoader對(duì)象酝豪。反射模擬framework來(lái)初始化Application對(duì)象敛惊,這時(shí)候去調(diào)用Application的onCreate模擬插件應(yīng)用啟動(dòng)膊毁。同時(shí)類(lèi)加載器也已經(jīng)設(shè)置好胀莹,資源文件也設(shè)置好,也就可以加載插件的代碼和資源文件了
繼續(xù)看熱修復(fù)里面對(duì)資源文件和 補(bǔ)丁代碼的處理
熱修復(fù)針對(duì)資源文件的處理
在Tinker中
newAssetManager = (AssetManager) findConstructor(assets).newInstance();
final Resources resources = context.getResources();
assetsFiled = findField(resources, "mAssets");
packagesFields = new Field[]{packagesFiled};
}
for (Field field : packagesFields) {
final Object value = field.get(currentActivityThread);
addAssetPathMethod = findMethod(assets, "addAssetPath", String.class);
if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) {
}
getResources()返回的Resources對(duì)象內(nèi)部也是有assetsManager對(duì)象婚温,不過(guò)關(guān)系是Resources中有ResourcesImpl描焰,ResourcesImpl有assetsManager
對(duì)于AssetsManager可以反射addAssetPath,這樣getResource()就可以識(shí)別指定補(bǔ)丁文件的資源文件
Tinker加載補(bǔ)丁代碼部分
TinkerDexLoader.loadTinkerJars
里面針對(duì)不同版本適配,以V23為例
V23.install(classLoader, files, dexOptDir);
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
這里面仍然是反射ClassLoader中makeElements使dexElements數(shù)組容納補(bǔ)丁,這樣類(lèi)加載器也就可以加載對(duì)應(yīng)補(bǔ)丁了
總而言之:如果是希望當(dāng)前類(lèi)加載器能夠加載額外的dex文件栅螟,是通過(guò)反射BaseClassLoader里面pathList的dexElements對(duì)象
如果是加載額外的資源文件荆秦,需要反射Resources對(duì)象成員變量AssetsManager的addAssetPath方法