Mode proxy et application Hook pour startActivity

définition

En bref, laissez la classe proxy et la classe cible (classe proxy) implémenter la même interface et laissez la classe proxy contenir un objet de la classe cible, afin que l'accès à la classe proxy puisse être transféré pour accéder à la classe cible. . Nous pouvons apporter certaines améliorations fonctionnelles à la classe proxy sans modifier la classe cible, améliorant ainsi indirectement la fonctionnalité de la classe cible.

Le diagramme UML est le suivant :
Insérer la description de l'image ici
implémentation du code de base :

public interface Subject {
    void request()
}

===================================================================================
public class RealSubject implements Subject {
    @Override
    public void request() {
        // 完成一些事情
    }
}

===================================================================================\
public class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject subject) {
        this.realSubject = subject;
    }

    @Override
    public void request() {
        realSubject.request();
    }
}

Plusieurs méthodes d'application du mode proxy :

  1. Agent distant (AIDL)
    Insérer la description de l'image ici

La méthode add dans le proxy écrit les paramètres dans les données. Grâce à la méthode de transaction de mRemote, les données et la réponse sont envoyées à l'autre extrémité de l'AIDL. À l'autre extrémité de l'AIDL, les paramètres sont extraits des données via la méthode onTransact de la classe Stub et calculée.Après avoir obtenu le résultat, renvoyez le résultat via la fonction de rappel de réponse.

  1. Agent de protection (contrôle d'autorité) :
    Le patron accorde temporairement son pouvoir d'examen des demandes de congés à sa secrétaire pendant qu'il est en déplacement professionnel.

  2. Agent virtuel (espace réservé à l'image),
    par exemple : visualisation d'images WeChat, lorsque le réseau n'est pas bon, utilisez d'abord une petite image floue pour placer l'endroit, puis remplacez la petite image précédente une fois la grande image téléchargée
    Insérer la description de l'image ici
    . (Classe simulée)
    A doit être utilisé La méthode dans B, mais B n'a pas encore été développé, alors B fournira une fausse méthode, renverra la valeur codée en dur, puis la remplacera une fois le développement terminé.

5. Ajouter un journal
Il existe une méthode doSomething dans Class1. Nous voulons enregistrer une ligne de journal avant et après l'exécution de doSomething. Nous pouvons concevoir un Class1Proxy pour exécuter cette fonction, afin qu'elle n'affecte pas Class1.

public interface Class1Interface {
    public void doSomething();
}
public class Class1 implements Class1Interface{
    @Override
    public void doSomething() {
        System.out.println("class1 doSomething");
    }
}
public class Class1Proxy implements Class1Interface{
    Class1 class1 = new Class1();

    @Override
    public void doSomething() {
        System.out.println("Begin log.");
        class1.doSomething();
        System.out.println("End log.");
    }
}

Ensuite, nous pouvons utiliser Class1Proxy pour remplacer Class1

Class1Proxy proxy = new Class1Proxy();
proxy.doSomething();

La méthode d'implémentation ci - dessus est le mode proxy statique , et son inconvénient est que

  1. Proxy statique Si une nouvelle méthode est ajoutée à l'interface, en plus de toutes les classes d'implémentation (classes de sujets réels) devant implémenter cette méthode, toutes les classes proxy doivent également implémenter cette méthode. Augmente la complexité de la maintenance du code.
  2. L'objet proxy ne sert qu'un seul type d'objet, si vous souhaitez servir plusieurs types d'objets. Des proxys doivent être fournis pour chaque type d'objet. Les proxys statiques ne sont pas capables lorsque la taille du programme est légèrement plus grande.

Par exemple, si nous devons imprimer des journaux avant l'exécution de doSomething dans Class2, nous devons implémenter une autre classe Class2Proxy, et le nombre de classes Proxy sera important.

proxy dynamique

Le proxy statique dont nous avons parlé ci-dessus possède déjà le fichier de classe de la classe proxy avant l'exécution du code ; tandis que le proxy dynamique génère dynamiquement l'objet de classe proxy en le lançant lorsque le code est en cours d'exécution et détermine qui proxy.
C'est-à-dire que nous ne savons pas qui représenter pendant la phase de codage, nous déciderons qui représenter lors de l'exécution du code.
Java fournit des méthodes Proxyde classe newProxyInstancepour générer des objets proxy et fournit également des interfaces proxy dynamiques InvocationHandlerpour relayer les méthodes qui nécessitent une implémentation proxy.
Jetons un coup d'œil à newProxyInstancela déclaration de la méthode :

static Object newProxyInstance(
	ClassLoader loader,
	Class<?>[] interfaces,
	InvocationHandler h)

Description du paramètre :

  • Le chargeur est défini sur le classLoader correspondant à l'objet cible (objet proxy)
  • interfaces est défini sur le type d'interface implémenté par l'objet cible (objet proxy)
  • h est défini sur un objet de classe qui implémente l'interface InvocationHandler, et nous injectons l'objet cible via son constructeur.
 Class1Interface class1 = new Class1();
 Class1Interface class1Proxy = (Class1Interface) Proxy.newProxyInstance(class1.getClass().getClassLoader(), class1.getClass().getInterfaces(), new InvocationHandlerForTest(class1));
 class1Proxy.doSomething();

================================================================================
public class InvocationHandlerForTest implements InvocationHandler {
    private Object realSubject; // 真实的主题对象

    public InvocationHandlerForTest(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志开始");
        Object result = method.invoke(realSubject,args);
        System.out.println("日志结束");
        return result;
    }
}

L'objet créé via la méthode Proxy.newProxyInstance est un objet qui implémente l'interface Class1Interface. De cette façon, nous avons implémenté l'objet proxy de Class1. Si nous avons maintenant besoin de proxy Class2, alors nous pouvons directement :

Class2Interface class2 = new Class2();
Class2Interface class2Proxy = (Class2Interface) Proxy.newProxyInstance(class2.getClass().getClassLoader(), class2.getClass().getInterfaces(), new InvocationHandlerForTest(class2));
class2Proxy.work();

Notez que dans la ligne de code method.invoke(target, objects), target est l'objet class1 et les objets sont les paramètres requis par la méthode doSomthing de class1. Ensuite, l'appel de la méthode doSomething de class1Proxy appellera la méthode doSomething de class1. .

Avantages des proxys dynamiques
  1. Toutes les fonctions proxy peuvent être complétées via une classe proxy, et toutes les méthodes déclarées dans l'interface sont transférées vers une méthode centralisée du processeur d'appel (InvocationHandler.invoke). Lorsqu'il existe un grand nombre de méthodes d'interface, nous pouvons les gérer de manière flexible sans avoir à transférer chaque méthode comme un proxy statique.
  2. L'application du proxy dynamique rend nos responsabilités de classe plus uniques et plus réutilisables.
Inconvénients des proxys dynamiques

Les classes ne peuvent pas être proxy, seules les interfaces peuvent être proxy. Si notre classe n'implémente aucune interface, alors nous ne pouvons pas utiliser cette méthode pour le proxy dynamique (car la classe $Proxy() intègre Proxy et l'intégration Java n'est pas autorisée. plusieurs classes parentes ).

Nous pouvons utiliser l'objet proxy généré par la méthode proxy dynamique Proxy.newProxyInstance pour remplacer l'objet d'origine. Cette technologie est appelée technologie Hook dans le plug-in.

Application de crochet

Nous supposons qu'une SecondActivity est démarrée dans MainActivity, mais que la ThirdActivity est finalement démarrée.
La première chose que nous devons savoir est le processus de base d’exécution de startActivity dans Activity.

Deux formes de méthode startActivity :

  1. Utilisez le startActivity fourni avec Activity
Intent intent= new Intent(MainActivity.this ,,SecondActivity.class) ;
startActivity(intent);
  1. Utilisez la méthode startActivity de Context
Intent intent= new Intent(MainActivity.this ,,SecondActivity.class) ;
startActivity(intent);

Les deux méthodes ont le même objectif, elles seront exécutées dans la Activityméthode startActivityForResult, et finalement mInstrumentation.execStartActivityappelées par, execStartActivitypuis transmises à la méthode ActivityTaskManager.getService().startActivitypour traitement.
Voici la première moitié de startActivity :

MainActivity informe ATMS de démarrer SecondActivity

ATMS reçoit la demande de MainActivity, puis mService.getLifecycleManager().scheduleTransactionretourne au processus APP via la méthode, envoie ActivityThread.H.EXECUTE_TRANSACTIONle message, exécute la ActivityThreadméthode handleLaunchActivity()et exécute enfin la méthode appelée dans cette méthode pour terminer le démarrage. Voici la seconde moitié de startActivity :ActivityThreadmInstrumentation.callActivityOnCreate();SecondActivityonCreate()

ATMS informe le processus APP de démarrer SecondActivity

Les endroits où nous pouvons accrocher en première mi-temps comprennent :

  • Activityméthode startActivityForResult;
  • Activitychamps mInstrumentation;
  • ActivityTaskManager.getService()L'objet obtenu par la méthode

Les endroits qui peuvent être accrochés en seconde période comprennent :

  • Objet mInstrumentation d'ActivityThread, méthode newActivity et méthode callActivityOnCreate correspondantes.
Remplacer le startActivityForResult de l'activité

Créez une classe de base Activity-BaseActivity pour toutes les activités de l'application et remplacez la méthode startActivityForResult dans BaseActivity.

Accrochez le champ d'activité mInstrumentation

Il y a un champ mInstrumentation dans Activity. La méthode startActivityForResult de l'activité appellera la méthode executeStartActivity de mInstrumentation :

# Activity.java
private Instrumentation mInstrumentation;

public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if(this.mParent == null) {
        ActivityResult ar = this.mInstrumentation.execStartActivity(this, this.mMainThread.getApplicationThread(), this.mToken, this, intent, requestCode, options);
        if(ar != null) {
            this.mMainThread.sendActivityResult(this.mToken, this.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
        }

        if(requestCode >= 0) {
            this.mStartedActivity = true;
        }
    } else if(options != null) {
        this.mParent.startActivityFromChild(this, intent, requestCode, options);
    } else {
        this.mParent.startActivityFromChild(this, intent, requestCode);
    }

}

Nous obtenons d’abord cette variable privée par réflexion.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 获取Activity中的Instrumentation对象,私有变量,通过反射获取
        Instrumentation instrumentation = (Instrumentation) RefInvoke.getFieldObject(Activity.class,this,"mInstrumentation");
        // 创建Instrumentation代理对象
        InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation);
        // 替换掉MainActivity中的mInstrumentation对象为代理对象
        RefInvoke.setFieldObject(Activity.class,this,"mInstrumentation",instrumentationProxy);

        setContentView(R.layout.activity_main);
        findViewById(R.id.turn_to_other_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}
public class InstrumentationProxy extends Instrumentation {
    private static final String TAG = InstrumentationProxy.class.getSimpleName();
    private Instrumentation mInstrumentation;

    public InstrumentationProxy(Instrumentation mInstrumentation) {
        this.mInstrumentation = mInstrumentation;
    }

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
                                            Intent intent, int requestCode, Bundle options){
        Log.d(TAG, "XXX到此一游!");
        ComponentName componentName = intent.getComponent();
        String className = componentName.getClassName();
        // 判断目标activity是不是SecondActivity,替换SecondActivity为ThirdActivity
        if(className.equals("com.chinatsp.hookstartactivity.SecondActivity")){
            intent.setClassName(who,ThirdActivity.class.getCanonicalName());
        }
      
        // 由于这个方法是隐藏的,因此需要使用反射调用;
        Class[] p1 = {Context.class, IBinder.class,
                IBinder.class, Activity.class,
                Intent.class, int.class, Bundle.class};
        Object[] v1 = {who, contextThread, token, target,
                intent, requestCode, options};
        return (ActivityResult) RefInvoke.invokeInstanceMethod(
                mInstrumentation, "execStartActivity", p1, v1);
    }
}

