Android——简单易懂说原理之Hook技术与插件化原理

本文详细介绍了Android中的Hook技术,包括Activity的startActivity方法和Content的startActivity方法的Hook实现。接着,文章深入探讨了插件化原理,讲解了在Android 9.0前后使用Hook技术实现Activity插件化的过程,以及如何通过代理分发实现Service、ContentProvider、BroadcastReceiver的插件化。此外,还提到了VirtualAPK的插件化实现,包括资源和so的插件化。文章最后总结了AMS、PMS和Handler在插件化中的作用,并指出插件化的优点在于模块化更新。
摘要由CSDN通过智能技术生成

写在前面:关于插件化涉及到的实在是太多了,这里强烈推荐大家看一看包建强的《Android插件化开发指南》。另外关于本文内作为示例的VirtualAPK,最后一次更新是2018年,兼容到Android9.0。大家也可以看看腾讯在2019年开源的 Shadow

一、Hook技术

Hook技术是一种在程序运行时对程序进行调试的技术。举个例子,使用Hook前如下图:
在这里插入图片描述
使用Hook,变成了下面这样:
在这里插入图片描述
Hook挂在了对象A和对象B之间,这样就可以对对象A的参数做修改,以及修改对象B的返回值,也就是一种劫持——对象B收到的不是对象A发出的,对象A收到的也不是对象B返回的。
我们知道应用程序进程与系统进程之间、以及应用程序进程之间都是彼此独立的,但是使用了Hook技术,就可以在进程之间进行行为更改了。比如想要使用应用程序进程更改系统进程的某些行为,就可以使用Hook融入到系统进程中,然后再来通过Hook改变系统进程中对象的行为,如下图所示:
在这里插入图片描述
需要注意的是,这个被劫持的对象B(Hook点),为了保证Hook的稳定性,一般会选择容易找到且不易变化的对象,比如静态变量或者是单例。

了解了什么是Hook技术,接下来我们就说说在Android开发中会涉及到的Hook技术。

  • 按照Hook的API语言划分,分为Hook Java和Hook Native。Hook Java主要通过反射和代理来实现,应用于在SDK开发环境中修改Java代码;Hook Native应用于在NDK开发环境和系统开发中修改Native代码。
  • 按照Hook的进程划分,分为应用程序进程Hook和全局Hook。应用程序进程Hook只能Hook当前所在的应用程序进程;对Zygote进行Hook,就可以实现Hook系统所有的应用程序进程,即全局Hook(不要忘了应用程序进程是Zygote进程fock出来的,不清楚的请参考《Android系统启动流程及应用程序进程启动过程》一文)。
  • 按照Hook的实现方式划分,分为通过反射和代理实现、通过Hook框架实现两种。其中通过反射和代理实现只能Hook当前的应用程序进程,而通过Hook框架实现则可以实现全局Hook。

由于我们讲Hook技术是为了讲插件化原理做准备,因此在这里主要讲讲Hook Java。
首先我们来复习一下代理模式,之所以要讲代理模式,这是因为用代理对象来替代Hook点,我们就可以在代理上实现自己想做的操作了。
在这里插入图片描述
真实主题类实现抽象主题接口方法,实现真实方法意图;代理类实现抽象主题接口方法,在这其中调用所持有的被代理者所实现的接口方法。最后在客户端类创建真实主题类,之后创建 “抽象主题接口 代理= new 代理类(真实主题类)”,并调用“代理.实现的接口方法”。
以上就是我们经常使用的静态代理。之所叫静态代理,是因为代码运行前就已经存在了代理类的class编译文件。相对的,动态代理就是在代码运行时通过反射来动态地生成代理对象,并确定到底来代理谁。
Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke方法。首先创建动态代理类,如下所示:

public class 动态代理类 implements InvocationHandler{
   
    private Object obj;//指向被代理类
    public 动态代理类(Object obj){
   
        this.obj=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        Object result=method.invoke(obj, args);
        return result;
    }
}

之后修改客户端类的调用方式,如下:

抽象主题接口 真实主题=new真实主题类();
//创建动态代理
动态代理类  动态代理=new 动态代理类(真实主题);
//创建真实主题类的ClassLoader
ClassLoader loader=真实主题.getClass().getClassLoader();
//动态创建代理类
抽象主题接口 代理= (抽象主题接口) Proxy.newProxyInstance(loader,new Class[]{
   IShop.class},动态代理);
代理.实现的接口方法();

上面调用了Proxy.newProxyInstance()方法来生成动态代理类,最后调用代理.实现的接口方法会调用动态代理类的invoke方法。

了解了这些必备知识点后,我们就以Hook常用的startActivity为例进行讲解。

1.Activity的startActivity方法

我们知道Activity是通过startActivity方法启动的,不了解这部分知识点可以查看《Android四大组件工作过程》一文。在startActivity方法中,实际上最终调用的是startActivityForResult方法,如下所示:
在这里插入图片描述

