继Activity的插件化juejin.im/post/5c67da…之后, 现在我们来看看资源的插件化方法,这里以VirtualApk的实现方案为例进行讲解,资源的插件化方案分为两种:一种是合并资源方案,将插件的所有资源添加到宿主的Resources中,这种插件方案可以访问宿主的资源。另一种是构建插件资源方案,每个每个插件都构造出独立的Resources,这种方案不可以访问宿主资源。具体的代码逻辑在LoadedPlugin中,如下所示:
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {
return ResourcesManager.createResources(context, packageName, apk);
} else {
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
},
复制代码
createResources方法用于创建Resources,如是是资源合并方案,会调用ResourcesManager的createResources方法,其内部会得到包含宿主资源的AssetManager,再通过反射调用AsserManager的addAssetPath来添加插件资源,返回新的Resources,最后采用Hook的方式用新的Resources替换就的Resources。
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return createResourcesForN(hostContext, packageName, apk);
}
Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
ResourcesManager.hookResources(hostContext, resources);
return resources;
}
复制代码
创建新的Resources的代码如下:
private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
assetManager = AssetManager.class.newInstance();
reflector.bind(assetManager);
final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;
if (cookie1 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir);
}
} else {
assetManager = hostResources.getAssets();
reflector.bind(assetManager);
}
final int cookie2 = reflector.call(apk);
if (cookie2 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + apk);
}
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
final int cookie3 = reflector.call(plugin.getLocation());
if (cookie3 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
}
}
if (isMiUi(hostResources)) {
newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
} else if (isVivo(hostResources)) {
newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
} else if (isNubia(hostResources)) {
newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
} else if (isNotRawResources(hostResources)) {
newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
} else {
// is raw android resources
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
// lastly, sync all LoadedPlugin to newResources
for (LoadedPlugin plugin : pluginList) {
plugin.updateResources(newResources);
}
return newResources;
}
复制代码
执行addAssetPath方法之后,插件中的资源就合并到宿主资源中去了,形成新的Resources,之后采用hook技术用新的Resources去代替旧的资源。
public static void hookResources(Context base, Resources resources) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return;
}
try {
Reflector reflector = Reflector.with(base);
reflector.field("mResources").set(resources);
Object loadedApk = reflector.field("mPackageInfo").get();
Reflector.with(loadedApk).field("mResources").set(resources);
Object activityThread = ActivityThread.currentActivityThread();
Object resManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
resManager = android.app.ResourcesManager.getInstance();
} else {
resManager = Reflector.with(activityThread).field("mResourcesManager").get();
}
Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field("mActiveResources").get();
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} catch (Exception e) {
Log.w(TAG, e);
}
}
复制代码
接下来继续讲解第二种方式,如果采用构建插件资源方案,会先创建AssetManager,再创建Resources并将AssetManager作为参数传进去。createAssetManager方法如下:
protected AssetManager createAssetManager(Context context, File apk) throws Exception {
AssetManager am = AssetManager.class.newInstance();
Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath());
return am;
}
复制代码
首先动态创建AssetManager,再反射调用AssetManager的addAssetPath方法来加载插件,这个AssetManager只包含插件资源,所以,该方法新创建的Resources是插件的资源。 这就是资源的加载过程,当然还需要开绿系统版本和手机型号的差异性,读者可以自行阅读VituralApk的源码。github.com/didi/Virtua…