Java基础知识点面试题,安卓程序员必备hook技术之进阶篇

方式1:使用Activity自带的startActivity

示例代码

private void startActivityByActivity() {
        Intent i = new Intent(MainActivity.this, Main2Activity.class);
        startActivity(i);
    }

程序执行走向图.

代码追踪:


这里有个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:使用applictonContextstartActivity

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); 的最终执行者是:ActivityThreadmInstrumentation成员,持有者是ActivityThread 主线程.
两种方式都可以把mInstrumentation当作hook切入点,将它从它的持有者中"偷梁换柱".

下面开始动手尝试:

##二. 第一种启动方式的hook方案

创建一个HookActivityHelper.java ,然后三步走:

  1. 找到hook点,以及hook对象的持有者,上文中已经说明:hook点是ActivitymInstrumentation成员,持有者就是Activity
      Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");
      mInstrumentationField.setAccessible(true);
      Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);

base是系统原来的执行逻辑,存起来后面用得着.

  1. 创建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;
        }

    }
  1. 用代理类对象替换 hook对象.
  ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);
  mInstrumentationField.set(activity, proxyInstrumentation);

如何使用: 在MainActivityonCreate中加入一行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的对象和该对象的持有者
锁定 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);
    }
}

效果

####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

下方红框标记的部分,就是取得AMSActivityManagerService实例)的代码.

如果可以在系统接收到AMS实例之前,把他了,是不是就可以达到我们的目的?
进去看看getService的代码:

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

那么,我们从这个单例中,就可以获得系统当前的 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")) {


              @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
                    if (method.getName().equals("startActivity")) {


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值