这里有个if(mParent==null)
判定,先看true
分支:
发现一个坑,mInstrumentation.execStartActivity
这里居然不能继续往下索引了?很奇怪,不过不重要,我们直接进入Instrumentation.java
去找这个方法:
在这个execStartActivity中,可以找到关键代码
:
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
通过这种方式启动Activity,最终的执行权被交给了
ActivityManager.getService()
(即AMS
),它的作用是 启动一个Activity并且返回result
,然后checkStartActivityResult(result, intent);
这句话,对当前的跳转意图intent
进行检测;
have you declared this activity in your AndroidManifest.xml
这句异常应该很熟悉了吧?启动一个没有注册的Activity的报错.
再看个if(mParent==null)
的false
分支:
控制权依然是交给了mInstrumentation.execStartActivity()
,剩余的代码索引和上面的一样.
所以,代码索引的结论,按照一张图来表示就是:
方式2:使用applictonContext
的startActivity
private void startActivityByApplicationContext() {
Intent i = new Intent(MainActivity.this, Main2Activity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(i);
}
在 方式1 中已经展示了源码索引的方式,所以这里不再赘述贴图.直接给出代码索引结论图
:
两张图对比,我们很容易得出一个结论:
启动Activity
的最终执行权,都被交给了 Instrumentation.java
类,
方式1:Activity.startActivity
的最终执行者是 它的mInstrumentation
成员,mInstrumentation
的持有者是 Activity
自身.
方式2:getApplicationContext().startActivity(i);
的最终执行者是:ActivityThread
的 mInstrumentation
成员,持有者是ActivityThread
主线程.
两种方式都可以把mInstrumentation
当作hook切入点,将它从它的持有者中"偷梁换柱".
下面开始动手尝试:
##二. 第一种启动方式的hook方案
创建一个HookActivityHelper.java
,然后三步走:
- 找到
hook
点,以及hook
对象的持有者,上文中已经说明:hook
点是Activity
的mInstrumentation
成员,持有者就是Activity
Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);
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
成员变量
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
文末
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家
这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
【视频教程】
天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
【视频教程】
[外链图片转存中…(img-RGbrxV9a-1712725656371)]
天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-9OlkOA7O-1712725656371)]