我们知道,如果我们实现了一个 Activity,那么就必须在 your AndroidManifest.xml 中申明,否则会报 android.content.ActivityNotFoundException: Unable to find explicit activity class {XXXXXX}; have you declared this activity in your AndroidManifest.xml? 的错误。
下面介绍的的动态注册组件就是为了绕开这个机制。当然,如果你在宿主的 AndroidManifest.xml 里面把插件需要的 Activity 都申明一下,那么下面的工作就完全不用做了。
动态注册组件原理
动态的注册组件就是我们常说的Hook技术。
想要了解插件化的Hook技术我们需要先了解一下 Activity 的启动流程,Activity 的启动流程要涉及到App进程以及system_server进程,system_server进程的AMS负责 Activity 的真实性校验以及生命周期管理,App进程负责创建 Activity 对象以及回调生命周期的方法。
由于Activity 的检验过程是在AMS进程完成的,我们对system_server进程里面的操作无能为力,只有在我们APP进程里面执行的过程才是有可能被Hook掉的,因此,所有的Hook我们只能在App进程完成,那么在AMS进程里面进行校验的 Activity 也必须是真实存在的。
因此,Hook的基本思路就是当调用AMS时,就用我们真实注册的存在的 Activity 信息(对应上一篇文章的AndroidManifest.xml中的A、A1、A2….A33等 Activity),AMS回调到App进程时替换为插件中需要启动的 Activity 信息,从而达到欺骗系统的目的。
可以先看一下我的博客startActivity 流程,通过看启动流程图我们可以清晰的看到,涉及到 Acitivity 的AMS进程和App进程的边界操作有两个:startActivity和对LAUNCH_ACTIVITY消息的处理,这也就是我们需要Hook的两个重要点。
下面通过实际代码来进程介绍。
前面我们说过,替换 Instrumentation 对象和 ActivityThreadHandlerCallback 是插件化工作中的重头戏。这里用到了我们说的“Hook”技术。Instrumentation类看一下官方文档对这个类的解释,该类跟踪 Application 及 Activity 的整个生命周期,它的一些方法在 Application 及 Activity 所有生命周期函数的调用中,都会先调用这些方法。
熟悉 Activity 启动流程的同学都知道,启动 Activity 是由 Activity 的 startActivityForResult() 方法启动,通过 Instrumentation 的 execStartActivity 方法激活生命周期。
1 | public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { |
Activity 的实例化在 ActivityThread 的 performLaunchActivity() 方法中通过 Instrumentation 的 newActivity() 方法实例化。
1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
‘onCreate()’ 生命周期函数的调用也是在 ActivityThread.performLaunchActivity() 中调用 Instrumentation 的 callActivityOnCreate() 方法来实现的。
1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
因此,得到了这个对象,我们就可以进入并跟踪 Application 和 Activity 的生命周期流程。
Small 想要做到动态注册 Activity,首先在宿主 Manifest 中注册一个命名特殊的占坑 Activity 来欺骗 startActivityForResult 以获得生命周期,再欺骗 performLaunchActivity 来获得插件 Activity 实例,又为了处理之间的信息传递,因此有了后面的 ActivityThreadHandlerCallback。
接下来我们就在 ApkBundleLauncher.InstrumentationWrapper 来看一下这些是如何实现的。
先来看一下 execStartActivity 方法:execStartActivity方法有两个实现,一个是API Level 20以前的,一个是API Level 20以后的,仅仅是参数不同而已。
由于前面我们用 ApkBundleLauncher.InstrumentationWrapper 替换了 mInstrumentation,因此会调用到 ApkBundleLauncher.InstrumentationWrapper 中的 execStartActivity() 方法。该方法做的工作后面再详细介绍。主要是把需要启动的真实 Activity 替换为占坑 Activity。
熟悉 Activity 流程的同学都知道,真正启动 Activity 时,ActivityManagerService 调用 ApplicationThread.scheduleLaunchActivity 接口,通知相应的进程执行启动 Activity 的操作,ApplicationThread 把这个启动 Activity 的操作转发给 ActivityThread,ActivityThread 通过 ClassLoader 导入相应的 Activity 类,然后把它启动。
具体的在 ActivityThread.ApplicationThread.scheduleLaunchActivity 方法中会调用 sendMessage(H.LAUNCH_ACTIVITY, r),该消息由 ActivityThread 中的消息处理对象 mH 来处理,由于我们把 mH 的 mCallback 替换为了ActivityThreadHandlerCallback,因此也会对 LAUNCH_ACTIVITY 消息进行拦截处理,处理完后再由mH 来处理正常的流程。
这里解释一下为什么要调用 ensureInjectMessageHandler 来反射替换 ActivityThread 的 Handler 对象 mH 的 mCallback 呢?
先看一下 Handler 源码中的 dispatchMessage() 方法。
1 | /** |
这里会先执行 mCallback 的 handleMessage(),在返回false的情况下再执行自身的 handleMessage()。
这样就可以在 ActivityThreadHandlerCallback 中处理一些事情,然后在调用 mH 的方法。
启动插件Activity流程
这里以 ApkBundleLauncher 来作为 Bundle 的 BundleLauncher 为例进行说明。
1 | ├── Small.openUri() |
根据URI匹配Bundle
匹配插件中注册的 Activity
1 | protected static Bundle getLaunchableBundle(Uri uri) { |
BundleLauncher.launchBundle() 启动 Activity1
2
3
4
5
6
7
8
9
10
11
12
13
14public void launchBundle(Bundle bundle, Context context) {
if(bundle.isLaunchable()) {
if(context instanceof Activity) {
Activity activity = (Activity)context;
if(this.shouldFinishPreviousActivity(activity)) {
activity.finish();
}
activity.startActivityForResult(bundle.getIntent(), 10000);
} else {
context.startActivity(bundle.getIntent());
}
}
}
启动真实的Activity
1 | public void prelaunchBundle(Bundle bundle) { |
启动流程拦截,启动占坑位Activity
1 | protected static class InstrumentationWrapper extends Instrumentation |
消息拦截
由于前面介绍的反射替换 ActivityThread 的 mH 对象的 mCallback,这里拦截了四种消息:
1 | private static class ActivityThreadHandlerCallback implements Handler.Callback { |
LAUNCH_ACTIVITY
对 LAUNCH_ACTIVITY 的处理主要是将启动的占坑位的 Activity 重新替换为真实的 Activity
1 |
|
拦截OnCreate方法
对 Activity 的 OnCreate 调用的拦截由 InstrumentationWrapper.callActivityOnCreate() 来进行处理。
1 |
|
另外还有对 OnStop,OnDestroy等其他生命周期方法的拦截,这里就不一一介绍了。