De cette façon, nous avons réussi à accrocher la méthode startActivity. Comme mInstrumentation est un objet, le mode proxy statique est utilisé ici.

Accrochez le getService d'ATMS

InstrumentaionLa execStartActivityméthode finira par aboutir à ActivityTaskManager.getService().startActivityla méthode :

# Instrumentation.java

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
   ...

    try {
		// 流程就从Launcher进程进入到AMS所在的SystemServer进程了
        int result = ActivityTaskManager.getService().startActivity(whoThread,
                who.getBasePackageName(), who.getAttributionTag(), intent,
                intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
       ...
    }
    return null;
}

//=========ActivityTaskManager.java=========
public static IActivityTaskManager getService() {
    return IActivityTaskManagerSingleton.get();
}

@UnsupportedAppUsage(trackingBug = 129726065)
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton = new Singleton<IActivityTaskManager>() {
    @Override
    protected IActivityTaskManager create() {
        final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
        return IActivityTaskManager.Stub.asInterface(b);
    }
};


//=================Singleton.java=========
        /**
         * Singleton helper class for lazily initialization.
         * ......
         */
        public abstract class Singleton<T> {
            private T mInstance;

            protected abstract T create();

            public final T get() {
                synchronized (this) {
                    if (mInstance == null) {
                        mInstance = create();
                    }
                    return mInstance;
                }
            }
        }

