Android插件化-Resource Injection-资源插件化

549 阅读1分钟

「这是我参与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;
    }
}

这样就完成了资源注入