Android个人项目插件化总结——方式二(Hook Instrumentation)

本文总结了Android插件化的一种方法,即通过Hook Instrumentation实现。详细阐述了如何hook Activity的mInstrumentation和ActivityThread的mInstrumentation,拦截execStartActivity和newActivity方法,替换Intent以实现跳转到目标Activity。同时讨论了当使用Application.startActivity()时,只需hook ActivityThread的Instrumentation即可。

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

文章目录

概述

第二种方式思路非常清晰,直接Hook Instrumentation。

由activity启动流程知道,startActivity会交给Activity的mInstrumentation.execStartActivity()来处理。

最终会调用ActivityThread里面的performLaunchActivity()方法。performLaunchActivity()方法进而调用 mInstrumentation.newActivity()会创建启动Activity。

所以步骤很简单。

因为有Application.startActivity()和Activity.startActivity()。等会说Application.startActivity(),先说Activity.startActivity()。

  1. hook Activity的mInstrumentation变量
  2. hook ActivityThread的mInstrumentation变量
  3. 在我们的Instrumentation类里面拦截execStartActivity方法,在里面将Intent替换成我们的占坑Activity。
  4. 在我们的Instrumentation类里面拦截newActivity方法,将Intent替换回来,再完成Activity的创建。

代码

首先是我们的HookedInstrumentation 类,我们通过反射得到原有的Instrumentation 放入我们的类中,在execStartActivity方法中替换Intent,完成AMS的验证。newActivity方法中再将Intent替换回来。这里需要注意一下,因为在newActivity中传入了我们自己的ClassLoader,关于ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);这个代码先不要管,下篇会说怎样加载插件APK的资源。

public class HookedInstrumentation extends Instrumentation   {
    public static final String TAG = "Lpp";
    protected Instrumentation mBase;
    private PluginManager mPluginManager;

    public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) {
        mBase = base;
        mPluginManager = pluginManager;
    }

    /**
     * 覆盖掉原始Instrumentation类的对应方法,用于插件内部跳转Activity时适配
     *
     * @Override
     */
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        mPluginManager.hookToStubActivity(intent);

        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity", Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("do not support!!!" + e.getMessage());
        }
    }

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (mPluginManager.hookToPluginActivity(intent)) {
            String targetClassName = intent.getComponent().getClassName();
            PluginApp pluginApp = mPluginManager.getLoadedPluginApk();
            Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);
            Log.d(TAG, "newActivity: " + activity);
            Log.d(TAG, "newActivity getApplicationContext: " + activity.getApplication());
            ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);
            return activity;
        }
        return super.newActivity(cl, className, intent);
    }

}

完成我们的Instrumentation类后,接下来就是反射设置给ActivityThread和Activity了。

Activity

 public void hookCurrentActivityInstrumentation(Activity activity) {
     ReflectUtil.setActivityInstrumentation(activity, sInstance);
 }
 public static void setActivityInstrumentation(Activity activity, PluginManager manager) {
     try {
         sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity);
         HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager);
         sActivityInstrumentationField.set(activity, instrumentation);
     } catch (IllegalAccessException e) {
         e.printStackTrace();
     }
 }

ActivityThread

 public void hookInstrumentation() {
     try {
         Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation();
         final HookedInstrumentation instrumentation =
                 new HookedInstrumentation(baseInstrumentation, this);

         Object activityThread = ReflectUtil.getActivityThread();
         ReflectUtil.setInstrumentation(activityThread, instrumentation);
     } catch (Exception e) {
         e.printStackTrace();
     }
 }

下面是初始化的代码,得到ActivityThread等等

 public static final String METHOD_currentActivityThread = "currentActivityThread";
 public static final String CLASS_ActivityThread = "android.app.ActivityThread";
 public static final String FIELD_mInstrumentation = "mInstrumentation";
 public static final String TAG = "Lpp";

 private static Instrumentation sInstrumentation;
 private static Instrumentation sActivityInstrumentation;
 private static Field sActivityThreadInstrumentationField;
 private static Field sActivityInstrumentationField;
 private static Object sActivityThread;

 public static boolean init() {
     //获取当前的ActivityThread对象
     Class<?> activityThreadClass = null;
     try {
         activityThreadClass = Class.forName(CLASS_ActivityThread);
         Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread);
         currentActivityThreadMethod.setAccessible(true);
         Object currentActivityThread = currentActivityThreadMethod.invoke(null);

         //拿到在ActivityThread类里面的原始mInstrumentation对象
         Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation);
         instrumentationField.setAccessible(true);
         sActivityThreadInstrumentationField = instrumentationField;

         sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
         sActivityThread = currentActivityThread;


         sActivityInstrumentationField =  Activity.class.getDeclaredField(FIELD_mInstrumentation);
         sActivityInstrumentationField.setAccessible(true);
         return true;
     } catch (ClassNotFoundException
             | NoSuchMethodException
             | IllegalAccessException
             | InvocationTargetException
             | NoSuchFieldException e) {
         e.printStackTrace();
     }

     return false;
 }

