资源替换
前两回分析了如何动态加载dex并且执行里面的函数。这时候已经可以动态升级dex/apk了。那么apk的资源怎么动态替换呢。DexClassLoader只能加载dex,无法加载apk中的资源。
有的解决方案是,所有的XML呈现的布局全部用Java代码实现。这样做是可行的,真的有人在用,但很繁琐,比如分辨率等问题实现起来也会比较复杂。
dynamic-load-apk方案用的是这样的:
首先,Activity继承了Context,Context中有两个抽象方法:
/** Return an AssetManager instance for your application's package. */
/** 为应用程序的包返回一个AssetManager实例。 */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
/** 为应用程序包返回一个Resources实例 */
public abstract Resources getResources();
context就是通过这两个方法在Activity中获取资源的。这两个方法的实现在ContextImpl中。getResuorces返回的resources是这样的:
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
//getTopLevelResources根据资源目录、分隔资源目录..等参数提供顶级资源给application
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
overrideConfiguration, compatInfo);
}
}
我们也想通过getResources获取资源,那么可以模仿它这样来写。
1.AssetManager中有addAssetsPath方法,参数可以是一个dircetory或者一个ZIP file。它里面有个native方法可以把资源加载到res中。
2.嗯,然后用assetManager来反射调用(为什么addAssetPath是隐藏的API?)addAssetPath方法,参数是mDexPath,也就是外部apk的路径。如下。
addAssetPath.invoke(assetManager, mDexPath);
- 3.然后用这个assetManager创建一个新的资源:
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
最后,要覆写那两个抽象方法哦,不然的话会一直用父类默认得到的AssetManager和Resources:
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
大概就是这样了。
-NOV29