hook(2)Activity启动流程,2021年大厂Android岗面试必问

本文详细介绍了如何通过hook ActivityThread的mInstrumentation字段和AMS的IActivityManager接口,实现全局Activity启动流程的拦截。文章讲解了两种hook方案,包括它们的优缺点,并着重解决了在不同Android版本间的兼容性问题,提供了最终的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

}

效果:跳转依然正常,并且logcat中可以发现下面的日志.

image.png

#####ok,插入自己的逻辑,成功


##三. 第二种启动方式的hook方案
创建ApplicationContextHookHelper.java,然后 同样是三步走

1.确定hook的对象和该对象的持有者
锁定 ActivityThreadmInstrumentation成员.

//1.主线程ActivityThread内部的mInstrumentation对象,先把他拿出来
Class<?> ActivityThreadClz = Class.forName(“android.app.ActivityThread”);
//再拿到sCurrentActivityThread
Field sCurrentActivityThreadField = ActivityThreadClz.getDeclaredField(“sCurrentActivityThread”);
sCurrentActivityThreadField.setAccessible(true);
Object activityThreadObj = sCurrentActivityThreadField.get(null);//静态变量的属性get不需要参数,传null即可.
//再去拿它的mInstrumentation
Field mInstrumentationField = ActivityThreadClz.getDeclaredField(“mInstrumentation”);
mInstrumentationField.setAccessible(true);
Instrumentation base = (Instrumentation) mInstrumentationField.get(activityThreadObj);// OK,拿到

2.创建代理对象 和上面的代理类一模一样,就不重复贴代码了

//2.构建自己的代理对象,这里Instrumentation是一个class,而不是接口,所以只能用创建内部类的方式来做
ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);

3.替换掉原对象

//3.偷梁换柱
mInstrumentationField.set(activityThreadObj, proxyInstrumentation);

如何使用: 在Main4ActivityonCreate中加入一行ApplicationContextHookHelper.hook();

public class Main4Activity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);

ApplicationContextHookHelper.hook();
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivityByApplicationContext();
}
});
}

private void startActivityByApplicationContext() {
Intent i = new Intent(Main4Activity.this, Main5Activity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(i);
}
}

效果
image.png

####OK,第二种启动方式,我们也可以加入自己的逻辑了.hook成功!


##四. 目前方案弊端分析
启动方式1的hook: 只是在针对单个Activity类,来进行hook,多个Activity则需要写多次,或者写在BaseActivity里面.
启动方式2的hook:可以针对全局进行hook,无论多少个Activity,只需要调用一次ApplicationContextHookHelper.hook();函数即可,但是,它只能针对 getApplicationContext().start![](https://www.hualigs.cn/image/61dba891ed8ee.jpg) Activity(i); 普通的Activity.startActivity则不能起作用.

那么有没有一种完全体的解决方案:能够在全局起作用,并且可以在两种启动方式下都能hook.
回顾之前的两张代码索引结论图,会发现,两种启动Activity的方式,最终都被执行到了 AMS内部,
下一步,尝试hook AMS.


##五. 最终解决方案

代码索引: 基于SDK 28 ~ android9.0

下方红框标记的部分,就是取得AMSActivityManagerService实例)的代码.
image.png
如果可以在系统接收到AMS实例之前,把他了,是不是就可以达到我们的目的?
进去看看getService的代码:

image.png

真正的AMS实例来自一个Singleton单例辅助类的create()方法,并且这个Singleton单例类,提供get方法,获得真正的实例.

image.png

那么,我们从这个单例中,就可以获得系统当前的 AMS实例,将它取出来,然后保存.
OK,确认:
hook对象: ActivityManagerIActivityManagerSingleton成员 变量内的 单例 mInstance.
hook对象的持有者:ActivityManagerIActivityManagerSingleton成员变量

那么,动手:

  1. 找到hook对象,并且存起来