补充

上面说的是通过Activity.startActivity()完成跳转时的情况。

当我们使用Application.startActivity()完成跳转时,只需要hook ActivityThread里面的Instrumentation就可以了。

让我们来看为什么。

    getApplication().startActivity();
    
    public final Application getApplication() {
        return mApplication;
    }

Application继承的是ContextWrapper,ContextWrapper又继承自Context,所以我们在ContextWrapper里面找startActivity方法。

	public class ContextWrapper extends Context {
	    Context mBase;
	    ······
	    @Override
	    public void startActivity(Intent intent) {
	        mBase.startActivity(intent);
	    }
	}		

调用了mBase的startActivity,但是mBase是Context类型的,Context是一个抽象类,所以我们寻找他的实现类。

他的实现类是ContextImpl 。寻找他的startActivity方法。

class ContextImpl extends Context {
	······
    final ActivityThread mMainThread;
   
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
}

可以很清楚的看到,他从mMainThread中得到了Instrumentation对象,并调用了他的execStartActivity方法。而这个mMainThread就是ActivityThread 。

所以说通过Application启动活动时,只需要hook ActivityThread里面的Instrumentation就可以了

### hook在编程或软件开发中的定义和用法 #### 1. Hook 的基本概念 Hook 是一种常见的编程术语,在不同的上下文中可能具有略微不同的意义,但其核心思想始终围绕着拦截、扩展或修改某些行为的功能。它允许开发者通过某种方式介入系统的运行流程,从而改变默认的行为或者注入自定义逻辑。 在 C++ 软件调试技术中,掌握 Hook 技术可以帮助开发者更好地理解程序内部的工作机制,并能够动态调整应用程序的状态[^1]。而在前端 Vue.js 开发领域,钩子方法(hook method)则提供了一种优雅的方式来管理组件生命周期的不同阶段,使代码更模块化和易于维护[^2]。 对于 Android 插件化开发而言,Hook 更加具体地表现为对系统框架层面上的关键对象进行重定向操作的能力。例如通过对 ActivityManagerService 或 Instrumentation 类的成员函数实施替换处理来达到加载外部 APK 文件的目的[^3]。 另外,在一些高级场景下比如基于 Xposed 框架的应用改造工作中,“原理”部分也提到了如何利用 Java Reflection 和代理模式完成 runtime 层面的方法挂钩工作。这种类型的 Hook 不仅限于 Android 平台本身,还可以广泛应用于桌面端甚至服务器端环境下的功能增强需求之中[^4]。 综上所述,无论是在哪一类 IT 领域当中讨论到 Hook ,它们都共同指向了一个目标 —— 即赋予程序员更大的灵活性去控制原本不可见或者难以触及的部分。 #### 2. 使用案例分析 以下是几个典型例子展示了 Hook 如何被实际运用: - **JavaScript 中的回调函数** ```javascript var a = 0; function bb(x) { console.log(x); } function timer(time, callback) { setTimeout(() => { a = 6; callback(a); // 当定时器到期时触发此回调函数 }, time); } console.log(a); // 输出初始值 '0' timer(3000, bb); // 经过三秒后打印更新后的变量'a'即数值‘6’ ``` 在这个 JavaScript 实现里,`timer()` 函数接受两个参数:一个是延迟时间 `time`;另一个则是待执行完毕之后要调用回来的匿名函数指针形式表示出来的动作描述符——也就是我们所说的 “callback”。这正是最简单直观的一种 Hook 表达样式之一[^2]。 - **Xposed Framework 下的 Method Hook** 假设我们需要监控某个应用每次启动新 activity 的过程,则可以通过编写如下所示的一个简易版 Module 来达成目的: ```java public class MyModule implements IXposedHookLoadPackage { @Override public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { if (!lpparam.packageName.equals("com.example.targetapp")) return; findAndHookMethod(Activity.class.getName(), lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Log.d("MyModule", String.format( "%s.onCreate called with bundle %s", ((Activity)(param.thisObject)).getClass().getSimpleName(), (Bundle)param.args[0])); } }); } } ``` 这里借助了反射手段获取目标类实例及其公开接口列表,并进一步绑定至指定位置处设置监听点以便实时捕获感兴趣的数据流变化情况[^4]。 --- 问题
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值