La méthode getService d'ATMS renvoie un type IActivityTaskManager, qui est une interface. Nous pouvons utiliser la méthode Proxy.newProxyInstance pour accrocher cet objet de type interface IActivityTaskManager à un objet généré par notre classe proxy personnalisée AMSProxy.class :

public class AMSHookHelper {
    private static final String TAG = AMSHookHelper.class.getSimpleName();

    public static void hookAMS() throws ClassNotFoundException,ClassCastException{
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityTaskManagerSingleton");
        }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            activityManagerInterface = Class.forName("android.app.IActivityTaskManager");
        }else {
            activityManagerInterface = Class.forName("android.app.IActivityManager");
        }

        // getInterfaces()方法和Java的反射机制有关。它能够获得这个对象所实现的接口。activityManager 本身就是activityManagerInterface 的Class
        Object activityManagerProxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject,"mInstance",activityManagerProxy);
        Log.d(TAG, "hook activity manager success");

    }
}

public class AMSProxy implements InvocationHandler {
    private static final String TAG = AMSProxy.class.getSimpleName();

    private Object mAMSProxy;

    public AMSProxy(Object mAMSProxy) {
        this.mAMSProxy = mAMSProxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.d(TAG,"hey, baby,you are hooked!!");
        Log.d(TAG,"method:"+method.getName()+" called with args:" + Arrays.toString(args));
        if("startActivity".equals(method.getName())){
            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(intent);
            // 实际跳转的页面Activity
            proxyIntent.setClassName("com.chinatsp.hookstartactivityamn", ThirdActivity.class.getCanonicalName());
            args[index] = proxyIntent;

            return method.invoke(mAMSProxy,args);
        }

        return method.invoke(mAMSProxy,args);
    }
}

