Android插件化开发之Hook StartActivity方法(2)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Android插件化开发之Hook StartActivity方法(2)

1)首先我们得拿到主线程对象的引用,如何获取呢?ActivityThread类里面有一个静态方法currentActivityThread可以帮助我们拿到这个对象类;但是ActivityThread是一个隐藏类,我们需要用反射去获取,代码如下:

// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

2)拿到这个currentActivityThread之后,我们需要修改它的mInstrumentation这个字段为我们的代理对象,我们先实现这个代理对象,由于JDK动态代理只支持接口,而这个Instrumentation是一个类,我们可以手动写静态代理类,覆盖掉原始的方法,代码如下

package com.example.hookstartactivity;
import java.lang.reflect.Method;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityResult;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class InstrumentationProxy extends Instrumentation {
   public static final String TAG = "InstrumentationProxy";
   public static final String EXEC_START_ACTIVITY = "execStartActivity";
   // ActivityThread里面原始的Instrumentation对象,这里千万不能写成mInstrumentation,这样写
   //抛出异常,已亲测试,所以这个地方就要注意了
   public Instrumentation oldInstrumentation;
   //通过构造函数来传递对象
   public InstrumentationProxy(Instrumentation mInstrumentation) {
     oldInstrumentation = mInstrumentation;
   }
   //这个方法是由于原始方法里面的Instrumentation有execStartActivity方法来定的
   public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
       Intent intent, int requestCode, Bundle options) {
     Log.d(TAG, "\n打印调用startActivity相关参数: \n" + "who = [" + who + "], " +
        "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
            "\ntarget = [" + target + "], \nintent = [" + intent +
            "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
    Log.i(TAG, "------------hook  success------------->");
    Log.i(TAG, "这里可以做你在打开StartActivity方法之前的事情");
    Log.i(TAG, "------------hook  success------------->");
    Log.i(TAG, "");
    //由于这个方法是隐藏的,所以需要反射来调用,先找到这方法
      try {
        Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                EXEC_START_ACTIVITY,
                  Context.class, IBinder.class, IBinder.class, Activity.class, 
                  Intent.class, int.class, Bundle.class);
          execStartActivity.setAccessible(true);
          return (ActivityResult) execStartActivity.invoke(oldInstrumentation, who, 
                      contextThread, token, target, intent, requestCode, options);
          } catch (Exception e) {
            //如果你在这个类的成员变量Instrumentation的实例写错mInstrument,代码讲会执行到这里来
              throw new RuntimeException("if Instrumentation paramerter is mInstrumentation, hook will fail");
          }
   }
}

3)然后用代理对象替换,代码如下

package com.example.hookstartactivity;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.app.Application;
import android.app.Instrumentation;
import android.util.Log;
public class MyApplication extends Application {
  public static final String TAG = "MyApplication";
  public static final String ACTIVIT_THREAD = "android.app.ActivityThread";
  public static final String CURRENT_ACTIVITY_THREAD = "currentActivityThread";
  public static final String INSTRUMENTATION = "mInstrumentation";
  @Override
  public void onCreate() {
    try {
         //这个方法一般是写在Application的oncreate函数里面,如果你写在activity里面的oncrate函数里面就已经晚了
         attachContext();
    } catch (Exception e) {
         e.printStackTrace();
    }
  }
  public static void attachContext() throws Exception{
      //获取当前的ActivityThread对象
      Class<?> activityThreadClass = Class.forName(ACTIVIT_THREAD);
      Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(CURRENT_ACTIVITY_THREAD);
      currentActivityThreadMethod.setAccessible(true);
      Object currentActivityThread = currentActivityThreadMethod.invoke(null);
      //拿到在ActivityThread类里面的原始mInstrumentation对象
      Field mInstrumentationField = activityThreadClass.getDeclaredField(INSTRUMENTATION);
      mInstrumentationField.setAccessible(true);
      Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
      //构建我们的代理对象
      Instrumentation evilInstrumentation = new InstrumentationProxy(mInstrumentation);
      //通过反射,换掉字段,注意,这里是反射的代码,不是Instrumentation里面的方法
      mInstrumentationField.set(currentActivityThread, evilInstrumentation);
      //做个标记,方便后面查看
      Log.i(TAG, "has go in MyApplication attachContext method");
  }
}

要注意这个替换要在Application里面的oncreate方法里面去执行,如果到Activity方法里面去执行的话就晚了,程序不会报错,但是hook不到。


然后我是在主页面写了一个按钮,点击来触发startActivity的。

MainActivity.java 文件如下

package com.example.hookstartactivity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
  public static final String TAG  =  "MainActivity";
  public TextView tv;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView)findViewById(R.id.start);
    tv.setOnClickListener(new OnClickListener(){
      @Override
      public void onClick(View v) {
        try {
          Intent intent = new Intent(MainActivity.this, SecondActivity.class); 
          Bundle bundle = new Bundle();
          Log.i(TAG, "-------------------------------->");
          Log.i(TAG, "startActivity before");
          Log.i(TAG, "-------------------------------->");
          startActivity(intent, bundle);
          Log.i(TAG, "-------------------------------->");
          Log.i(TAG, "startActivity after");
          Log.i(TAG, "-------------------------------->");
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    } );
  }
}

跳转到的Second.java文件如下

package com.example.hookstartactivity;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
public class SecondActivity extends ActionBarActivity{
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
  }
}


第七步、运行代码

启动项目在ubuntu终端打印的日志图片如下:

1.png

然后我点击图标调用startActivity方法后ubuntu终端打印的日志图片如下:

2.png日志上面看到,hook success了

看到ubuntu终端日志打印,前面显示了类,方便通过日志找bug,我用的是pidcat,下载pidcat

然后在ubuntu上面运行


pidcat.py 包名


就可以非常方便看的日志找bug了。

第八步、总结

 通过这篇日志,希望打击可以更好的理解hook、java反射、静态代理、动态代理、ActivityThread、Instrumentation

 最后附上源码下载地址,需要的小伙伴请猛搓这里  HookStartActivityDemo

 


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
9天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
11天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
9天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
10天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
22 2
|
11天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
18天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
16天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
17天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
36 3
|
14天前
|
移动开发 Java Android开发
探索Android与iOS开发的差异性与互联性
【10月更文挑战第32天】在移动开发的大潮中,Android和iOS两大平台各领风骚。本文将深入浅出地探讨这两个平台的开发差异,并通过实际代码示例,展示如何在各自平台上实现相似的功能。我们将从开发环境、编程语言、用户界面设计、性能优化等多个角度进行对比分析,旨在为开发者提供跨平台开发的实用指南。
37 0