base
是系统原来的执行逻辑,存起来后面用得着.
- 创建
Instrumentation
代理类, 继承Instrumentation
然后,重写execStartActivity
方法,加入自己的逻辑,然后再执行系统的逻辑.
private static class ProxyInstrumentation extends Instrumentation {
public ProxyInstrumentation(Instrumentation base) {
this.base = base;
}
Instrumentation base;
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d(“ProxyInstrumentation”, “我们自己的逻辑”);
//这里还要执行系统的原本逻辑,但是突然发现,这个execStartActivity居然是hide的,只能反射咯
try {
Class<?> InstrumentationClz = Class.forName(“android.app.Instrumentation”);
Method execStartActivity = InstrumentationClz.getDeclaredMethod(“execStartActivity”,
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
return (ActivityResult) execStartActivity.invoke(base,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 用代理类对象替换
hook
对象.
ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);
mInstrumentationField.set(activity, proxyInstrumentation);
如何使用: 在
MainActivity
的onCreate
中加入一行ActivityHookHelper.hook(this)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityHookHelper.hook(this);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivityByActivity();
}
});
}
private void startActivityByActivity() {
Intent i = new Intent(MainActivity.this, Main2Activity.class);
startActivity(i);
}
}
效果:跳转依然正常,并且logcat
中可以发现下面的日志.
#####ok,插入自己的逻辑,成功
##三. 第二种启动方式的hook方案
创建ApplicationContextHookHelper.java
,然后 同样是三步走
:
1.确定hook的对象和该对象的持有者
锁定ActivityThread
的mInstrumentation
成员.
//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);
如何使用: 在
Main4Activity
的onCreate
中加入一行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);
}
}
效果
####OK,第二种启动方式,我们也可以加入自己的逻辑了.hook成功!
##四. 目前方案弊端分析
启动方式1的hook: 只是在针对单个Activity
类,来进行hook
,多个Activity
则需要写多次,或者写在BaseActivity
里面.
启动方式2的hook:可以针对全局进行hook,无论多少个Activity,只需要调用一次ApplicationContextHookHelper.hook();
函数即可,但是,它只能针对 getApplicationContext().startActivity(i);
普通的Activity.startActivity
则不能起作用.
那么有没有一种完全体的解决方案:能够在全局起作用,并且可以在两种启动方式下都能hook
.
回顾之前的两张代码索引结论图
,会发现,两种启动Activity的方式,最终都被执行到了 AMS
内部,
下一步,尝试hook AMS.
##五. 最终解决方案
代码索引: 基于SDK 28 ~ android9.0
下方红框标记的部分,就是取得
AMS
(ActivityManagerService
实例)的代码.
如果可以在系统接收到AMS实例之前,把他截
了,是不是就可以达到我们的目的?
进去看看getService的代码:
真正的AMS
实例来自一个Singleton
单例辅助类的create()
方法,并且这个Singleton
单例类,提供get
方法,获得真正的实例.
那么,我们从这个单例中,就可以获得系统当前的 AMS
实例,将它取出来,然后保存.
OK,确认:
hook
对象: ActivityManager
的IActivityManagerSingleton
成员 变量内的 单例 mInstance
.
hook
对象的持有者:ActivityManager
的IActivityManagerSingleton
成员变量
那么,动手:
- 找到
hook
对象,并且存起来
//1.把hook的对象取出来保存
//矮油,静态的耶,开心.
Class<?> ActivityManagerClz = Class.forName(“android.app.ActivityManager”);
Method getServiceMethod = ActivityManagerClz.getDeclaredMethod(“getService”);
final Object IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
- 创建自己的代理类对象,
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);
}
});
- 偷梁换柱:这次有点复杂, 不再是简单的
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
找原因:
为什么会没有getService
这个方法!?
查看了我当前设备的系统版本号
居然是23版本,6.0
.
所以,恍然大悟,我们写的hook代码并没有兼容性,遇到低版本的设备,就失灵了.
解决方案:
1.找到SDK 23
的源码
(注意,前方有坑,androidStudio
,你如果直接把combileSDK
改成23.会出现很多位置问题,所以不建议这么做. 但是我们一定要看SDK 23
的源码,怎么办?
- .在线查看源码 - https://www.androidos.net.cn/sourcecode ;
- 从谷歌官网下载
SDK 23
的源码,然后用SourceInsight
查看)
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实例
的方式完全不同.
它是使用ActivityManagerNative.getDefault()
来获得的,继续往下找,看看有没有什么不同。
进去ActivityManagerNative
找找看:
OK,找到了区别,确定结论:SDK 28
和23
在这块代码上的区别就是:
获得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();
}
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
.(img-MA7f9bNH-1715255793465)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-9kcr3aOv-1715255793466)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!