Exécutez enfin la méthode dans la MainActivityméthode :attachBaseContextAMSHookHelper.hookAMS

public class MainActivity extends AppCompatActivity {

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        try {
           AMSHookHelper.hookAMN();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.turn_to_other_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

Accrochez le champ Instrumentation d'ActivityThread

À l'intérieur de l'activité, il y a un champ mInstrumentation et nous implémentons Hook en le remplaçant par une classe InstrumentationProxy personnalisée.

Il existe également un champ mInstrumentation dans ActivityThread. ActivityThread appellera la méthode newActivity de mInstrumentation pour générer un objet Activity, puis appellera la méthode callActivityOnCreate de mInstrumentation pour démarrer l'activité. Par conséquent, nous pouvons également remplacer cette mInstrumentation par un objet proxy personnalisé pour implémenter Hook.

public class InstrumentationInActivityThreadHookHelper {
    private static final String TAG = InstrumentationInActivityHookHelper.class.getSimpleName();
    public static void hookInstrumentationInActivity() throws ClassNotFoundException{
        // 先获取到当前的ActivityThread
        Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread");
        // 拿到原始的Instrumentation字段
        Instrumentation instrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread,"mInstrumentation");
        // 创建代理对象
        InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation);
        // 替换ActivityThread中的instrumentation为代理对象
        RefInvoke.setFieldObject(currentActivityThread,"mInstrumentation",instrumentationProxy);
    }
}
public class InstrumentationProxy extends Instrumentation {
    private static final String TAG = InstrumentationProxy.class.getSimpleName();
    // ActivityThread中原始的Instrumentation对象, 保存起来
    private Instrumentation mBase;

