在不同版本下通过Hook来启动插件activity的流程
不论哪个版本在启动插件Activity之前都需要通过DexPathLoader来进行相应的加载,这个部分不在此次内容中。
版本区分的话这里是根据Activity的启动流程差异来进行相应的替换,基本思路一致,版本划分的话主要是8.0以下,10.0以下,10.0及以上版本。
1.Hook替换Intent
private const val TARGET_INTENT = "target_intent"
fun hookAMS() {
try {
LogE(Build.VERSION.SDK_INT.toString())
//1。获取Singleton对象
val singletonFieldParams =
when {
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) -> {
//8.0以下
"android.app.ActivityManagerNative" to "gDefault"
}
(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)->{
//10.0以下
"android.app.ActivityManager" to "IActivityManagerSingleton"
}
else -> {
//10.0及以上
"android.app.ActivityTaskManager" to "IActivityTaskManagerSingleton"
}
}
val aClass = Class.forName(singletonFieldParams.first)
val singletonField = aClass.getDeclaredField(singletonFieldParams.second)
singletonField.isAccessible = true
val singleton = singletonField[null]
val singletonClazz = Class.forName("android.util.Singleton")
val mInstanceFiled = singletonClazz.getDeclaredField("mInstance")
mInstanceFiled.isAccessible = true
val mInstance = mInstanceFiled[singleton]
//2。获取Singleton中的IActivityTaskManager对象,区分版本10以下
val proxyClass =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Class.forName("android.app.IActivityManager")
} else {
Class.forName("android.app.IActivityTaskManager")
}
//3.创建动态代理
val newProxyInstance = Proxy.newProxyInstance(
Thread.currentThread().contextClassLoader,
arrayOf(proxyClass)
) { proxy, method, args -> //这里已经代理了IActivityTaskManagerSingleton
// 对象,那么它的方法执行都会到这里,那么我们只要要获取获取对应的方法,那么就可以获取方法中的参数
//参数当中是有Intent,那么这里就可以修改为宿主中Activity来执行
//只需要处理startActivities的方法
//这里代理的对象方法是在android8以后的版本
try {
var index = -1
if ("startActivity" == method.name) {
index = args.indexOfFirst { it is Intent }
val defIntent = args[index] as Intent
val proxyIntent = Intent()
proxyIntent.setClassName(
"com.arm.arount", "com" +
".arm.arount.ProxyActivity"
)
proxyIntent.putExtra(TARGET_INTENT, defIntent)
args[index] = proxyIntent
}
method.invoke(mInstance, *args.orEmpty())
} catch (e: Exception) {
e.printStackTrace()
}
}
//4.修改singleton的代理对象
mInstanceFiled[singleton] = newProxyInstance
} catch (e: Exception) {
e.printStackTrace()
}
}
到上面一步我们已经完成了hook startActivity的流程,并且将启动插件的Intent替换为了宿主代理的Intent,并在其中保存了原始的Intent。那么下一步我们就需要在合适的实际替换回原始的Intent。
2.替换回原始Intent
先上代码:
fun hookHandleIntent() {
try {
//获取ActivityThread
val aClass = Class.forName("android.app.ActivityThread")
//获取它的静态实例对象
val sCurrentActivityThread = aClass.getDeclaredField("sCurrentActivityThread")
sCurrentActivityThread.isAccessible = true
val activityThread = sCurrentActivityThread[null]
val mHField = aClass.getDeclaredField("mH")
mHField.isAccessible = true
val handler = mHField[activityThread] as Handler
val mCallbackField = Handler::class.java.getDeclaredField("mCallback")
mCallbackField.isAccessible = true
val callback = Handler.Callback { msg ->
try {
when (msg.what) {
//Android8.0以下的处理的LaunchActivity
100 -> {
val intentField = msg.obj.javaClass.getDeclaredField("intent")
intentField.isAccessible = true
val proxyIntent: Intent = intentField.get(msg.obj) as Intent
val intent = proxyIntent.getParcelableExtra<Intent?>(TARGET_INTENT)
intent?.let {
intentField.set(msg.obj, it)
}
}
//8.0以上的生命周期入口
159 -> {
val mActivityCallbacksField = msg.obj.javaClass.getDeclaredField(
"mActivityCallbacks"
)
mActivityCallbacksField.isAccessible = true
val mActivityCallbacks = mActivityCallbacksField[msg.obj] as List<*>
val launchActivityItem = mActivityCallbacks.firstOrNull {
"android.app.servertransaction.LaunchActivityItem" == it?.javaClass?.name
}
launchActivityItem?.let {
val mIntentField = it.javaClass.getDeclaredField("mIntent")
mIntentField.isAccessible = true
val proxyIntent = mIntentField[launchActivityItem] as Intent
val intent =
proxyIntent.getParcelableExtra<Intent>(TARGET_INTENT)
if (intent != null) {
mIntentField[launchActivityItem] = intent
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
//这里一定要返回false不然正常流程的handleMessage就执行不到了
false
}
mCallbackField[handler] = callback
} catch (e: Exception) {
e.printStackTrace()
}
}
属性activity启动流程的因为都发现我们是在ActivityThread的mh的Handler中拦截消息的,Handler中有一个CallBack对象,可以在handleMessage之前拿到msg
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//这里就是一定要返回false的原因
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在拿到msg之后我们需要处理以下8.0以下以及8.0以上的区分:
· 在Android8.0以下时,是在ActivityThread的Handler中处理
100-109这几个值来处理不同的生命周期的,并且在msg中可以直接获取到Intent对象。
· 而在Android8.0以后,这几个值没有了,统一为159的入口,并通过状态模式来处理,并且相应的参数有封装,我们需要获取一些包装参数之后才能获取到Intent。
到这里插件的Activity就可以启动了,但是你会发现资源布局都没法加载,下面再说插件资源的加载。
Hook的方式来进行插件化开发,很受系统版本的限制,并且随着Android系统版本的升级要做不同的适配。所以还有另外一种插件方案就是通过容器来实现插件化,这种方式不需要hook太多的系统代码,腾讯的Shadow插件就是这个来实现的,宿主的app中有的是容器的activity,编写的业务插件按原来的逻辑继承Activity,但是在编译打包的时候通过插件将其继承改为ShadowActivity,这里其实是改为了普通类,而在代理类中通过调用这些方法,从而实现了生命周期方法的调用,不过这里都避免不了加载插件的过程,所有DexClassLoader还是必须要加载使用的,这也是热修复的基础。