//1.把hook的对象取出来保存
//矮油,静态的耶,开心.
Class<?> ActivityManagerClz = Class.forName(“android.app.ActivityManager”);
Method getServiceMethod = ActivityManagerClz.getDeclaredMethod(“getService”);
final Object IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例

  1. 创建自己的代理类对象,IActivityManager 是一个AIDL生成的动态接口类,所以在编译时,androidStudio会找不到这个类,所以,先反射,然后用Proxy进行创建代理。

//2.现在创建我们的AMS实例
//由于IActivityManager是一个接口,那么我们可以使用Proxy类来进行代理对象的创建
// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
Class<?> IActivityManagerClz = Class.forName(“android.app.IActivityManager”);
Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManagerClz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
if (method.getName().equals(“startActivity”)) {
Log.d(“GlobalActivityHook”, “全局hook 到了 startActivity”);
}
return method.invoke(IActivityManagerObj, args);
}
});

  1. 偷梁换柱:这次有点复杂, 不再是简单的field.set,因为这次的hook对象被包裹在了一个Singleton里。

//3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面
Field IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField(“IActivityManagerSingleton”);
IActivityManagerSingletonField.setAccessible(true);
Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
//反射创建一个Singleton的class
Class<?> SingletonClz = Class.forName(“android.util.Singleton”);
Field mInstanceField = SingletonClz.getDeclaredField(“mInstance”);
mInstanceField.setAccessible(true);
mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

使用方法:老样子,在你自己的Activity onCreate里面加入GlobalActivityHookHelper.hook();
运行起来,预期结果应该是:能够在logcat中看到日志 :
GlobalActivityHook - 全局hook 到了 startActivity;
但是,你运行起来可能看不到这一行。

如果你看不到这个日志,那么原因就是:

程序报错了,

报错啦!
没有这样的方法,怎么回事?
debug找原因:
image.png
为什么会没有getService这个方法!?
查看了我当前设备的系统版本号image.png
居然是23版本,6.0.
所以,恍然大悟,我们写的hook代码并没有兼容性,遇到低版本的设备,就失灵了.

解决方案:

1.找到SDK 23的源码
(注意,前方有坑,androidStudio,你如果直接把combileSDK改成23.会出现很多位置问题,所以不建议这么做. 但是我们一定要看SDK 23的源码,怎么办?

2.查看getService方法不存在的原因,两个版本28 和 23,在这一块代码上有什么不同.
3.改造 GlobalActivityHookHelper.java ,判定当前设备的系统版本号,让它可以兼容所有版本.

按照上面的步骤:
我发现SDK 23里面:
Instrumentation类的 execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId)方法里,获取AMS实例的方式完全不同.

image.png
它是使用 ActivityManagerNative.getDefault()来获得的,继续往下找,看看有没有什么不同。
进去ActivityManagerNative 找找看:
image.png
image.png

OK,找到了区别,确定结论:SDK 2823在这块代码上的区别就是:
获得AMS实例的类名和方法名都不同.另外,查了度娘之后发现,这个变化是在SDK 26版本修改的,所以26和26以后,ActivityManager.getService()来获取,26以前,用ActivityManagerNative.getDefault()来获得
调整当前的hook方法,修改为下面这样:

public class GlobalActivityHookHelper {

//设备系统版本是不是大于等于26
private static boolean ifSdkOverIncluding26() {
int SDK_INT = Build.VERSION.SDK_INT;
if (SDK_INT > 26 || SDK_INT == 26) {
return true;
} else {
return false;
}
}

public static void hook() {

try {
Class<?> ActivityManagerClz;
final Object IActivityManagerObj;
if (ifSdkOverIncluding26()) {
ActivityManagerClz = Class.forName(“android.app.ActivityManager”);
Method getServiceMethod = ActivityManagerClz.getDeclaredMethod(“getService”);
IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
} else {
ActivityManagerClz = Class.forName(“android.app.ActivityManagerNative”);
Method getServiceMethod = ActivityManagerClz.getDeclaredMethod(“getDefault”);
IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
}

//2.现在创建我们的AMS实例
//由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
Class<?> IActivityManagerClz = Class.forName(“android.app.IActivityManager”);
Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IActivityManagerClz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
if (method.getName().equals(“startActivity”)) {
Log.d(“GlobalActivityHook”, “全局hook 到了 startActivity”);
}
return method.invoke(IActivityManagerObj, args);
}
});