    public InstrumentationProxy(Instrumentation mBase) {
        this.mBase = mBase;
    }

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        // 原来的intent
        ComponentName componentName = intent.getComponent();
        Log.d(TAG, "到此一游!cl:"+cl+" ,className="+className
                + " ,component_packageName="+componentName.getPackageName()
                + " ,component_className="+componentName.getClassName());

        if("com.chinatsp.hookstartactivityamn.SecondActivity".equals(componentName.getClassName())){
            // 如果是跳转到SecondActivity,就替换为ThirdActivity
            className = ThirdActivity.class.getCanonicalName();
            intent.setComponent(new ComponentName(componentName.getPackageName(),"com.chinatsp.hookstartactivityamn.ThirdActivity"));
        }
        return mBase.newActivity(cl, className, intent);
    }

    public void callActivityOnCreate(Activity activity, Bundle bundle) {
        Log.d(TAG, "到此一游! bundle = " + bundle);

//        Class[] p1 = {Activity.class, Bundle.class};
//        Object[] v1 = {activity, bundle};
//        RefInvoke.invokeInstanceMethod(
//                mBase, "callActivityOnCreate", p1, v1);
        mBase.callActivityOnCreate(activity,bundle);
    }
}

callActivityOnCreateLa méthode n’a pas changé, le principal changement est dans newActivityla méthode.

Démarrer une activité non déclarée dans AndroidManifest

Rappelez-vous le diagramme de séquence de démarrage de l'activité.
Insérer la description de l'image ici
Dans le processus normal, si l'APP démarre une activité qui n'est pas déclarée dans AndroidManifest, AMS lèvera une exception Activity Not Found.
La vérification par AMS de savoir si l'activité est déclarée dans le fichier AndroidManifest est effectuée dans la deuxième étape. L'idée de base :

  1. Dans un premier temps, avant d'envoyer les informations d'activité à démarrer, remplacez cette activité par une StubActivity déclarée dans le fichier AndroidManifest, afin de contourner la vérification AMS. Pendant le processus de remplacement, les informations d'activité d'origine doivent être stockées dans le Bundle.
  2. Dans la cinquième étape, lorsque AMS demande à l'APP de démarrer StubActivity, nous remplaçons StubActivity par l'activité d'origine. Les informations d'activité originales sont stockées dans le bundle et peuvent être supprimées.

L'implémentation du code de la première étape est en fait constituée de plusieurs méthodes dans la première moitié du processus Hook startActivity de la section précédente. Ici, nous choisissons la méthode HookATMS :

public class AMSHookHelper {
    private static final String TAG = AMSHookHelper.class.getSimpleName();
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";

    /**
     * Hook AMS
     * 主要完成的操作是: 把真正要启动的Activity临时替换为在AndroidManifest.xml中声明好的Activity,进而骗过AMS
     */
    public static void hookAMN() throws ClassNotFoundException,NoSuchMethodException{
        // 获取AMN的gDefault对象实例
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityTaskManagerSingleton");
        }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            activityManagerInterface = Class.forName("android.app.IActivityTaskManager");
        }else {
            activityManagerInterface = Class.forName("android.app.IActivityManager");
        }

        Object proxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject, "mInstance",proxy);
    }
}

public class AMSProxy implements InvocationHandler {
    private static final String TAG = AMSProxy.class.getSimpleName();
    private Object mActivityManager;