注意查看用红框圈起来的部分,这里就是我们要进行Hook的地方。可以看到,使用了mInstrumentation的execStartActivity方法来启动Activity,而这个mInstrumentation正是Activity的成员变量,因此我们可以选择Instrumentation为Hook点,用代理的Instrumentation来替代原始的Instrumentation,从而完成Hook。
首先写一个代理Instrumentation类,如下:

public class InstrumentationProxy extends Instrumentation {
   
    private static final String TAG = "InstrumentationProxy";
    Instrumentation instrumentation;
    public InstrumentationProxy(Instrumentation instrumentation){
   
        this.instrumentation = instrumentation;
    }
    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token,
                                            Activity target, Intent intent, int requestCode,
                                            Bundle options){
   
        Log.d(TAG,"进到这里表示Hook成功:"+who);
        try {
   
            //通过反射找到Instrumentation的execStartActivity方法
            Method execStartActivity = Instrumentation.class.
                    getDeclaredMethod("execStartActivity",Context.class,IBinder.class,
                            IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
            return (ActivityResult)execStartActivity.invoke(instrumentation,who,contextThread,token,
                    target,intent,requestCode,options);
        }catch (Exception e){
   
            throw new RuntimeException(e);
        }
    }
}

可以看到,InstrumentationProxy 继承了Instrumentation ,且包含了Instrumentation 的引用,并且实现了execStartActivity方法。execStartActivity方法内部通过反射找到并调用Instrumentation 的execStartActivity方法,这就用到了我们刚才复习过的动态代理模式。
写完了代理类,我们再来用InstrumentationProxy 来替换掉Instrumentation ,如下所示:

//使用InstrumentationProxy替换Instrumentation
public void replaceActivityInstrumentation(Activity activity){
   
    try {
   
        //得到Activity的mInstrumentation字段
        Field field = Activity.class.getDeclaredField("mInstrumentation");
        //取消Java的权限控制检查
        field.setAccessible(true);
        //得到Instrumentation对象
        Instrumentation instrumentation = (Instrumentation)field.get(activity);
        //创建InstrumentationProxy并注入Instrumentation对象
        Instrumentation instrumentationProxy = new 	InstrumentationProxy(instrumentation);
        //使用InstrumentationProxy替换Instrumentation
        field.set(activity,instrumentationProxy);
    }catch (Exception e){
   
        e.printStackTrace();
    }
}

每一步的含义都写在注释里,这里就不再赘述了。
最后在我们应用的Activity的onCreate方法中使用这个replaceActivityInstrumentation方法来进行替换。

@Override
protected void onCreate(Bundle savedInstanceState) {
   
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    replaceActivityInstrumentation(this);
    //打个电话试一下有没有成功
    Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086"));
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

运行一下,看一下Log,显示了我们在代理类中的log,成功。
在这里插入图片描述

2.Content的startActivity方法

我们知道Context的实现类是ContextImpl,因此最后是通过ContextImpl的startActivity方法启动Activity的,不了解这部分知识点可以查看《Context、Activity任务栈模型、WindowManager、Window以及WindowManagerService》一文。startActivity方法代码如下所示:

frameworks/base/core/java/android/app/ContextImpl.java
在这里插入图片描述

还是注意查看用红框圈起来的部分,这里就是我们要进行Hook的地方。可以看到,这里首先通过 ActivityThread类型的mMainThread.getInstrumentation()获得Instrumentation,然后再调用execStartActivity方法来启动Activity。因为一个进程中只有一个ActivityThread,因此我们可以选择Instrumentation为Hook点,用代理的Instrumentation来替代原始的Instrumentation,从而完成Hook。
还是用之前写好的代理类,我们直接看替换部分,如下所示:

//使用InstrumentationProxy替换Instrumentation(Hook Context)
public void replaceContextInstrumentation(){
   
    try {
   
        //获取ActivityThread类
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        //获取ActivityThread中定义的sCurrentActivityThread字段
        Field activityThreadfield = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
        //取消Java的权限控制检查
        activityThreadfield.setAccessible(true);
        //获得sCurrentActivityThread对象
        Object currentActivityThread = activityThreadfield.get(null);
        //获得ActivityThread中的mInstrumentation字段
        Field mInstrumentationField = activityThreadClazz.getDeclaredField("mInstrumentation");
        //取消Java的权限控制检查
        mInstrumentationField.setAccessible(true);
        //得到Instrumentation对象
        Instrumentation mInstrumentation = (Instrumentation)mInstrumentationField.get(currentActivityThread);
        //创建InstrumentationProxy并注入Instrumentation对象
        Instrumentation mInstrumentationProxy = new InstrumentationProxy(mInstrumentation);
        //使用InstrumentationProxy替换Instrumentation
        mInstrumentationField.set(currentActivityThread,mInstrumentationProxy);
    }catch (Exception e){
   
        e
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值