四大组件之Activity:
名词解释
AMS:AMS Binder对象
AMN:app中获取AMS代理的对象
ATP:ApplicationThreadProxy用于AMS和APP通信,AT的代理对象
APT:ApplicationThread APP中的Binder对象。APT中会利用mh这个Handler发送消息做对应处理
AT:APT会调用AT得方法,AT中给mh发消息
mh:App的Handler,用于接受处理AMS等系统服务发送的消息处理
原始流程
startActivity交互过程如下:
App:
ContextTheme—>ContextWrapper—>Context—>ContextImpl:
ContextImpl内部调用Instrumention.startActivity()传入启动Activity的类名(用于校验是否注册过没有注册抛异常)等信息,该方法其中通过AMN获取AMS代理对象和AMS进行通信。
AMS远端:
获取要启动的activity类名,观察是否在清单文件中注册过(PMS在安装时会解析对应APK清单文件存储到本地)。接着给启动activity的进程发送一个pause的消息通过ATP
然后判断该进程是否启动过(没有启动需要先和zygote用socket通信先创建一个进程Process.start)
1.没有启动过(没有启动需要先和zygote用socket通信先创建一个进程Process.start,接着创建ActivityThread,利用AMN把ATP传给AMS(这样AMS就可以和APP通信了)
接着AMS在给ATP发消息 AT收到给mh发送消进行反射创建Application调用onCreate,在接着就是下面2中所说的进程已经启动情况的步骤了)
2.启动过(AMS会封装Activity变成一个ActivityClientRecord,该对象会携带cl后面mh会根据这个cl去反射创建Activity。)
APP中:
ATP中收到AMS发过来的消息取出ActivityClientRecord,调用AT吧这个参数传进去。
AT中调用mh的handlerPerformLaunchActivity。mh其实是一个handler,handleCallBack中来决定调用哪个方法对应于本示例startActivity为下面的逻辑
回调中取出acRecord,利用acRecord的classloader对象来反射创建对应activity,并调用onCreate方法
通知最开始pause的那个activity恢复运行~(利用ATP也是)
至此交互结束
Hook点
我们可以Hook哪些点呢?提炼出精华我们在哪里可以把狸猫换成太子~
第一个点:
Instrumention利用AMN获取到AMS代理,将信息传给AMS校验
(我们在Instrumention中拦截这个代理,替换为我们自定义的对象。检测到startActivity时将Activity换成我们宿主中已经注册了的StubActivity)
好了,现在AMS校验过了,他要通知APP创建Activity对象了。但是这个时候是StubService的Activity。我们需要把他换掉我们真实启动的Activity。
第二个点:
我们怎么知道真实启动的Activity是谁呢?在第一步中其实整个流程传递的都是Intent,包括我们接受到ANS消息时也是Intent。
既然是同一个Intent对象。我们在这个Intent中动手脚(APP调用AMS把真实启动的Activity保存到Intent中,在AMS通知APP创建的时候在取出来)
好,第一个是在Instrumention中换成我们的代理接下来就会和AMS交互(这里是最后一步下一步就要和AMS通信因此选在这里进行Hook)
那么通知ATP,AT,mh中都可以进行还原取出Intent中真实启动的activity。
我们在mh中最后一步反射创建的地方进行修改(和上面一样,在最接近现场的地方进行Hook)。将mh替换为我们的mh。这样我们收到handlerPerformLaunchActivity的消息时,就可以做我们自己的操作了
不同的ClassLoader会加载不同的类,classloader中如果没有这个class是加载不了的。所以我们要用正确的cl去加载对象。这个cl其实就是插件的cl,也就是dexcl。我们在和AMS交互的时候可以传入对应的cl,之后创建再取出正确的cl。
注意点:
由于Activity默认是Standard模式,所以宿主中的一个Activity可以对应插件中的多个标准模式的Activity。每次启动都会创建一个实例。
但是Android中是有LaunchMode的,不同的启动模式对应的效果也不同。那么如何支持LaunchMode呢
LaunchMode的支持:
其实就是Activity栈的变化。我们自己手动保存一份当前启动的Activity信息集合,根据启动Activity的launchmode去操作这个集合并对集合中的Activity实例做处理【这个也是Android原生的处理方式】
(要保存两个集合一个是已经启动的集合commentName为key,真实的Activity实例为value(这个模拟的就是Activity栈);一个是当前APK中所有的Activity集合(是否可以通过类名找到对应的Activity信息)name对应ActivityComment)
注:第二个集合需要在Application的attachBaseContext中去完善这个集合。越早越好
流程:
1.先遍历第二个集合中看插件中是否有对应Activity的信息。很好理解如果你启动一个压根就不存在的Activity之后反射也会崩溃~~
这之后的代码逻辑就是mh中Hook那个handlerPerformLaunchActivity的消息时的处理,针对创建Activity实例和调用Activity方法进行额外的逻辑处理
2.首先看下已经启动的集合中Activity的数量,launchmode是针对多Activity才有用,如果栈中只有一个Activity那么launchmode将没用
3.再接着取出这个activity的launchmode是不是非Standard的,默认模式不需要处理,直接把他加到我们的第一个已经启动activity的集合中即可。原生也是这样
3.1
SingleTop:
首先看下原生的这个启动模式是什么样的情况:
如果栈顶是这个Activity那么将不会创建实例也不会调用onCreate,而是会调用他的onNewIntent方法。否则就是按Standard处理
我们怎么处理呢~~
注意:本质上我们还是standard的:
原先:S1–>T1 之后就是S1–>T1(有状态的)—>T1了,
Question:我们可以把之前Top的T1干掉吗也就是中间的那个T1,就是S1—>T1了~
A:不行,这样启动的T1状态会丢失。真实的情况应该仅仅是调用T1(有状态的)的onNewInstance
怎么做呢:不添加这个后来的activity,直接取出栈顶的activity实例调用OnNewInstance即可~~
对应逻辑:
先看下栈顶是不是这个activity。
~如果不是就直接加入到集合中就行,也就是默认Standard模式
~如果是的话,则忽略这个要启动的Activity,把栈顶的Activity取出来调用onNewInstance即可
3.2
SingleTask:
首先看下原生的这个启动模式是什么样的情况:
如果在栈中已经有该Activity实例了,那么清除掉在它上面的所有Activity,并调用这个Activity的onNewInstance方法
根据前面那个mode的处理,应该也能猜出怎么做了
在集合中找出这个Activity实例和他对应的下标。进行对这个范围(下标–集合最大数量)的activity实例进行挨个销毁。
接着在调用这个activity的onNewInstance方法
3.3:SingleInstance:
一样的操作。