加载插件时需要获取到插件APK的详细信息,比如AndroidManifest.xml中注册的四大组件。Android SDK提供了接口,如下:
PackageManager pm = getPackageManager();
PackageInfo packageInfo = pm.getPackageArchiveInfo(
apkFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES
| PackageManager.GET_SERVICES
| PackageManager.GET_PROVIDERS
| PackageManager.GET_RECEIVERS
| PackageManager.GET_META_DATA);
ActivityInfo[] activityInfos = packageInfo.activities;
ApplicationInfo applicationinfo = packageInfo.applicationInfo;
Bundle metaData = applicationinfo.metaData;
ServiceInfo[] serviceInfos = packageInfo.services;
ProviderInfo[] providerInfos = packageInfo.providers;
ActivityInfo[] receiverInfos = packageInfo.receivers;
我们重点关注一下Activity,发现ActivityInfo中基本的信息都有,包括name、processName、theme、configChanges、launchMode等,不过缺少一个非常关键的东西,那就是Intent-Filter。
为什么我会专门提到这个呢?如果我们启动插件中的组件时直接指定类名,那确实不用理会Intent-Filter了,直接按name查找组件就OK了。比如我们目前使用的插件框架,调起插件Apk中的Activity都是通过指定Activity的类名来查找对应的Activity并将其当做普通类调起,然后在一个Proxy Activity中调用其生命周期的回调。如果要调起插件Apk中的Launcher Activity,宿主APP是无法知道谁是插件的Launcher Activity的,因为我们拿不到Intent-Filter。解决办法是在插件的AndroidManifest.xml的meta-data中声明一个专用类,宿主APP会调用该类的特定函数来启动Launcher Activity,由于是插件内部类调的,所以插件自己当然知道哪个是Launcher Activity,宿主就不用操心了。这一系列做法虽然能解决问题,不过总显得很累赘和不自然,别人为了做插件需要额外增加不少工作量,而本文就是为了解决这一系列问题提出一些思路。
这里核心问题是如何在SDK未提供相应接口的情况下获取Activity的Intent Filter。
我们先看看pm.getPackageArchiveInfo的实现,或许其中可以发现点什么:
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
final PackageParser parser = new PackageParser();
final File apkFile = new File(archiveFilePath);
try {
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
} catch (PackageParserException e) {
return null;
}
}
这里先新建一个PackageParser,调用parseMonolithicPackage生成Package,然后调用其静态函数generatePackageInfo来解析并生成PackageInfo。值得一提的是这个parseMonolithicPackage用于解析单独的apk文件,如果是apk的目录则用parseClusterPackage函数解析。在PackageParser中有一个函数会自动根据apk文件类型来分别调用对应的解析函数,如下:
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
接下来我们看看parseMonolithicPackage的实现:
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final AssetManager assets = new AssetManager();
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
可见这里核心是调用了parseBaseApk,继续看下去: