zoukankan      html  css  js  c++  java
  • androd hook acitivity 启动流程,替换启动的activity(Android Instrumentation)

    前言:如果程序想要知道有activity启动,如果想要拦截activity,然后跳转到指定的activity怎么办?
    

    我们看下ActivityThread 里面:

       private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
    
            ActivityInfo aInfo = r.activityInfo;
            if (r.packageInfo == null) {
                r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                        Context.CONTEXT_INCLUDE_CODE);
            }
    
            ComponentName component = r.intent.getComponent();
            if (component == null) {
                component = r.intent.resolveActivity(
                    mInitialApplication.getPackageManager());
                r.intent.setComponent(component);
            }
    
            if (r.activityInfo.targetActivity != null) {
                component = new ComponentName(r.activityInfo.packageName,
                        r.activityInfo.targetActivity);
            }
    
            ContextImpl appContext = createBaseContextForActivity(r);
            Activity activity = null;
            try {
                java.lang.ClassLoader cl = appContext.getClassLoader();
                activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
                StrictMode.incrementExpectedActivityCount(activity.getClass());
                r.intent.setExtrasClassLoader(cl);
                r.intent.prepareToEnterProcess();
                if (r.state != null) {
                    r.state.setClassLoader(cl);
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(activity, e)) {
                    throw new RuntimeException(
                        "Unable to instantiate activity " + component
                        + ": " + e.toString(), e);
                }
            }
    
    ....
               if (activity != null) {
                    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                    Configuration config = new Configuration(mCompatConfiguration);
                    if (r.overrideConfig != null) {
                        config.updateFrom(r.overrideConfig);
                    }
                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                            + r.activityInfo.name + " with config " + config);
                    Window window = null;
                    if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                        window = r.mPendingRemoveWindow;
                        r.mPendingRemoveWindow = null;
                        r.mPendingRemoveWindowManager = null;
                    }
                    appContext.setOuterContext(activity);
                    activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config,
                            r.referrer, r.voiceInteractor, window, r.configCallback);
    
                    if (customIntent != null) {
                        activity.mIntent = customIntent;
                    }
    
    
    

    可以看到,执行启动activity的时候,

    activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
                StrictMode.incrementExpectedActivityCount(activity.getClass());
    

    那么我们是不是可以在这个时候拦截一下返回的activity呢?

    OK,我们继承Instrumentation,并且重写里面的方法。

    package com.****r.app;
    
    import android.app.Activity;
    import android.app.Instrumentation;
    import android.content.Intent;
    
    import com.*****.ActivityAbout;
    
    /**
     * =======================================================================================
     * 作    者:caoxinyu
     * 创建日期:2019/2/19.
     * 类的作用:
     * 修订历史:
     * =======================================================================================
     */
    public class MyInstrumentation extends Instrumentation {
        private Instrumentation base;
    
        public MyInstrumentation(Instrumentation base) {
            this.base = base;
        }
    
        @Override
        public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            try {
                //这里需要setExtrasClassLoader 不然的话,getParecleable 对象可能会拿不到
                //很多hook Instrumentation的人都不知道。
                // 这里try catch 是防止恶意攻击  导致android.os.BadParcelableException: ClassNotFoundException when unmarshalling
                intent.setExtrasClassLoader(cl);
                intent.getBooleanExtra("a",false);
            }catch (Exception e){
    
            }
            if (intent.getBooleanExtra("ActivityAbout",false)) {
                return super.newActivity(cl, ActivityAbout.class.getName(), intent);
            }
            return super.newActivity(cl,className, intent);
        }
    
    
    }
    

    那么怎么使我们重写的类生效呢?

    package com.***;
    
    import android.app.Instrumentation;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    public class Hooker {
        private static final String TAG = "Hooker";
    
        public static void hookInstrumentation() {
            Class<?> activityThread = null;
            try {
                activityThread = Class.forName("android.app.ActivityThread");
                Method sCurrentActivityThread = activityThread.getDeclaredMethod("currentActivityThread");
                sCurrentActivityThread.setAccessible(true);
                //获取ActivityThread 对象
                Object activityThreadObject = sCurrentActivityThread.invoke(activityThread);
    
                //获取 Instrumentation 对象
                Field mInstrumentation = activityThread.getDeclaredField("mInstrumentation");
                mInstrumentation.setAccessible(true);
                Instrumentation instrumentation = (Instrumentation) mInstrumentation.get(activityThreadObject);
                MyInstrumentation customInstrumentation = new MyInstrumentation(instrumentation);
                //将我们的 customInstrumentation 设置进去
                mInstrumentation.set(activityThreadObject, customInstrumentation);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    

    上面这些代码是通过反射,把自己的Instrumentation 设置进去。

    然后在程序初始化的时候,调用下面的代码即可。

    		Hooker.hookInstrumentation();
    
    

    我们启动一个A activity,如果intent.getBooleanExtra(“ActivityAbout”,false),那么你的A activity 将被拦截成ActivityAbout。

    那么还有一个问题,为什么要设置ClassLoader?

     intent.setExtrasClassLoader(cl);
    

    因为如果不设置的话,getParecleable 对象可能会拿不到。在8.0以前的手机,直接崩溃:android.os.BadParcelableException: ClassNotFoundException when unmarshalling。在8.0以上的话,系统会catch 住这个崩溃,但是你的数据全都会被清空。

    具体分析如下:

    简单try catch,在低版本上没有问题。但是在android8.0以上,会有问题。
    在Android8.0以上,如果getBooleanExt 方法里面失败了,系统会catch BadParcelableException,并把intent 里面的数据清空。具体可见下面的截图,
    在这里插入图片描述
    这就导致简单try catch 之后的代码,运行在8.0以上手机,收不到intent里面的数据,因为Intent 里面的跳转数据被清空了。

    还是要查清楚为什么会出现ClassNotFoundException when unmarshalling

    根据源码,在这里getBooleanExt 会出问题是因为系统在这一步还没有设置解析Parcelable 的classLoader。如下图
    在这里插入图片描述
    所以,有问题的代码需要这样改下。在这里插入图片描述

    系统是在调用了 mInstrumentation.newActivity之后设置了classLoader r.intent.setExtrasClassLoader(cl), 所以hook 在newActivity 这一步get Parcelable 数据是有问题的。

    不然会有下面这种错误:

    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.aaa./.WelcomeActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.nearme.mcs.entity.MessageEntity
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2492)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2678)
            at android.app.ActivityThread.access$900(ActivityThread.java:187)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1523)
            at android.os.Handler.dispatchMessage(Handler.java:111)
            at android.os.Looper.loop(Looper.java:210)
            at android.app.ActivityThread.main(ActivityThread.java:5809)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1113)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:879)
         Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.nearme.mcs.entity.MessageEntity
            at android.os.Parcel.readParcelableCreator(Parcel.java:2305)
            at android.os.Parcel.readParcelable(Parcel.java:2255)
            at android.os.Parcel.readValue(Parcel.java:2162)
            at android.os.Parcel.readArrayMapInternal(Parcel.java:2495)
            at android.os.BaseBundle.unparcel(BaseBundle.java:221)
            at android.os.BaseBundle.getBoolean(BaseBundle.java:658)
            at android.content.Intent.getBooleanExtra(Intent.java:5129)
            at com.nearme.game.sdk.y.o_a(SourceFile:46)
            at com.nearme.game.sdk.y.newActivity(SourceFile:28)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2469)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2678) 
            at android.app.ActivityThread.access$900(ActivityThread.java:187) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1523) 
            at android.os.Handler.dispatchMessage(Handler.java:111) 
            at android.os.Looper.loop(Looper.java:210) 
            at android.app.ActivityThread.main(ActivityThread.java:5809) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at java.lang.reflect.Method.invoke(Method.java:372) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1113) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:879) 
    

    加油,自己学会了使用Source Insight 看源码。开始慢慢的去学习技术的原理。加油!

  • 相关阅读:
    异常部分
    5.Spring Cloud初相识-------Hystrix熔断器
    4.Spring Cloud初相识--------Feign负载均衡
    3.Spring Cloud初相识--------Ribbon客户端负载均衡
    2.Spring Cloud初相识--------Eureka服务注册与消费
    17、SpringBoot------整合dubbo
    3.Hadoop测试Yarn和MapReduce
    2.Hadoop集群安装进阶
    1.Hadoop集群安装部署
    3.Netty的粘包、拆包(二)
  • 原文地址:https://www.cnblogs.com/caoxinyu/p/10568495.html
Copyright © 2011-2022 走看看