AMS进阶——如何启动没有注册的Activity

背景:

前面我们通过梳理了Activity启动的整个流程知道:Activity的启动主要经历如下几个阶段:

  • 发起Activity启动
  • 与AMS建立通信节点,主要是在Instrumentation类中获取AMS的Binder代理,准备通信。
  • 由应用进程切到系统进程(system_service)的AMS服务,由AMS判断应用进程是否创建,如果没有创建则AMS与Zegote进程通信创建应用进程。
  • 应用进程创建后,启动应用进程,再由应用进程切到AMS服务,AMS进一步做启动Activity的校验(如:Activity是否注册等)。
  • 启动Activity,在ActivityThread的Handler中执行Activity生命周期

 我们都知道正常情况下要启动一个Activity必须要在AndroidManifest文件中注册这个Activity。那么有没有办法不注册也能够正常启动呢?

通过阅读源码,我们知道AMS的工作之一就是服务Activity的启动管理。在这个过程中会对要启动的Activity进行一系列的检验,就比如这里的Activity是否注册就是AMS检验的一环。而要做到不注册就启动就 必须骗过AMS的检验。所以方案就是通过Hook AMS和ActivityThread中的Handler来实现。

 一、Hook AMS

        要想骗过AMS的启动检验,那就必须在AMS的StartActivity检验之前拦截并替换Intent对象中所指向的目标Activity为已经注册的代理Activity。这样AMS在检验的时候检验的就是代理Activity。从而达到骗过AMS检验的目的。

        经过上一节的流程梳理,我们可以通过Hook获取AMS通信节点来达到拦截AMS的startActivity方法,在检验之前替换Intent的目的

 

 

public static void hookAms(){
    try {

        // 获取Singleton对象
        Class<?> clazz = Class.forName("android.app.ActivityManager");
        Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
        singletonField.setAccessible(true);
        Object singleton = singletonField.get(null);

        // 获取IActivityManager 对象
        Class<?> singletonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        final Object mInstance = mInstanceField.get(singleton);


        Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");

        Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{iActivityManagerClass}, new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // IActivityManager 的方法执行的时候,都会先跑这儿
                        Log.d(TAG,"methodName="+method.getName());
                        if ("startActivity".equals(method.getName())) {
                            // 替换Intent
                            int index = 0;

                            for (int i = 0; i < args.length; i++) {
                                if (args[i] instanceof Intent) {
                                    index = i;
                                    break;
                                }
                            }
                            // 启动插件的intent
                            Intent intent = (Intent) args[index];

                            Intent proxyIntent = new Intent();
                            //这里的packageName要填Activity的主工程的包名,也就是应用包名,而不是Activity类的包名
                            proxyIntent.setClassName("com.single.code.app.plugin",
                                    "com.single.code.app.pluginlib.PluginProxyActivity");

                            // 保存原来的
                            proxyIntent.putExtra(TARGET_INTENT, intent);
                            Log.e(TAG, "startActivity: proxyIntent intent ="+proxyIntent);
                            Log.e(TAG, "startActivity: target intent ="+intent);
                            args[index] = proxyIntent;
                        }
                        return method.invoke(mInstance, args);
                    }

                });

        // 替换系统的 IActivityManager对象
        mInstanceField.set(singleton, proxyInstance);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

 二、Hook Handler

        通过Hook AMS来替换Intent达到骗过AMS的Activity启动检验后,我们还需要在Activity真正被启动之前再把Intent替换成真实的目标Activity,否则就会发现启动的就是我们的代理Activity了。同样通过梳理Activity启动流程我们知道,Activity在经过AMS的一系列检验之后最终在ActivityThread的Handler中执行启动并执行生命周期。

所以我们要做的就是在Activity启动执行生命周期之前再次把Intent替换回来即可。而ActivityThread的mH属性就是个完美的Hook点。

public static void hookHandler(){
    try {
        Class<?> clazz = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object activityThread = sCurrentActivityThreadField.get(null);

        Field mHField = clazz.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mH = mHField.get(activityThread);

        // new 一个 Callback 替换系统的 mCallback对象
        Class<?> handlerClass = Class.forName("android.os.Handler");
        Field mCallbackField = handlerClass.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mH, new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                // 将Intent换回来
                Log.e(TAG, "handleMessage:"+msg);
                switch (msg.what) {
                    case 100:
                        try {
                            // 获取ActivityClientRecord中的intent对象
                            Field intentField = msg.obj.getClass().getDeclaredField("intent");
                          
                            intentField.setAccessible(true);
                            Intent proxyIntent = (Intent) intentField.get(msg.obj);

                            // 拿到插件的Intent
                            Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                           if(intent != null){
                               Log.e(TAG, "handleMessage: " + intent);
                               // 替换回来
                               proxyIntent.setComponent(intent.getComponent());
                           }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        break;
                    case 159:
                        try {
                            Class<?> clazz = Class.forName("android.app.servertransaction.ClientTransaction");
                            Field mActivityCallbacksField = clazz.getDeclaredField("mActivityCallbacks");
                            mActivityCallbacksField.setAccessible(true);

                            List activityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
                            for (int i = 0; i < activityCallbacks.size(); i++) {
                                if (activityCallbacks.get(i).getClass().getName()
                                        .equals("android.app.servertransaction.LaunchActivityItem")) {
                                    Object launchActivityItem = activityCallbacks.get(i);
                                    Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");
                                    mIntentField.setAccessible(true);
                                    Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
                                    Log.e(TAG, "handleMessage: proxyIntent intent ="+proxyIntent);
                                    //插件的intent
                                    Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                    Log.e(TAG, "handleMessage: target intent ="+intent);
                                    if (intent != null) {
                                        mIntentField.set(launchActivityItem, intent);
                                    }
                                    break;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        break;
                }
                return false;
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 这里的Hook只针对Android10一下源码。所以Android10及以上需要自行去适配。可能有人疑惑这不是脱裤子放屁吗?你直接去注册不叫好了,干嘛搞这么一套。搞这么一套除了装13之外,还是因为在学习插件化是发现这也是插件化的必备知识之一。

这里贴一下GitHub的Demo:https://github.com/279154451/SAppPlugin

有兴趣的同学可以clone下来跑一下看看

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值