正常情况下启动一个Activity,首先需要在AndroidManifest文件中声明,其次需要把该应用安装到手机系统中。
而插件apk是没有正在安装到手机系统中的,也就按照正常的启动流程插件Activity是不能启动的。另外插件apk的类需要加载进来是需要指定ClassLoader。前面的文章也大概讲过,当启动一个插件Activity时,先是用预定义的代理Activity替换目标Activity(及插件Activity)去启动,当AMS处理完回调到应用空间时(及回到运行Activity的进程空间时)再用目标Activity替换回来,去调用目标Activity的声明周期。再这个过程中,要实例化目标Activity用到了PluginClassLoader,自定义的ClassLoader来加载实例化目标Activity.
总结:也就是说要启动插件Activity DroidPlugin至少需要解决以下三个问题:
1 在启动Activity进入到AMS前需要用预定义的代理Activity替换目标Activity(及插件Activity)。
2 请完成后进入到运行Activity的进程中时,需要将目标activity替换回来。
3 需要用自定义的PluginClassLoader加载目标Activity。
通过分析Android Framework源码可以了解到(这里没有分析全部的Activity启动流程,只是针对上面三个问题的源码处理流程,这样能帮助理解DroidPlugin的篡改处理流程)
1 启动一个Activity最终都会通过调用ActivityManagerProxy 也就是AMS服务的代理来请求AMS启动。
2 AMS做完相关Activity的启动工作以后
A 会通过ActivityThread的内部类ApplicationThread这个binder对象回调到运行Activity的进程空间。
B 然后通过调用ActivityThread成员变量mH这个继承自Handler类的对象往主线程发送一条LAUNCH_ACTIVITY的消息。
C 在Handler分发消息的流程函数dispatchMessage中可以了解到,Handler有一个成员变量mCallback,如果不为空则调用mCallback.handleMessage(msg),当callback的handleMessage函数返回True时直接返回了, 就不会执行handleMessage(msg);函数。Activity正常启动的情况下会调用mH.handleMessage函数。执行LAUNCH_ACTIVITY消息对应的处理函数handleLaunchActivity函数启动Activity(也就是调用Activity的生命周期)
3 实例化目标Activity过程。
A 在执行LAUNCH_ACTIVITY消息时,会调用ActivityThread的getPackageInfoNoCheck函数获取一个LoadedApk对象。获取过程中会先通过包名从ActivityThread的成员变量mPackages中去查找是否有包名对应的LoadedApk存在,如果不存在则创建一个LoadedApk对象并以当前包名为key保存到mPackages中。同时将创建的LoadedApk保存到ActivityClientRecord的成员变量packageInfo中。在LoadedApk的构造函数中还会初始化一个mClassLoader的成员变量,默认情况下这个mClassLoader是一个PathClassLoader的对象。
B 创建完LoadedApk对象之后就会调用handleLaunchActivity函数,在这个函数里面会调用performLaunchActivity函数中会获取ActivityClientRecord的成员变量packageInfo内部的mClassLoader来加实例化目标Activity对象。
DroidPlugin就是在上面三段代码处理流程中找到Hook点来解决上面三个问题实现插件Activity的启动的。
1 启动前用代理Activity替换目标Activity:
在DroidPlugin中这个ActivityManagerProxy也已经被Hook,(如何Hook 可以看IActivityManagerHook的Install函数,了解AMS的Hook过程)那么只需要在IActivityManagerHook对应的处理分发类IActivityManagerHookHandle中对ActivityManagerProxy的StartActivity函数进行处理Activity的替换就行了。
2 启动后用目标Activity替换回代理Activity。
在DroidPlugin中通过PluginCallbackHook Hook了ActivityThread中的成员变量mH这个Handler的成员变量mCallback,特别对Id为LAUNCH_ACTIVITY的消息做了将目标Activity替换回代理Activity的预处理。(如何Hook 可以看PluginCallbackHook 的Install函数)
3 篡改实例化目标Activity(插件Activity)的过程。
前面分析了正常Activity对象是通过LoadedApk的成员变量mClassLoader来是实例化的,而在前面的文章也说过插件Activity要想加载,必须通过自定义的继承自DexClassLoader的PluginClassLoader来实例化,因此DroidPlugin用插件Apk的包名为key,伪造了一个LoadedApk对象为Value,保存到了ActivityThread的成员变量mPackages中同时用PluginClassLoader的对象替换了原本保存在LoadedApk成员变量mClassLoader中的ClassLoader对象。
接下来就通过源码分析DroidPlugin对上面三个流程源码的处理:
一 IActivityManagerHook对目标Activity的替换流程。
通过Hook的过程分析可以知道,IActivityManagerHook是通过IActivityManagerHookHandle对AMS各个函数分发处理类,而startActivity是通过startActivity类的函数beforeInvoke来处理的。
startActivity的beforeInvoke函数如下:
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
RunningActivities.beforeStartActivity();
boolean bRet = true;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
bRet = doReplaceIntentForStartActivityAPILow(args);
} else {
bRet = doReplaceIntentForStartActivityAPIHigh(args);
}
if (!bRet) {
setFakedResult(Activity.RESULT_CANCELED);
return true;
}
return super.beforeInvoke(receiver, method, args);
}
}
A 此函数中首先调用的是RunningActivities.beforeStartActivity();
分析此函数前线了解RunningActivities类的一个内部类和几个成员变量的作用。
内部类RunningActivityRecord及其四个成员变量: 记录运行时Activity的一个数据类,
activity:Activity类,保存目标Activity对象。
targetActivityInfo: ActivityInfo类,保存目标ActivityInfo对象。
stubActivityInfo: ActivityInfo类,保存预定义代理ActivityInfo对象。
index: 记录activity启动时的顺序。
mRunningActivityList : 以activity对象为key, RunningActivityRecord对象为Value。保存所有当前进程所有运行的Activity。
mRunningSingleStandardActivityList,mRunningSingleTopActivityList,mRunningSingleTaskActivityList, mRunningSingleInstanceActivityList,
以activity的启动顺序为key,以RunningActivityRecord为Value分别保存四种不同启动模式的Activity。
RunningActivities.beforeStartActivity()函数如下:
public static void beforeStartActivity() {
synchronized (mRunningActivityList) {
for (RunningActivityRecord record : mRunningActivityList.values()) {
if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
continue;
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
doFinshIt(mRunningSingleTopActivityList);
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
doFinshIt(mRunningSingleTopActivityList);
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
doFinshIt(mRunningSingleTopActivityList);
}
}
}
}
这个函数主要是遍历mRunningActivityList,然后根据RunningActivityRecord的类型,除Standard启动模式外,其他三种启动模式分别调用doFinshIt并以分别保存正在运行的其他三种启动模式的map对象为参数。
继续看doFinshIt函数:
private static void doFinshIt(Map<Integer, RunningActivityRecord> runningActivityList) {
if (runningActivityList != null && runningActivityList.size() >= PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) {
List<RunningActivityRecord> activitys = new ArrayList<>(runningActivityList.size());
activitys.addAll(runningActivityList.values());
Collections.sort(activitys, sRunningActivityRecordComparator);
RunningActivityRecord record = activitys.get(0);
if (record.activity != null && !record.activity.isFinishing()) {
record.activity.finish();
}
}
}
这个函数主要就是,获取保存对应启动模式Activity的map对象不为空,且的size大于等于3的情况下,调用activity的finish函数结束最早启动的activity。
也就是说doFinishIt函数是当预定义的Activity不够用时,在启动新的Activity前把最早启动的activity Finish掉。
看完这段代码我有两个疑问:
疑问1: 为什么要在beforeStartActivity函数中遍历mRunningActivityList而不是直接调用doFinishIt函数三次?遍历可能会多次调用同一种启动模式的doFinishIt函数啊。多次调用和调用一次,结果是一样的啊?
疑问2: 为什么doFinishIt函数中最大值是三呢?而不是4?除Standard启动模式之外的三种启动模式预定义了四个啊?还有DialogTheme的Activity也预定义了4个?如果存在如下情况,启动了一个Activity,三个DialogThemeActivity,现在又要启动一个Activity,那不是第一个Activity错杀了?(可能哪里没有理解透彻,希望大虾解答!)
B 回到beforeInvoke继续分析,下面的代码中正对不同阶段的版本调用了不同的函数,最后这两个函数在正常处理完成的逻辑下会返回True,也就是返回super.beforeInvoke,而这个函数默认是返回false的,当beforeInvoke返回false时,就会直接调用被代理的函数去执行。也就是说替换一定发生在这两个函数中,这里就以高版本的处理函数来分析(基本包含低版本的处理逻辑)。
doReplaceIntentForStartActivityAPIHigh函数如下:
protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException {
int intentOfArgIndex = findFirstIntentIndexInArgs(args);
if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
Intent intent = (Intent) args[intentOfArgIndex];
if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) {
PluginPatchManager.getInstance().startPluginActivity(intent);
return false;
}
ActivityInfo activityInfo = resolveActivity(intent);
if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
ComponentName component = selectProxyActivity(intent);
if (component != null) {
Intent newIntent = new Intent();
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
setIntentClassLoader(newIntent, pluginClassLoader);
newIntent.setComponent(component);
newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
newIntent.setFlags(intent.getFlags());
String callingPackage = (String) args[1];
if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
args[intentOfArgIndex] = newIntent;
args[1] = mHostContext.getPackageName();
}
}
}
return true;
}
1 从参数中找到Intent参数的位置,取出Intent,然后判断是否能启动插件Activity,如果不能则调用PluginPatchManager的startPluginActivity延迟启动。
能否启动取决于一下两个条件:
条件1:PluginManagerService是否已经启动。
条件2:启动Activity的包名和宿主进程的包名不一样。
2 调用resolveActivity函数从插件服务中找到启动插件插件ActivityInfo。resolveActivity函数不做过多分,看过插件安装的文章中讲过保存的过程,这里只是获取。
3 判断获取到的activityInfo不为空,且是插件中的Activity,然后调用selectProxyActivity(此函数在进程管理文章中已近有分析)找到合适的代理Activity。
4 创建一个新的Intent,设置启动Activity为前面找到的代理Activity,并把目标activity保存到新的Intent中.最后判断调用者的包名是不是宿主进程包名,然后把新的Intent替换掉之前找到的Intent参数的位置。
至此替换过程已经完成。
二:AMS处理完成回到运行Activity进程空间,完成目标Activity替换回来,以及目标Activity实例化过程。
A 前面已经分析过了具体的处理类,直接看PluginCallback对LAUNCH_ACTIVITY的处理函数:handleLaunchActivity:考虑到函数比较长分段阅读:
1 找出目标Activity
private boolean handleLaunchActivity(Message msg) {
try {
Object obj = msg.obj;
Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
// 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候,也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。
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());
}
ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
if (stubActivityInfo != null) {
PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
}
1.1 是从msg.obj中获取代理Intent参数,然后从代理Intent中获取启动之前保存的目标intent。
1.2 判断目标Intent是否存在,而且不是一个ShortcutProxyActivity然后调用IPackageManagerHook.fixContextPackageManager(mHostContext);
要创建的Activity(及ContextImpl)有一个成员变量mPackageManager 是一个ApplicationPackageManager类对象,再ApplicationPackageManager类里面有一个成员变量,mPM,保存了PackageManager服务的代理对象。
这一步要做的事情就是保证这个代理对象是之前Hook过的对象。
1.3 接下来的代码就是设置目标intent具体的类,然后找到目标ActivityInfo和代理ActivityInfo。最后提交管理。
2 创建插件Activity加载环境:
PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
setIntentClassLoader(targetIntent, pluginClassLoader);
setIntentClassLoader(stubIntent, pluginClassLoader);
这里就是前面DroidPlugin中需要解决的第二个问题,预存LoadedApk,并替换LoadedApk中的ClassLoader。以便加载插件Activity。
先分析完这个函数在分析PluginProcessManager.preLoadApk函数的实现。
3 最后一段代码比较长,而且都是在做兼容处理,就不贴代码了。
具体做的事情就是把目标ActivityInfo和代理ActivityInfo保存到目标Intent中。在msg.obj中把目标Intent替换到原来的代理intent,再把目标ActivityInfo替换掉代理ActivityInfo。
4 PluginProcessManager.preLoadApk还是分段阅读:
public static void preLoadApk(Context hostContext, ComponentInfo pluginInfo) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, PackageManager.NameNotFoundException, ClassNotFoundException {
/*添加插件的LoadedApk对象到ActivityThread.mPackages*/
boolean found = false;
synchronized (sPluginLoadedApkCache) {
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);
这段代码主要做了如下工作:获取ActivityThread对象的成员变量mPackages,并判断mPackages是否包含当前插件包名为key的LoadedApk对象。如果不包含则调用ActivityThread的getPackageInfoNoCheck函数创建LoadedApk对象。以插件包名为key将创建的LoadedApk对象保存到sPluginLoadedApkCache中。
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, ClassLoader.getSystemClassLoader());
} catch (Exception e) {
}
if(classloader==null){
PluginDirHelper.cleanOptimizedDirectory(optimizedDirectory);
classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
}
synchronized (loadedApk) {
FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);
}
sPluginClassLoaderCache.put(pluginInfo.packageName, classloader);
Thread.currentThread().setContextClassLoader(classloader);
found = true;
}
ProcessCompat.setArgV0(pluginInfo.processName);
}
}
}
这段代码的主要工作是:
1 获取optimizedDirectory,libraryPath,apk三个插件路径(看过插件安装应该非常熟悉了),创建PluginClassLoader对象classloader。
2 替换到LoadedApk中的成员变量mClassLoader.然后以插件包名为key保存到sPluginClassLoaderCache中。
3 设置当前线程ClassLoader为PluginClassLoader对象classloader。
4 设置当前进程名字为插件包名,这个不一定能实现。至少我的手机上还是显示的预定义的pluginXX.
if (found) {
PluginProcessManager.preMakeApplication(hostContext, pluginInfo);
}
}
最后这段代码主要是为之前创建的LoadedApk对象内部的成员mApplication。而preMakeApplication其实就是调用了LoadedApk的makeApplication函数。就不贴代码了。
三: 创建插件Activity创建之前的其他工作:
阅读DroidPlugin的源码可以发现,其实还通过InstrumentationHook Hook了Instrumentation类,查看InstrumentationHook的onInstall函数可以知道实际就是通过PluginInstrumentation 类对象替换了ActivityThread的成员变量mInstrumentation原来的值。
通过源码分析了解到,在performLaunchActivity函数中会调用mInstrumentation.callActivityOnCreate(activity, r.state);而在callActivityOnCreate函数中才会整整调用activity.performCreate来真正执行activity的onCreate函数。也就是说DroidPlugin Hook mInstrumentation变量就是想在执行activity的onCreate函数之前做了一些工作:
具体来看PluginInstrumentation的callActivityOnCreate函数:
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
if (enable) {
IWindowManagerBinderHook.fixWindowManagerHook(activity);
IPackageManagerHook.fixContextPackageManager(activity);
PluginProcessManager.fakeSystemService(mHostContext, activity);
onActivityCreated(activity);//放到最后分析
fixBaseContextImplOpsPackage(activity.getBaseContext());
fixBaseContextImplContentResolverOpsPackage(activity.getBaseContext());
}
//上面代码是实际调用activity onCreate函数前的处理。
if (mTarget != null) {
mTarget.callActivityOnCreate(activity, icicle);
} else {
super.callActivityOnCreate(activity, icicle);
}
}
1 IWindowManagerBinderHook.fixWindowManagerHook(activity);
在Activity对象内部有一个PhoneWindow类的成员变量 mWindow, 在PhoneWindow类中有一个内部类WindowManagerHolder,他里面有一个成员变量sWindowManager 保存了WindowManager服务的代理。
这一步要做的事情就是保证PhoneWindow 内部类WindowManager的静态成员变量SwindowManager这个WindowManager代理对象也是我们Hook过的WindowManager。
2 IPackageManagerHook.fixContextPackageManager(activity);这个前面已经分析过了。
3 PluginProcessManager.fakeSystemService(mHostContext, activity);
分析前现需要理解两个变量 SYSTEM_SERVICE_MAP 以及 mServiceCache 是做什么的?
ContextImpl 在类加载的时候会有静态代码块,初始化封装一些系统服务。
静态代码块中,会以要封装服务的名字为key ,以ServiceFetcher 对象为value 保存在SYSTEM_SERVICE_MAP中,其中在创建ServiceFetcher对象时实现了创建封装服务的方法。
当用户需要获取某一个系统服务时,通过ContextImpl getSystemService函数以及服务名字,这个函数就会从SYSTEM_SERVICE_MAP中获取ServiceFetcher对象,
然后调用ServiceFetcher对象的getService来获取。getService中,会先从ContextImp的成员变量mServiceCache,ServiceFetcher对象中有一个mContextCacheIndex保存了
一个位置,如果ServiceFetcher中的服务已近缓存了,那就必定就在mServiceCache的mContextCacheIndex位置,
如果不存在那么就直接调用ServiceFetcher的createService,创建服务对象并缓存到mServiceCache的mContextCacheIndex位置。
有了上面的理解,接下来fakeSystemService这个函数就好理解了:
这个函数调用了另外一个函数fakeSystemServiceInner
在fakeSystemServiceInner中主要做的事情就是:
事情一:保证新创建的Activity(及ContextImpl)中mServiceCache缓存的服务对象都是我们Hook过的。
事情二:为了兼容性跳过一些不能篡改的服务sSkipService。
事情三:在确保ContextImplement成员变量mServiceCache中缓存的服务是已经Hook过的以后,把这个ContextImplement 保存到mFakedContext。
这个函数的目的,因为系统服务可能缓存在多个地方,所以需要保证多个地方的服务都是Hook过的,从而保证插件正常运行。
4 fixBaseContextImplOpsPackage(activity.getBaseContext());
修改ContextImpl中的成员变量mOpPackageName 为宿主进程的包名。
5 fixBaseContextImplContentResolverOpsPackage
修改ContentResolver的成员变量,mPackageName
6 onActivityCreated
这个函数主要做的事情如下:
事情一:从目标Intent中获取目标ActivityInfo和代理ActivityInfo,调用RunningActivities.onActivtyCreate记录运行的Activity信息。
事情二:设置目标Activity屏幕请求方向。
事情三:调用PluginManager.getInstance().onActivityCreated添加到进程已经Activity管理中。
到这里DroidPlugin对插件Activity的处理全部分析完了。