//3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面
Field IActivityManagerSingletonField;
if (ifSdkOverIncluding26()) {
IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField(“IActivityManagerSingleton”);
} else {
IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField(“gDefault”);
}

IActivityManagerSingletonField.setAccessible(true);
Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
Class<?> SingletonClz = Class.forName(“android.util.Singleton”);//反射创建一个Singleton的class
Field mInstanceField = SingletonClz.getDeclaredField(“mInstance”);
mInstanceField.setAccessible(true);
mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

} catch (Exception e) {
e.printStackTrace();
}
}
}

再次尝试:

image.png

成功,实现了全局范围内的startActivity动作的hook.


##六. Hook开发可能的坑
1. androidStudio阅读源码很多类无法索引,这是因为有一些类是@hide的,无法Ctrl点进去,
解决方案:Ctrl+shift+R 输入类名,手动进入.

2.androidStudio阅读源码直接报红 :或者一些是AIDL动态生成的接口,无法直接查看,比IActivityManager. ,
解决方案:这种接口不用管它,如果非要用到它,那就使用本类的包名+IActivityManager作为全限定名,去反射创建它.

3. hook开发,是学习源码思想,改变源码执行流程,所以,在多个版本的设备上运行,很容易发生不兼容的情况.
解决方案:找到不兼容的设备版本,根据报的异常,参照源码的版本变迁做出相应的兼容性改动.


#结语
历时3天,忙里偷闲,总算是写完了.
喜欢的客官帮忙点个赞哦,你们的鼓励是我最大的动力,以后还会更新更多干货.
最后~本文的代码Demo.

它,如果非要用到它,那就使用本类的包名+IActivityManager作为全限定名,去反射创建它.**

3. hook开发,是学习源码思想,改变源码执行流程,所以,在多个版本的设备上运行,很容易发生不兼容的情况.
解决方案:找到不兼容的设备版本,根据报的异常,参照源码的版本变迁做出相应的兼容性改动.


#结语
历时3天,忙里偷闲,总算是写完了.
喜欢的客官帮忙点个赞哦,你们的鼓励是我最大的动力,以后还会更新更多干货.
最后~本文的代码Demo.

### PyCharm 打开文件显示全的解决方案 当遇到PyCharm打开文件显示全的情况时,可以尝试以下几种方法来解决问题。 #### 方法一:清理缓存并重启IDE 有时IDE内部缓存可能导致文件加载异常。通过清除缓存再启动程序能够有效改善此状况。具体操作路径为`File -> Invalidate Caches / Restart...`,之后按照提示完成相应动作即可[^1]。 #### 方法二:调整编辑器字体设置 如果是因为字体原因造成的内容显示问题,则可以通过修改编辑区内的文字样式来进行修复。进入`Settings/Preferences | Editor | Font`选项卡内更改合适的字号大小以及启用抗锯齿功能等参数配置[^2]。 #### 方法三:检查项目结构配置 对于某些特定场景下的源码视图缺失现象,可能是由于当前工作空间未能正确识别全部模块所引起。此时应该核查Project Structure的Content Roots设定项是否涵盖了整个工程根目录;必要时可手动添加遗漏部分,并保存变更生效[^3]。 ```python # 示例代码用于展示如何获取当前项目的根路径,在实际应用中可根据需求调用该函数辅助排查问题 import os def get_project_root(): current_file = os.path.abspath(__file__) project_dir = os.path.dirname(current_file) while not os.path.exists(os.path.join(project_dir, '.idea')): parent_dir = os.path.dirname(project_dir) if parent_dir == project_dir: break project_dir = parent_dir return project_dir print(f"Current Project Root Directory is {get_project_root()}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值