VirtualAPK的使用
VirtualAPK的使用还是蛮简单的,根据提供的文档一步一步来就可以了,但是其中有一点需要注意,那就是plugin的打包。plugin是且必须是一个apk文件,但是我们不能像正常打包流程那样进行打包,否则会抛出java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity
这个异常。我们需要执行命令行./gradlew clean assemblePlugin
或者在AS右边的Gradle里点击assemblePlugin
来进行打包。这样就可以使用VirtualApk来加载Plugin了。
Plugin的加载
LoadedPlugin
中实现了Plugin的加载,主要代码如下:
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
this.mPluginManager = pluginManager;
this.mHostContext = context;//主工程的Context
this.mLocation = apk.getAbsolutePath();//插件的路径
//解析Plugin文件(Plugin必须是一个apk文件)
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
this.mPackageInfo = new PackageInfo();//存储plugin的包信息
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
//签名信息
if (Build.VERSION.SDK_INT >= 28
|| (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview
try {
this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
} catch (Throwable e) {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
this.mPackageInfo.signatures = info.signatures;
}
} else {
this.mPackageInfo.signatures = this.mPackage.mSignatures;
}
this.mPackageInfo.packageName = this.mPackage.packageName;//包名
if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {//判断plugin是否已经加载
throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
}
this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
this.mPackageInfo.versionName = this.mPackage.mVersionName;
this.mPackageInfo.permissions = new PermissionInfo[0];
this.mPackageManager = createPluginPackageManager();//创建plugin包管理
this.mPluginContext = createPluginContext(null);//创建插件的context
this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);
this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();
this.mResources = createResources(context, getPackageName(), apk);//创建Resources
//创建一个ClassLoader,加载plugin中的class
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
tryToCopyNativeLib(apk);//copy插件的so文件到主工程
// Cache instrumentations
Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();
for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {
instrumentations.put(instrumentation.getComponentName(), instrumentation.info);
}
this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);//将mInstrumentationInfos改为只读
this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);
// Cache activities
Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity activity : this.mPackage.activities) {
activity.info.metaData = activity.metaData;
activityInfos.put(activity.getComponentName(), activity.info);
}
this.mActivityInfos = Collections.unmodifiableMap(activityInfos);//将mActivityInfos改为只读
this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);
// Cache services
Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
for (PackageParser.Service service : this.mPackage.services) {
serviceInfos.put(service.getComponentName(), service.info);
}
this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);//将mServiceInfos改为只读
this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);
// Cache providers
Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();
for (PackageParser.Provider provider : this.mPackage.providers) {
providers.put(provider.info.authority, provider.info);
providerInfos.put(provider.getComponentName(), provider.info);
}
this.mProviders = Collections.unmodifiableMap(providers);//将mProviders改为只读
this.mProviderInfos = Collections.unmodifiableMap(providerInfos);
this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);
// Register broadcast receivers dynamically
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
//创建BroadcastReceiver实例
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);//将plugin中的静态BroadcastReceiver转动态注册
}
}
this.mReceiverInfos = Collections.unmodifiableMap(receivers);//将mReceiverInfos改为只读
this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
// try to invoke plugin's application
invokeApplication();//创建plugin的Application对象并调用onCreate方法
}
createResources
的主要实现如下
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {//将plugin的资源与主工程的资源合并
return ResourcesManager.createResources(context, packageName, apk);
} else {//为plugin创建单独的资源管理器
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
合并资源的代码主要在ResourcesManager
中,主要是我们新创建一个包含主项目及plugin项目资源的Resources
对象,然后通过反射进行替换。在这里最主要的问题时由于国内对Android系统进行了修改,所以导致兼容性有问题。
createClassLoader
主要实现如下
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {//如果合并,则将plugin中的dex文件插入到主工程的dex文件之后
DexUtil.insertDex(loader, parent, libsDir);
}
return loader;
}
主要就是为Plugin创建一个ClassLoader
来加载dex文件,如果Constants.COMBINE_CLASSLOADER
为true
,则将plugin中的dex文件插入到主项目的dex列表中,代码如下
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
Object baseDexElements = getDexElements(getPathList(baseClassLoader));//获取主项目的dex列表
Object newDexElements = getDexElements(getPathList(dexClassLoader));//获取plugin的dex文件列表
Object allDexElements = combineArray(baseDexElements, newDexElements);//合并主工程与plugin的dex文件,plugin的dex文件在主工程后面
Object pathList = getPathList(baseClassLoader);
Reflector.with(pathList).field("dexElements").set(allDexElements);//将合并后的dex列表设置给主工程
insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);//
}
最后就是通过invokeApplication
去创建plugin中的Application
并且调用其生命周期。其实plugin的加载流程跟Android安装一个apk的流程有点类似的。可以说是简化版的安装apk。