了解插件化需要先了解通过Binder进行IPC以及Hook这两块儿内容,推荐 维术大神的插件化系列文章,如果想看插件化的具体实现细节原理可以说这个系列的文章必看,当然还要对framework层的源码有一定的了解,比如对四大组件的生命周期过程有一定的了解。因为插件化技术涉及到的不仅仅是一个框架几个类,更多的是对Android Framework层源码的代理反射,只有在了解系统源码的情况下才能找到有效的hook点,才能达到期满AMS/PMS等系统服务,因此,分析DroidPlugin框架的源码建立在对源码有一定的了解的基础上。
在分析源码之前,先对下边几个重要的类进行一下简要说明:
- PluginHelper:外观模式,在Application的onCreate方法中进行插件管理(PluginManager)、进程管理(PluginProcessManager)等模块的初始化。
- IPluginManager.aidl:aidl文件,定义了提供操作插件的方法。比如安装、删除插件,解析插件中的四大组件信息、获取插件权限等。
- PluginManager: 插件管理服务客户端(client)。持有服务端的Binder代理IPluginManager。宿主通过该类中持有的代理间接调用server端提供的方法。
- PluginManagerService:一个运行在后台的service,向客户端提供IPluginManager.Stub实现类,处理客户端的IPC请求。
- PackageParser:插件解析器,用来解析一个插件apk中的信息。策略模式(抽象类),针对不同sdk版本提供了不同的实现,最终是通过反射调用系统 android.content.pm 包下的PackageParser来获取插件中的所有信息。如签名文件,dex路径,清单文件信息(包括声明的四大组件,权限,版本号)等。【其实获取到一个插件apk对应的packageParser就可以获取到基本所有的信息】,在首次装载完成之后,会使用一个Map
进行缓存。判断一个插件是否已经装在也是根据是否命中缓存来判断。
先阐述一下DroidPlugin中涉及的Binder通信:
首先在app启动时,会开启并连接一个服务PluginManagerService
,该service向客户端(PluginManager) 提供一个实现了IPluginManager.Stub的Binder代理对象,在该对象中实现了对插件apk的各种操作。服务连接成功之后,就可以在宿主app中通过客户端PluginManager中的binder代理对象来实现远程调用,从而实现插件的管理。
开始分析:
一、DroidPlugin的集成
- 方式一:清单文件中直接指定
android:name="com.morgoo.droidplugin.PluginApplication"
- 方式二:自定义Application:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
PluginHelper.getInstance().applicationOnCreate(getBaseContext());
}
@Override
protected void attachBaseContext(Context base) {
PluginHelper.getInstance().applicationAttachBaseContext(base);
super.attachBaseContext(base);
}
}
集成完毕。
二、扫描并解析某个路径中的插件
在扫描本地插件文件之前,首先会进行判断:
if (PluginManager.getInstance().isConnected()) {
startLoad();
}
public boolean isConnected() {
return mHostContext != null && mPluginManager != null;
}
其实判断的是宿主application已经初始化并且连接到了PluginManagerService并获取到了binder代理对象。
扫描
也很简单:
File file = Environment.getExternalStorageDirectory();
List apks = new ArrayList(10);
File[] files = file.listFiles();
if (files != null) {
for (File apk : files) {
if (apk.exists() && apk.getPath().toLowerCase().endsWith(".apk")) {
apks.add(apk);
}
}
}
就是扫描指定文件夹下的所有.apk文件。此处示例为sd卡根目录。
解析
接下来就是接下解析扫描到的插件信息:
PackageManager pm = getActivity().getPackageManager();
for (final File apk : apks) {
try {
if (apk.exists() && apk.getPath().toLowerCase().endsWith(".apk")) {
//调用系统方法来解析一个压缩文件中的安装包信息
final PackageInfo info = pm.getPackageArchiveInfo(apk.getPath(), 0);
if (info != null && isViewCreated) { //此处是展示插件的图标,名称,版本号信息。
try {
handler.post(new Runnable() {
@Override
public void run() {
adapter.add(new ApkItem(getActivity(), info, apk.getPath()));
}
});
} catch (Exception e) {
}
}
}
} catch (Exception e) {
}
}
通过调用系统api,来解析一个安装包对应的信息。
结果如图:
解析完毕。
三、安装插件
首次安装:
if (mPluginCache.containsKey(info.packageName)) { //已经安装
return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS;
} else {
forceStopPackage(info.packageName);
new File(apkfile).delete();
Utils.copyFile(filepath, apkfile); //将要安装的插件apk包copy到/data/user/0/com.zhu.droidplugindemo/Plugin/com.zhu.loginmodule/apk/base-1.apk
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); //解析插件包
parser.collectCertificates(0); //收集签名信息
PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
for (String requestedPermission : pkgInfo.requestedPermissions) {
boolean b = false;
try {
b = pm.getPermissionInfo(requestedPermission, 0) != null;
} catch (NameNotFoundException e) {
}
if (!mHostRequestedPermission.contains(requestedPermission) && b) {
Log.w(TAG, "No Permission %s", requestedPermission);
// new File(apkfile).delete();
// return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
}
}
}
saveSignatures(pkgInfo); //保存签名信息到/data/user/0/pkgName/Plugin/Signature/
// if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
// for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
// Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
// }
// }
//copy本地lib包
if (copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0)) < 0) {
new File(apkfile).delete();
return PackageManagerCompat.INSTALL_FAILED_NOT_SUPPORT_ABI;
}
dexOpt(mContext, apkfile, parser);
mPluginCache.put(parser.getPackageName(), parser);
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
sendInstalledBroadcast(info.packageName); //发送广播
return PackageManagerCompat.INSTALL_SUCCEEDED;
}
方法说明:
- 首先将要安装的插件apk包copy到/data/user/0/com.zhu.droidplugindemo/Plugin/com.zhu.loginmodule/apk/base-1.apk。
- 获取到对应版本的包解析器:PackageParser。
- 获取插件的签名信息。
- 插件的权限检查,如果插件中存在宿主未声明的权限,则返回对应的信息【插件化开发的一个弊端就是要事先将所有的权限都声明到宿主中去】
-
保存签名信息到/data/user/0/com.zhu.droidplugindemo/Plugin/Signature/目录下
-
复制lib包下的native库到指定目录
- 将该插件的解析器packageParser加入到缓存中。
- 通知宿主安装完成,发送ACTION_PACKAGE_ADDED广播。
至此,插件的首次安装过程完毕。
更新插件的过程和上边大同小异,只不过在开始安装之前清除了插件/data/data/pkgName/目录下对应的缓存目录:
if (mPluginCache.containsKey(info.packageName)) {
deleteApplicationCacheFiles(info.packageName, null);
}
ok,到此为止,插件已经算是安装完毕。
???这就完了?? 不就是解析了插件包并复制出了几个文件到指定目录吗?
Ofcourse not!此处只能说是识别出了插件,但是还并没有安装插件,还不能通过startActivity来打开插件
往下继续阅读之前,建议先仔细阅读了解 Android 插件化原理解析——Activity生命周期管理 一文中的内容。因为涉及到的点比较多,还包括一些源码,此处就不再赘述,下边的内容假设已经了解了Activity启动过程以及Hook技术。
Hook系统服务
1. 安装Hook
在宿主的Application的OnCreate方法中,会首先调用这么一行代码:
PluginProcessManager.installHook(baseContext);
该方法最终会调用到HookFactory中的installHook方法:
public final void installHook(Context context, ClassLoader classLoader) throws Throwable {
if (ProcessUtils.isMainProcess(context)) {
installHook(new IActivityManagerHook(context), classLoader);
installHook(new IPackageManagerHook(context), classLoader);
} else {
installHook(new IClipboardBinderHook(context), classLoader);
//for ISearchManager
installHook(new ISearchManagerBinderHook(context), classLoader);
//for INotificationManager
installHook(new INotificationManagerBinderHook(context), classLoader);
installHook(new IMountServiceBinderHook(context), classLoader);
installHook(new IAudioServiceBinderHook(context), classLoader);
installHook(new IContentServiceBinderHook(context), classLoader);
installHook(new IWindowManagerBinderHook(context), classLoader);
if (VERSION.SDK_INT > VERSION_CODES.LOLLIPOP_MR1) {
installHook(new IGraphicsStatsBinderHook(context), classLoader);
}
// if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// installHook(new WebViewFactoryProviderHook(context), classLoader);
// }
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
installHook(new IMediaRouterServiceBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
installHook(new ISessionManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
installHook(new IWifiManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
installHook(new IInputMethodManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new ILocationManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new ITelephonyRegistryBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new ISubBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new IPhoneSubInfoBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new ITelephonyBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new ISmsBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
installHook(new IMmsBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
installHook(new IAppOpsServiceBinderHook(context), classLoader);
}
installHook(new IActivityManagerHook(context), classLoader);
installHook(new IPackageManagerHook(context), classLoader);
installHook(new PluginCallbackHook(context), classLoader);
installHook(new InstrumentationHook(context), classLoader);
installHook(new LibCoreHook(context), classLoader);
installHook(new SQLiteDatabaseHook(context), classLoader);
installHook(new IDisplayManagerBinderHook(context), classLoader);
}
}
安装Hook:
public void installHook(Hook hook, ClassLoader cl) {
try {
hook.onInstall(cl);
synchronized (mHookList) {
mHookList.add(hook);
}
} catch (Throwable throwable) {
Log.e(TAG, "installHook %s error", throwable, hook);
}
}
ok,调用了每个Hook的onInstall方法。那么这个Hook是个什么东西呢?
Hook是什么?
Hook在该框架里边表现的是一个类,该类实现的功能就是Hook某个系统类。如果不是特别了解hook技术,请先阅读Hook机制之动态代理
Hook掉系统的好多服务
在连接到PluginManagerService
之后,会在onServiceConnected
中收到通知,PluginHelper中注册了一份(PluginManager中也注册了一份)。因此在连接到插件服务后,PluginHelper中做了如下操作:
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
PluginProcessManager.setHookEnable(true, true);
}
从字面意思看,就是开启了Hook。
PluginProcessManager.java
public static void setHookEnable(boolean enable, boolean reinstallHook) {
HookFactory.getInstance().setHookEnable(enable, reinstallHook);
}
发现又调用了HookFactory中的方法。
public void setHookEnable(boolean enable, boolean reinstallHook) {
synchronized (mHookList) {
for (Hook hook : mHookList) {
hook.setEnable(enable, reinstallHook);
}
}
}
在Hook工厂类中持有一个Hook列表,然后循环遍历每个Hook,并开启自身的hook。
其实说白了,开启hook就是找到需要hook掉系统某个服务或者api中的方法(hook点),然后针对该方法进行进行动态代理,通过反射来修改反射前传入的参数或者结果,从而达到修改参数或者修改返回结果的功能
可以看到,基本上平时能接触到的系统服务都被动态代理了。之后通过宿主app进行系统api的一些调用,都会走入到相关动态代理的方法中。
下边以打开插件中的Activity为例,来揭开hook的面纱。
想要在宿主app中打开插件中的Activity首先主要需要解决两个问题:
- 绕过AMS限制。
- 加载插件中的类。
针对这两个问题的详细讲解可以参考:DroidPlugin总结
问题一的解决方法:
- 占坑,宿主清单文件中声明StubActivity。
public abstract class ActivityStub extends Activity {
private static class SingleInstanceStub extends ActivityStub {
}
private static class SingleTaskStub extends ActivityStub {
}
private static class SingleTopStub extends ActivityStub {
}
private static class StandardStub extends ActivityStub {
}
public static class P00{
public static class SingleInstance00 extends SingleInstanceStub {
}
public static class SingleTask00 extends SingleTaskStub {
}
public static class SingleTop00 extends SingleTopStub {
}
public static class SingleInstance01 extends SingleInstanceStub {
}
public static class SingleTask01 extends SingleTaskStub {
}
public static class SingleTop01 extends SingleTopStub {
}
public static class SingleInstance02 extends SingleInstanceStub {
}
public static class SingleTask02 extends SingleTaskStub {
}
public static class SingleTop02 extends SingleTopStub {
}
public static class SingleInstance03 extends SingleInstanceStub {
}
public static class SingleTask03 extends SingleTaskStub {
}
public static class SingleTop03 extends SingleTopStub {
}
public static class Standard00 extends StandardStub {
}
}
//p1..p2...p3...
}
首先定义了各种启动模式以及多个进程中的Activity,然后在清单文件中声明这些Activity。
- 之后,获取插件启动Activity的Intent,在startActivity后交给AMS检查之前,将Intent中插件的信息替换为宿主的信息,并将旧的Inent作为extra保存到新的Intent中
private static class startActivity extends ReplaceCallingPackageHookedMethodHandler {
public startActivity(Context hostContext) {
super(hostContext);
}
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
bRet = doReplaceIntentForStartActivityAPILow(args);
} else {
bRet = doReplaceIntentForStartActivityAPIHigh(args);
}
return super.beforeInvoke(receiver, method, args);
}
}
protected boolean doReplaceIntentForStartActivityAPILow(Object[] args) throws RemoteException {
int intentOfArgIndex = findFirstIntentIndexInArgs(args);
if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
Intent intent = (Intent) args[intentOfArgIndex];
ActivityInfo activityInfo = resolveActivity(intent);
if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
ComponentName component = selectProxyActivity(intent);
if (component != null) {
Intent newIntent = new Intent();
newIntent.setComponent(component);
newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
newIntent.setFlags(intent.getFlags());
if (TextUtils.equals(mHostContext.getPackageName(), activityInfo.packageName)) {
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
args[intentOfArgIndex] = newIntent;
} else {
Log.w(TAG, "startActivity,replace selectProxyActivity fail");
}
}
}
return true;
}
}
在setHookEnable(true); 之后,任何通过startActivity
打开插件Activity的调用都会通过设置动态代理走进该方法。
beforeInvoke
是真正反射调用系统方法之前,是在一个抽象代理类中声明的方法,用来暴露给各个子类进行具体实现。对应的还有afterInvoke
方法。
在该方法中,首先会通过ComponentName component = selectProxyActivity(intent);
来选取宿主中声明的一个同样启动模式的StubActivity,然后将该宿主Activity的Component替换插件的Component从而绕过AMS的检查,因为如果打开清单文件中没有注册的Activity会抛出异常。
通过本次hook
,就绕过了AMS的这项检查。
- 在AMS检查回来之后,新建Activity之前,再将其替换回来。此时通过反射创建的Activity就是插件中的Activity了,同时由于插件Activity持有与AMS通信的token,因此可以进行后续的生命周期回调及IPC。
在startActivity之后,根据系统源码,会通过ApplicationThread来通知app来创建该Activity。因此需要在ApplicationThread接收到系统之前来将其再替换回来,不然新建的就不是插件中的Activity,而是宿主声明的StubActivity了。
所以需要寻找合适的hook点,来将其进行替换。这个hook点就是ActivityThread中的H类。
具体的Hook点:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
通过反射给ActivityThread中的mH添加一个自定义的mCallback,之后从AMS向ActivityThread发送的任何消息都会先经过mCallback的handleMessage,然后再在mCallback.handleMessage中将替换过的Activity再替换回来。
public class PluginCallback implements Handler.Callback{
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
return handleLaunchActivity(msg);
}
if (mCallback != null) {
return mCallback.handleMessage(msg);
} else {
return false;
}
}
}
private boolean handleLaunchActivity(Message msg) {
Object obj = msg.obj;
Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
IPackageManagerHook.fixContextPackageManager(mHostContext);
ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
if (targetActivityInfo != null) {
if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
}
//解析插件信息,生成插件的ClassLoader
PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
setIntentClassLoader(targetIntent, pluginClassLoader);
setIntentClassLoader(stubIntent, pluginClassLoader);
boolean success = false;
try {
targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
if (stubActivityInfo != null) {
targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
}
success = true;
} catch (Exception e) {
Log.e(TAG, "putExtra 1 fail", e);
}
if (!success) {
Intent newTargetIntent = new Intent();
newTargetIntent.setComponent(targetIntent.getComponent());
newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
if (stubActivityInfo != null) {
newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
}
FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent);
} else {
FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
}
FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
} else {
}
} else {
}
if (mCallback != null) {
return mCallback.handleMessage(msg);
} else {
return false;
}
}
}
替换的代码如上。方法说明:
- 将startActivity时替换的宿主的Activity再替换回来。
- 解析插件并生成插件的ClassLoader,然后放入缓存。因为在替换回来之后马上就要通过反射创建插件的Activity了。上述代码:
PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
的作用就是解析插件apk生成ApplicationInfo,然后构建LoadedApk,之后再生成插件的ClassLoader。【因为构建ClassLoader需要LoadedApk中的dexFile,resource等信息】
此时,在经过AMS检查之后会将StubActivity替换为插件的Activity。然后就会走ActivityThread.handleLaunchActivity中通过反射创建插件的Activity。
但是,我们还没有办法通过宿主加载插件中的类。因为通过宿主的类加载器肯定会抛出ClassNotFound异常。这是第二个问题。
问题二解决方法
DroidPlugin的解决方法:
- 通过PackageParser解析插件构建插件的ApplicationInfo。
- 根据构建的ApplicationInfo构建插件的LoadedApk。
- 通过反射将插件的LoadedApk存入ActivityThread中的mPackages缓存中。
该问题的实现也是在上边的PluginCallback中的handleLaunchActivity
方法中。
只有一句代码:PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
核心逻辑:
Object object = ActivityThreadCompat.currentActivityThread();
if (object != null) {
Object mPackagesObj = FieldUtils.readField(object, "mPackages");
Object containsKeyObj = MethodUtils.invokeMethod(mPackagesObj, "containsKey", pluginInfo.packageName);
if (containsKeyObj instanceof Boolean && !(Boolean) containsKeyObj) {
final Object loadedApk;
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());
} else {
loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo);
}
sPluginLoadedApkCache.put(pluginInfo.packageName, loadedApk);
/*添加ClassLoader LoadedApk.mClassLoader*/
String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, pluginInfo.packageName);
String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, pluginInfo.packageName);
String apk = pluginInfo.applicationInfo.publicSourceDir;
if (TextUtils.isEmpty(apk)) {
pluginInfo.applicationInfo.publicSourceDir = PluginDirHelper.getPluginApkFile(hostContext, pluginInfo.packageName);
apk = pluginInfo.applicationInfo.publicSourceDir;
}
if (apk != null) {
ClassLoader classloader = null;
try {
classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent());
} catch (Exception e) {
}
if(classloader==null){
PluginDirHelper.cleanOptimizedDirectory(optimizedDirectory);
classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent());
}
synchronized (loadedApk) {
FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);
}
sPluginClassLoaderCache.put(pluginInfo.packageName, classloader);
Thread.currentThread().setContextClassLoader(classloader);
found = true;
}
ProcessCompat.setArgV0(pluginInfo.processName);
}
}
方法说明:
- 获取到插件的LoadedApk对象。
- 构建插件的ClassLoader并反射赋值给LoadedApk
- 将缓存存入Map
缓存集合中。
此时,每个插件都有自己的classLoader,就可以加载自己的类。而且通过一系列hook也完成了插件Activity的生命周期的回调。此时,插件中的Activity已经可以被正常的使用了。