「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」
Android应用中所有资源(layout、value等)都会被打包到Apk中,生成一个对应的R类,其中包含对所有资源引用的id。
主要是两个接口:
- PackageManger#getPackageArchiveInfo:根据Apk路径解析一个未安装的Apk的PackageInfo
- PackageManager#getResourcesForApplication:根据ApplicationInfo创建一个Resource实例
我们要做的就是在ContainerActivity#onCreate中加载插件Apk的时候,用这两个方法创建出来一份插件资源实例。具体来说就是先用PackageManger#getPackageArchiveInfo拿到插件Apk的PackageInfo,有了PackageInfo之后我们就可以自己组一份ApplicationInfo,然后通过PackageManager#getResourcesForApplication来创建资源实例:
PackageManager packageManager = getPackageManager();
PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(
pluginApkPath,
PackageManager.GET_ACTIVITIES
| PackageManager.GET_META_DATA
| PackageManager.GET_SERVICES
| PackageManager.GET_PROVIDERS
| PackageManager.GET_SIGNATURES
);
packageArchiveInfo.applicationInfo.sourceDir = pluginApkPath;
packageArchiveInfo.applicationInfo.publicSourceDir = pluginApkPath;
Resources injectResources = null;
try {
injectResources = packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
// ...
}
拿到资源实例后,我们需要将宿主的资源和插件资源Merge一下,编写一个新的Resources类:
public class PluginResources extends Resources {
private Resources hostResources;
private Resources injectResources;
public PluginResources(Resources hostResources, Resources injectResources) {
super(injectResources.getAssets(), injectResources.getDisplayMetrics(), injectResources.getConfiguration());
this.hostResources = hostResources;
this.injectResources = injectResources;
}
@Override
public String getString(int id, Object... formatArgs) throws NotFoundException {
try {
return injectResources.getString(id, formatArgs);
} catch (NotFoundException e) {
return hostResources.getString(id, formatArgs);
}
}
// ...
}
然后在ContainerActivity完成插件组件加载后,创建一份Merge资源,再复写ContainerActivity#getResources,将获取到的资源替换掉:
public class ContainerActivity extends Activity {
private Resources pluginResources;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
pluginResources = new PluginResources(super.getResources(), PluginLoader.getResources(pluginApkPath));
// ...
}
@Override
public Resources getResources() {
if (pluginActivity == null) {
return super.getResources();
}
return pluginResources;
}
}
这样就完成了资源注入