前面分析插件加载的过程中其实插件已经启动了,在callApp 方法中启动了插件的application,只是组件还没有加载,这里我们探索一下activity 组件的加载过程,startActivity。
先介绍一下Replugin 的 实现插件化加载插件组件的原理,我们知道Android 的组件需要在AndroidManifest.xml中进行注册,只有注册的组件才能够被启用,而插件中的activity 是注册在插件apk的Androidmanifest.xml中,如果我们使用classloader强行加载插件中的activity 那么这个activity 没用被宿主跟系统初始化过,那么这个activity 是没有生命周期等功能的。因此Replugin为了解决这个问题,使用了占坑的方式,占坑就是预先在宿主的AndroidManifest.xml中注册组件(比如:Activity的每种LaunchMode都各定义一个Activity节点,每种LaunchMode+声明为独立的进程名各定义一个Activity节点等等)然后在加载插件activity的时候先解析这个注册的activity 的信息(LaunchMode,Process,Theme,TaskAffinity等)然后根据这些信息分配一个相对应的这个信息的在宿主中注册activity坑位(例com.xx.xx.loader.a.ActivityN1NRNTS5)然后启动这个activity与AMS 交互欺骗系统,然后从系统回到ActivityThread 的时候再通过hook的 classloader 去根据坑位和目标activity的对应关系去load 目标activity,来实现插件activity加载。我们看看具体的过程。
startActivity
//com.qihoo360.loader2.PluginLibraryInternalProxy
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
// 是否启动下载
// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
// 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
if (download) {
if (PluginTable.getPluginInfo(plugin) == null) {
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
// 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
// NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
if (isNeedToDownload(context, plugin)) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
if (Factory2.isDynamicClass(plugin, activity)) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
context.startActivity(intent);
return true;
}
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
// Added by Jiongxuan Zhang
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
}
}
// WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
// 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
// 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, activity));
}
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
return false;
}
// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
1.在调用 public static boolean startActivity(Context context, Intent intent) 或者public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) 这两个方法之后,最终会调用PluginLibraryInternalProxy.startActivity方法。在方法中先检验插件不可用(不存在或者版本不对)就弹出下载提示框,提示用户去下载,然后检测是否是动态注册类,如果是动态注册的activity不使用坑位机制,直接加载这个类。然后判断插件状态,不可用的话提示用户升级。然后加载大插件的话采用异步加载同时显示加载提示框。接下去就是打开插件activity 的关键,将插件activity 替换成坑位activity来实现加载
//com.qihoo360.loader2.PluginCommImpl
/**
* 加载插件Activity,在startActivity之前调用
* @param intent
* @param plugin 插件名
* @param target 目标Service名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {
ActivityInfo ai = null;
String container = null;
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);
try {
// 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回)
ai = getActivityInfo(plugin, activity, intent);
if (ai == null) {
return null;
}
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
if (LOG) {
// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}
// 远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
} catch (Throwable e) {
}
// 分配失败
if (TextUtils.isEmpty(container)) {
return null;
}
PmBase.cleanIntentPluginParams(intent);
// TODO 是否重复
// 附上额外数据,进行校验
// intent.putExtra(PluginManager.EXTRA_PLUGIN, plugin);
// intent.putExtra(PluginManager.EXTRA_ACTIVITY, activity);
// intent.putExtra(PluginManager.EXTRA_PROCESS, process);
// intent.putExtra(PluginManager.EXTRA_CONTAINER, container);
PluginIntent ii = new PluginIntent(intent);
ii.setPlugin(plugin);
ii.setActivity(ai.name);
ii.setProcess(IPluginManager.PROCESS_AUTO);
ii.setContainer(container);
ii.setCounter(0);
return new ComponentName(IPC.getPackageName(), container);
}
2.通过plugin activity intent 获取activityInfo , 查找过程先通过参数plugin 利用load方法找到对应的插件,在插件的加载中我们知道,插件加载过程中会生成ComponentList 类里面维护这4大组件的map表,直接通过activity 名字在map中找到对应的activityInfo。然后通过activityInfo获取主题存储到intent中,然后根据activity 的 processName,选择进程 ID 标识。之后是容器选择启动目标进程,PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);这里PluginProcessMain.getPluginHost()很熟悉就是PmHostSvc这个类已经用了很多次了。常驻进程的管理类,然后又调用了PmBase.startPluginProcessLocked方法。通过常驻进程拉起对应的插件进程。
//com.qihoo360.loader2.PmBase
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
// 强制使用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
if (info.request == PluginBinderInfo.BINDER_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
}
//
StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);
// 获取
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client != null) {
return client;
}
// 分配
int index = IPluginManager.PROCESS_AUTO;
try {
index = PluginProcessMain.allocProcess(plugin, process);
} catch (Throwable e) {
}
// 分配的坑位不属于UI、自定义进程或Stub坑位进程,就返回。(没找到有效进程)
if (!(index == IPluginManager.PROCESS_UI
|| PluginProcessHost.isCustomPluginProcess(index)
|| PluginManager.isPluginProcess(index))) {
return null;
}
// 启动
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
if (!rc) {
return null;
}
// 再次获取
client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client == null) {
return null;
}
return client;
}
3. 默认使用UI进程(也可以自定义进程) 然后通过缓存去获取IPluginClient 这个也是Binder对象,具体的实现类是PluginProcessPer 插件进程管理类,如果缓存命中则直接返回client,没有的话 通过 PluginProcessMain.allocProcess 方法分配进程,分配好进程后,通过PluginProviderStub.proxyStartPluginProcess 方法启动进程
//com.qihoo360.loader2.PluginProviderStub
static final boolean proxyStartPluginProcess(Context context, int index) {
//
ContentValues values = new ContentValues();
values.put(KEY_METHOD, METHOD_START_PROCESS);
values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie);
Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values);
if (uri == null) {
return false;
}
return true;
}
4.这里通过ContentProvider 的insert方法拉起进程。ProcessPitProviderBase.buildUri(index)获取ContentProvider的Uri uri = Uri.parse("content://" + AUTHORITY_PREFIX + str + "/main"); 拼接后的uri 就是通过gradle 动态在AndroidManifest.xml中注册的
<provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderP0'
android:authorities='com.xx.xxx.loader.p.mainN100'
android:process=':p0'
android:exported='false' />
通过拉起这个provider 拉起这个provider 所在的':p0'进程。然后回到2 调用PluginProcessMain.probePluginClient(plugin, process, info);再次获取IPluginClient 也就是PluginProcessPer对象。进程已经调用起来了,然后就是去启动activity组件, container = client.allocActivityContainer(plugin, process, ai.name, intent);先远程分配坑位。
//com.qihoo360.loader2.PluginProcessPer
public String allocActivityContainer(String plugin, int process, String target, Intent intent) throws RemoteException {
// 一旦有分配,则进入监控状态(一是避免不退出的情况,二也是最重要的是避免现在就退出的情况)
RePlugin.getConfig().getEventCallbacks().onPrepareAllocPitActivity(intent);
String loadPlugin = null;
// 如果UI进程启用,尝试使用传过来的插件,强制用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (IPC.isUIProcess()) {
loadPlugin = plugin;
process = IPluginManager.PROCESS_UI;
} else {
loadPlugin = plugin;
}
}
// 如果不成,则再次尝试使用默认插件
if (TextUtils.isEmpty(loadPlugin)) {
if (mDefaultPlugin == null) {
return null;
}
loadPlugin = mDefaultPlugin.mInfo.getName();
}
//
String container = bindActivity(loadPlugin, process, target, intent);
return container;
}
5.这里通过获取的PluginProcessPer加载activity,先判断是否启用UI 进程,如果启用就强制使用UI进程作为插件进程,然后如果传进来的插件信息是空的,就去默认插件中去寻找这个activity。通过PluginProcessPer.bindActivity 方法加载目标activity。
//com.qihoo360.loader2.PluginProcessPer
/**
* 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器
*
* @param plugin 插件名称
* @param process 进程
* @param activity Activity 名称
* @param intent 调用者传入的 Intent
* @return 坑位
*/
final String bindActivity(String plugin, int process, String activity, Intent intent) {
/* 获取插件对象 */
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
return null;
}
/* 获取 ActivityInfo */
ActivityInfo ai = p.mLoader.mComponents.getActivity(activity);
if (ai == null) {
return null;
}
if (ai.processName == null) {
ai.processName = ai.applicationInfo.processName;
}
if (ai.processName == null) {
ai.processName = ai.packageName;
}
/* 获取 Container */
String container;
// 自定义进程
if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
String processTail = PluginProcessHost.processTail(ai.processName);
container = mACM.alloc2(ai, plugin, activity, process, intent, processTail);
} else {
container = mACM.alloc(ai, plugin, activity, process, intent);
}
if (TextUtils.isEmpty(container)) {
return null;
}
/* 检查 activity 是否存在 */
Class<?> c = null;
try {
c = p.mLoader.mClassLoader.loadClass(activity);
} catch (Throwable e) {
}
if (c == null) {
return null;
}
return container;
}
6.这里仍旧是先获取plugin 然后通过ComponentList中缓存的 map 获取activity对应的activityInfo 然后根据是否设置了自定义进程分别通过alloce2 和alloc 方法去获取坑位。而这辆个方法最终调用的是 allocLocked 方法。
//com.qihoo360.loader2.PluginContainers
private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
String plugin, String activity, Intent intent) {
// 坑和状态的 map 为空
if (map == null) {
return null;
}
// 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
return state;
}
}
// 新分配:找空白的,第一个
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
state.occupy(plugin, activity);
return state;
}
}
ActivityState found;
// 重用:则找最老的那个
found = null;
for (ActivityState state : map.values()) {
if (!state.hasRef()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
}
if (found != null) {
found.occupy(plugin, activity);
return found;
}
// 强挤:最后一招,挤掉:最老的那个
found = null;
for (ActivityState state : map.values()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
if (found != null) {
found.finishRefs();
found.occupy(plugin, activity);
return found;
}
// never reach here
return null;
}
7. 先获取进程中所有的符合条件的坑位集合 然后首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射,没有的话 分配一个新的空白的坑位 设置对应关系并返回。还是没有找到就重用最老的那个坑位。最后是强行挤掉最老的一个坑位。坑位分配好后,回到第6步 代码 检查一下这个插件中这个activity是否存在。存在的话 就返回这个坑位。回到 第1 步 将Intent指向到“坑位”。利用坑位去startActivity和系统AMS交互获取生命周期等。而activity的生命周期是通过ActivityThread 中的Handler 进行消息的分发 Handler里处理了各种类型的消息,通过函数handleLaunchActivity处理LAUNCH_ACTIVITY消息,然后在handleLaunchActivity 方法中调用performLaunchActivity启动Activity。在这个方法中我们获取了packageInfo中的classloader 去加载activity。而我们在hook 一章中知道Replugin通过Hook宿主Application#LoadedApk中的ClassLoader对象(通常一个应用对应一个LoadedApk对象,而Application中的LoadedApk就是在应用启动时创建的,在ActivityThread#handleBindApplication完成的,那么应用组件的Class的加载都是基于这个LoadedApk来完成的),然后将Replugin中自定义的PathClassLoader注入到LoadedApk中,再把LoadedApk 赋值给packageInfo。因此 原本调用系统的classloader的loadClass方法就变成了框架创建的RePluginClassLoader 的 loadClass方法
//com.qihoo360.loader2.PluginProcessPer
/**
* 类加载器根据容器解析到目标的activity
* @param container
* @return
*/
final Class<?> resolveActivityClass(String container) {
String plugin = null;
String activity = null;
// 先找登记的,如果找不到,则用forward activity
PluginContainers.ActivityState state = mACM.lookupByContainer(container);
if (state == null) {
return ForwardActivity.class;
}
plugin = state.plugin;
activity = state.activity;
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
return null;
}
ClassLoader cl = p.getClassLoader();
Class<?> c = null;
try {
c = cl.loadClass(activity);
} catch (Throwable e) {
}
return c;
}
8. 最终调用的是PluginProcessPer.resolveActivityClass方法,这个方法中我们根据之前坑位对应目标activity 的关系找到目标 插件和 目标activity,然后 获取这个插件,然后获取插件的classloader 去加载插件的activity 。 这样整个加载插件的activity组件的流程就实现了。