    public AMSProxy(Object mActivityManager) {
        this.mActivityManager = mActivityManager;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.d(TAG,method.getName());
        if("startActivity".equals(method.getName())){
            // 只拦截这个方法
            // 替换参数
            Intent raw;
            int index = 0;
            for(int i = 0;i<args.length;i++){
                if(args[i] instanceof Intent){
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];
            Intent newIntent = new Intent();
            // 替身Activity的包名
            String stubPackage = raw.getComponent().getPackageName();
            // 把启动的Activity替换为StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT,raw);
            // 替换掉intent
            args[index] = newIntent;
            Log.d(TAG,"hook success");
            return method.invoke(mActivityManager,args);
        }
        return method.invoke(mActivityManager,args);
    }
}

La tâche principale d'AMSProxy consiste à intercepter la méthode startActivity, à supprimer l'intention d'origine des paramètres, à la remplacer par le newIntent qui démarre StubActivity et à enregistrer l'intention d'origine dans newIntent, qui sera utilisée ultérieurement pour remplacer StubActivity par TargetActivity.

La deuxième étape du code d'implémentation est la seconde moitié du processus Hook startActivity. Ici, sélectionnez l'objet mInstrumentation de Hook ActivityThread :

public class AMSHookHelper {
    private static final String TAG = AMSHookHelper.class.getSimpleName();
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
	...
    public static void hookInstrumentation(){
        // 先获取到当前的ActivityThread
        Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread");
        // 拿到原始的Instrumentation字段
        Instrumentation instrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread,"mInstrumentation");
        // 创建代理对象
        InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation);
        // 替换ActivityThread中的instrumentation为代理对象
        RefInvoke.setFieldObject(currentActivityThread,"mInstrumentation",instrumentationProxy);
    }
}
public class InstrumentationProxy extends Instrumentation {
    private static final String TAG = InstrumentationProxy.class.getSimpleName();
    // ActivityThread中原始的Instrumentation对象, 保存起来
    private Instrumentation mBase;

    public InstrumentationProxy(Instrumentation mBase) {
        this.mBase = mBase;
    }

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        // 原来的intent
        ComponentName componentName = intent.getComponent();
        Log.d(TAG, "到此一游!cl:"+cl+" ,className="+className
                + " ,component_packageName="+componentName.getPackageName()
                + " ,component_className="+componentName.getClassName());
        // 获取存入的targetIntent,也就是本来要启动的intent
        Intent targetIntent = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        // 为空则说明要启动的是一个在AndroidManifest中声明过的Activity,走正常流程
        if(targetIntent==null){
            return mBase.newActivity(cl,className,intent);
        }
       String newClassName = targetIntent.getComponent().getClassName();
        return mBase.newActivity(cl, newClassName, targetIntent);
    }

    public void callActivityOnCreate(Activity activity, Bundle bundle) {
        Log.d(TAG, "到此一游! bundle = " + bundle);

//        Class[] p1 = {Activity.class, Bundle.class};
//        Object[] v1 = {activity, bundle};
//        RefInvoke.invokeInstanceMethod(
//                mBase, "callActivityOnCreate", p1, v1);
        mBase.callActivityOnCreate(activity,bundle);
    }

}

InstrumentationProxyLa tâche principale consiste à intercepter newActivityla méthode, car callActivityOnCreateil n'y a pas de paramètre Intent dans la méthode, à supprimer l'intention d'origine du paramètre Intent et à remplacer les paramètres className et Intent par les TargetActivityobjets className et Intent que nous voulions initialement démarrer.

Enfin, exécutez la somme dans la méthode MainActivitydansattachBaseContextAMSHookHelper.hookAMN();AMSHookHelper.hookInstrumentation();

public class MainActivity extends AppCompatActivity {
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        try{
            AMSHookHelper.hookAMN();
            AMSHookHelper.hookInstrumentation();
        }catch (ClassNotFoundException | NoSuchMethodException e){
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.turn_to_other_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

Code source

Guess you like

Origin blog.csdn.net/jxq1994/article/details/130765093