插件化入门-最简单的Hook技术,让你的Activity不用注册就可以启动

插件化入门-最简单的Hook技术,让你的Activity不用注册就可以启动

  • 注:插件化是将未安装的apk文件里面的Activity加载至内存,作用是让主应用不用那么庞大,我们只需要下载一系列的插件化包即可实现我们App功能的扩展,而加载未安装的Apk文件最重要的技术就是Hook技术,本文就来看一下Hook技术的原理
  • 实现效果,可以启动一个并不在Androidmanifest文件里面注册的Activity
  • 注:因为各个版本的Activity启动流程有所改变,或者一些类是不一样的,本文中的代码仅保证在api = 27 的系统中正常运行
  • 注:代码我会粘贴在最后

开始

Hook是什么?

  • 所谓Hook就是使用Java反射机制动态替换运行过程中的类对象,达到在应该执行某个方法的时候先去执行我们的方法(方法拦截),也就是动态代理
  • 这个Hook也称钩子,因为相当于我们在系统代码运行的过程中强行勾出一个部分,加入我们自己的代码
  • 而这个钩子用的就是动态代理(因为我们没办法直接获取到Android系统的对象)
  • 这里如果大家对反射不是特别熟悉的话也没关系,我会在代码里面详细标注

Activity的启动流程?

  • 学会插件化的前提是掌握Activity的启动流程,掌握的越深,你的插件化也就更强大,这里我对Activity的启动流程也不是特别清楚,这里只说一下大致的过程,如要详细了解启动过程,还请自行百度相关资料
  • 我们的Activity的启动一般来讲是由系统进程和应用进程通过RPC交互验证信息,在安装apk文件的时候,我们应用本身的AndroidManifest文件会被PackageManagerService(也称包管理器)所解析,他就会知道我们的应用中有多少个组件,并且该组件的对应信息,在我们的应用程序端使用Intent启动一个Activity的时候,就会通过RPC过程去到系统进程(System Server)去验证我们将要启动的这个组件是否被注册等信息,验证成功后再告知我们的应用程序进程去真正的启动一个组件
  • 而插件化的本质就是在不安装apk文件的话,通过我们自己的应用程序去启动另外apk里面的组件,那么,这个时候如果按照正常的过程去走的话,apk里面的组件是没有在我们应用程序的AndroidManifest文件中注册的,这就会导致RPC过程验证失败,进而不能成功启动组件,而我们如果要启动外部apk中的组件就必须让这个RPC过程验证成功,那么这里我们常规的做法就是在这里拿一个假的已注册的activity去系统进程做验证,这个是肯定成功的,在验证完成之后启动的时候,我们又悄悄替换成我们自己真正的Activity
  • 这个就是一般插件化的思想了

上代码

首先在这里说一下Activity启动的过程(api = 27)

  • Activity.startActivity -> Instrumentation.execStartActivity -> ActivityManager.getService().startActivity -> 系统进程验证 -> ActivityThread.Application.scheduleLaunchActivity ->ActivityThread.H.handleMessage(case :LAUNCH_ACTIVITY)
  • 大致的过程就是这个样子,应用程序到系统进程去验证的时候是通过AMS(ActivityManager.getService()调用得到),回到应用程序时,通过ActivityThread.H的消息处理机制去真正的启动Activity
  • 那么可以看到,上面我说的两个东西就是在两个进程通信的最边缘,我们就是在这两个东西这里做做手脚,做做替换

先看ActivityManager.getService()

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }
private static final Singleton IActivityManagerSingleton =
            new Singleton() {
           @Override
           protected IActivityManager create() {
               final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
               final IActivityManager am = IActivityManager.Stub.asInterface(b);
               return am;
           }
       };
public abstract class Singleton {
    private T mInstance;
    protected abstract T create();
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
  • 代码先放在这里,想一下,我们刚才说在从应用程序系统进程去之前做些事情,做些替换,而上面的getService方法是去之前最后一个方法了,所以我们就必须保证在getService这个方法返回的对象调用startActivity方法之前就把我们假的已注册的Activity信息先替换上去,对吧。
  • 不知道大家对动态代理有多少了解,这里的情景就可以用动态代理,我们用动态代理对象去代理getService返回的这个对象的startActivity功能,然后在代理之前,将我们想要做的东西替换上去,那么现在首要问题是先拿到GetService这个方法返回的对象,我们需要代理这个对象
  • 那么来看看我们怎么获取这个对象呢?
  • 看上面的代码,我们需要获取ActivityManager的IActivityManagerSingleton字段的get方法返回的对象,这个返回的对象是IActivityManagerSingleton的mInstance字段,然后再去代理这个mInstance字段.

代理mInstance(ActivityManagerService)字段的代码

		//先获取ActivityManager的class文件
        final Class activityManager = Class.forName("android.app.ActivityManager");
        //通过反射机制获取ActivityManager类的iActivityManagerSingleton字段
        final Field iActivityManagerSingleton = activityManager.getDeclaredField("IActivityManagerSingleton");
		//这里我们设置他的可访问,因为如果这个字段是私有的话,我们下面的代码就不能获取到,会报错
        iActivityManagerSingleton.setAccessible(true);
        //因为iActivityManagerSingleton字段是静态字段,所以我们直接传null就能直接获取到系统运行时的iActivityManagerSingleton 字段
        final Object iActivityManagerSingletonObject = iActivityManagerSingleton.get(null);
//那么到这里我们就成功获取到了正在运行的系统ActivityManager的iActivityManagerSingleton字段

   //接下来这三行代码与上面相同,不在解释
        final Class singleton = Class.forName("android.util.Singleton");
        final Field mInstance = singleton.getDeclaredField("mInstance");
        mInstance.setAccessible(true);
        //这里我们传入的不是null而是上面的iActivityManagerSingletonObject 对象,因为MInstance不是静态的,我们想要获取实时运行类中的字段,就必须知道当前正在运行的类,所以传入iActivityManagerSingletonObject 
        final Object mAms = mInstance.get(iActivityManagerSingletonObject);
//到这里我们就拿到了getService返回的对象了,接下来我们就需要去动态代理这个对象

        //这个构造出我们的代理对象(这个对象是我们自己写的,下面贴代码)这个对象就是用来执行mAms的功能的
        AmsProxy proxy = new AmsProxy(mAms);
        //因为我们下面的Proxy.newProxyInstance这种方式代理的对象必须要求对象继承了接口,并且由上面的代码我们也可以看出他是继承的这个接口IActivityManager,所以我们先拿到接口的class文件
        final Class aClass = Class.forName("android.app.IActivityManager");
        //使用Proxy生成代理对象,第一个参数是类加载器。第二个是对象继承的接口,第三个就是我们的代理了,然后会返回一个对象,这个对象就会在系统运行时被插入到系统里面去执行AMS的startActivity功能
        final Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{aClass}, proxy);
        //这里是将我们构造出来的代理对象扔给系统
        mInstance.set(iActivityManagerSingletonObject, o);
    }
  • 相信上面的注释已经很清楚了,接下来我们看一下动态代理类是怎么写的
static class AmsProxy implements InvocationHandler {
		//这个就是我们在构造器里面传入的需要代理的对象
        private Object mObject;

        public AmsProxy(Object object) {
            mObject = object;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这个方法是在我们代理的这个对象的每一个方法被调用的时候都会执行,所以,我们想要拦截startActivity方法,就必须在这里做参数的判断,如果符合条件,我们才去做替换
            if (args != null) {
                int index = -1;
                for (int i = 0; i < args.length - 1; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                    }
                }
                if (index != -1) {
                //这里我做的是,找到Intent参数,然后将他的ComponentName 参数设置假的Activity,并把真的ComponentName 保存起来,以便我们在另一边恢复
                    Log.d(TAG, "invoke:");
                    final Intent intent = (Intent) args[index];
                    final ComponentName intentComponent = intent.getComponent();
                    //这个ProxyActivity就是我自己创建的负责验证的Activity,这个是在清单文件已经注册了的
                    ComponentName componentProxy = new ComponentName(mContext, ProxyActivity.class);
                    intent.setComponent(componentProxy);
                    intent.putExtra("realComponentName", intentComponent);
                }
            }
            //虽然我们在上面做了那么多的事情,但最终还是得调用原本对象的对应方法去继续执行,我们所做的就是在这个对象执行方法之前做一些我们自己的判断
            return method.invoke(mObject, args);
        }
    }
  • 好了上面就是我们代理AMS的全部代码了,接下来我们要做的就是在系统进程验证完毕之后回到应用程序进程做恢复了,不然启动的就是我们的假Activity了,就不是我们想要的效果了

代理ActivityThread的H的HandleMessage

  • 这里的逻辑就是ApplicationThread通过sendMessage将启动Activity的消息告知ActivityThread的H(handle),所以我们要在他处理消息之前使用我们原本想启动的组件替换掉我们做验证的假的Activity
  • 上关键源代码
//这里是ActivityThread内部自己的静态变量,正是这个静态变量,我们才能获取到ActivityThread的运行实例
private static volatile ActivityThread sCurrentActivityThread;
final H mH = new H();//这个就是处理消息的handler
private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;

		public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
    	}
}
  • 这里我有必要再补充一下Handler消息处理时的代码
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  • 这里我们看到,在调用自身HandleMessage方法处理消息之前,还会先使用mCallback来处理,而handler本身其实是没有设置这个mCallback的,所以这里我们就用来代理这个对象来处理消息
  • 先来看替换mCallback的代码
		//获取到ActivityThread的单例对象
        final Class activityThread = Class.forName("android.app.ActivityThread");
        final Field sCurrentActivityThread = activityThread.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThread.setAccessible(true);
        final Object aT = sCurrentActivityThread.get(null);
        
        //获取ActivityThread内部的mH字段
        final Field mH = activityThread.getDeclaredField("mH");
        mH.setAccessible(true);
        final Object o2 = mH.get(aT);
        final Field mCallback = Handler.class.getDeclaredField("mCallback");
        mCallback.setAccessible(true);
        ActivityThreadHandlerCallbackProxy callbackProxy = new ActivityThreadHandlerCallbackProxy((Handler) o2);
        mCallback.set(o2, callbackProxy);
  • 上面的代码同代理AMS的代码一模一样,这里我就不细说了
  • 要补充的一点是代理AMs的时候使用Proxy.newProxyInstance来创建代理类,而这次我们直接实现了接口去创建代理类,这个是因为IActivityManager接口是我们不能直接访问到的,所以需要使用Proxy.newProxyInstance来创建
  • 看代理类的代码
static class ActivityThreadHandlerCallbackProxy implements Handler.Callback {

        private Handler mHandler;

        public ActivityThreadHandlerCallbackProxy(Handler handler) {
            mHandler = handler;
        }

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == 100) {
                Object obj = msg.obj;
                try {
                    final Field intent = obj.getClass().getDeclaredField("intent");
                    intent.setAccessible(true);
                    final Intent realIntent = (Intent) intent.get(obj);
                    ComponentName componentName = realIntent.getParcelableExtra("realComponentName");
                    if (componentName != null) {
                        realIntent.setComponent(componentName);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mHandler.handleMessage(msg);
            //这个方法我只在这里做一下说明,为什么要在这里返回true呢,因为我们这个callback时候返回值的,如果你注意了前面Handle的HandleMessage方法里面,如果callback处理返回false,那么就还会调用系统的HandleMessage方法,因为我们在前面已经调用了,所以不能再次调用,所以这里返回true,当然你也可以在这里返回false,在前面不调用 mHandler.handleMessage(msg);
            return true;
        }
    }

总结,对hook的思考

  • 这里要说明的一点是,由于Android P 将ActivityThread里面的通信机制改了,所以貌似在新版本上面的hook不能生效,但是下面的hookInstrumentation这个却是可以的
  • 到这里就基本东西已经完了,其实代码并不是唯一,或者我们也可以去代理Instrumentation类,都是可以的,代理Instrumentation类的代码我也会贴在下面,供大家参考
  • 关于代理,我在这里再说一下自己的结论,首先,我们去代理的对象,一定要是在运行过程中会被调用的,再者,他可以通过静态对象或者其他的系统对象以反射的方式访问到
  • 好了,最后贴下代码,如果有什么问题或者需要讨论的,欢迎评论留言
  • 补充,这个方法最好在Application的onCreate方法中调用,调用之后就可以在别的地方启动未注册的Activity了

代码

  • git

你可能感兴趣的:(插件化)