Here determined: I will strive to graduate from university into BAT


Practice objective: An Activity can be started with an intent without having to register it in an intent.

  • Some articles are called Hook technology. The general content is to listen to the method or call or trigger, during the modification of method parameters or return value to modify app without changing app source code. Such as Xpose plug-ins can prevent QQ undo messages.
  • Today we listen for the activity to start and then make method changes, using dynamic proxies and lots of reflection

Activity Startup process Analysis Chapter 1

First, learn the activity launch process: Suppose we have an onclick method that triggers an activity when a button is clicked

//activity
public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
Copy the code

The next thing we understand is that the interface jumps. I mainly studystartActivityInternal implementation of the call procedure to start oneactivityProcess.

The implementation of this method is inContextImpl.javaThen we look at the inheritance diagram:



The resulting method inheritance relationship does not existContextImpl.java. He was actually wrapped up inContextWrapper.javaIn the

//ContextWrapper.java
public class ContextWrapper extends Context { Context mBase; . Omit other code@Override
    public void startActivity(Intent intent) { mBase.startActivity(intent); }... Omit other code}Copy the code

You can see there’s an mBase property object in there. The startActivity method of mBase is called when startActivity is called. This mBase is contextimpl.java

Now looking at contextimpl. Java we’ll look at the implementation of the startActivity method of this class

//ContextImpl.java
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }
Copy the code

StartActivity (Intent, null); Method, the former method is used for process detection.

 @Override
    public void startActivity(Intent intent, Bundle options) {
      / /... Omit the code and just look at the point of the method
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
Copy the code

. You can see the method call mMainThread getInstrumentation () to obtain an object and then call the object’s methods execStartActivity. MMainThread is declared as final ActivityThread mMainThread.

MMainThread. GetInstrumentation () to check the implementation is as follows

//ActivtyThread.java
 public Instrumentation getInstrumentation(a)
    {
        return mInstrumentation;
    }
Copy the code

So we continue to look at the execStartActivity method of the Instrumentation object

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 / /... Omit methods other methods
 intResult = ActivityManagerNative. GetDefault (.) startActivity (a bunch of parameters);/ /...
        return null;
    }
Copy the code

The above code is passed too many arguments in particular. So use four words instead. We see ActivityManagerNative. GetDefault () to obtain an object and then call startActivity ()

ActivityManagerNative. GetDefault () method is explored

//ActivityManagerNative.java
  static public IActivityManager getDefault(a) {
        return gDefault.get();
    }
Copy the code

GDefault is ActivityManagerNative Java properties object

The statement is as follows:

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create(a) {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager"."default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager"."default service = " + am);
            }
            returnam; }};Copy the code

What is Singleton? This is a singleton utility class. The first time you call this class get() calls back to the abstract method create() that you implement

Complete code for this class:

//Singleton.java
package android.util;
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create(a);

    public final T get(a) {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            returnmInstance; }}}Copy the code

This kind of reflection we later get to IActivityManager you can simply can

Let’s go back a little bit and look at the method that implements the abstraction create()

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create(a) {
            // Get activityMnagerServer from the System Services Manager (this is a system service, so AIDL is required for interprocess communication)
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager"."default service binder = " + b);
            }
           // Convert the IBinder object into the corresponding instantiation interface
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager"."default service = " + am);
            }
            returnam; }};Copy the code

From the example above you can see, see ActivityManagerNative getDefault () returns the remote service objects IActivityManager interface.

Look again at the execStartActivity method of the Instrumentation object

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 / /... Omit methods other methods
 intResult = ActivityManagerNative. GetDefault (.) startActivity (a bunch of parameters);/ /...
        return null;
    }
Copy the code

Here’s the conclusion: Get a remote service object interface IActivityManager (and it’s a singleton) and call the startActivity method of the IActivityManager. The real startActivity interface method of the remote service ActivityManagerServer is called back. It checks to see if the Activity you’re passing in is in the manifest. If your Activity isn’t in the manifest, it’s an exception. But we don’t need to look at the ActivityManagerServer source code, because it’s another app process, so we can’t interfere. If you can interfere, Android will be too unstable. There will be rogue developers who will let you click on one app and interfere with other apps. So we can’t hook into the remote service, but we can use IActivityManager by hand and foot. Because that is the server side there is a client side of the proxy interface object, (this piece needs a simple understanding of the knowledge of AIDL. It doesn’t matter if you don’t understand it, just memorize it and manipulate it)

After looking at the above is very messy, look at the picture below to often train of thought:

(You can right-click to see the larger image)



The reader must ask, right? sinceActivityManagerServerStart theactivityWe can’t interfere. So what’s next?

IActivityManager calls the Intent of the Activity to be launched to ActivityManagerServer. Suppose we use the dynamic proxy before IActivityManager calls the remote service. Replace the Intent with an Activity registered in the manifest file. Of course, now that the substitution is validated, what’s the point if all the activities we actually want to start are replaced? This validates that the system does not actually start the Activity, because the ActivityManagerServer eventually calls back to the ActivityManagerServer. Here we don’t worry, first through the verification again.

The first small goal is to implement the substitution

So let’s say we have three activities the first one is MainActivity, the second placeholder Activity and the third one is SecondActivity

  • MainActivity is registered in the manifest file, so it doesn’t matter which Activity it inherits
  • Placeholder Activity is registered in the manifest file, so it doesn’t matter which Activity you inherit
  • SecondActivity has no manifest file registered and inherits activities. (Don’t inherit or you will fail AppCompatActivity)

Where we click a button on MainActivity to trigger its onclick method to call startActivity to jump to SecondActivity

//MainActivity.java 
    public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
Copy the code

So, when a SecondActivity fails because it is not registered in a manifest file, our intention is to change that intention to start a placeholder activity

Step 1

Create an Application class named app.java (remember to associate it in the manifest) and override the attachBaseContext method

  • AttachBaseContext: this method is called before the onCreate method. Remember ContextWrapper and ContextImpl? Take a look back (Application also inherits ContextWrapper).
public class ContextWrapper extends Context { Context mBase; protected void attachBaseContext(Context base) { if (mBase ! = null) { throw new IllegalStateException("Base context already set"); } mBase = base; }Copy the code

AttachBaseContext is a callback to the mBase object assignment passed in. This is where you can actually use context.

At this point our code looks like this:

//APP.java
public class APP extends Application {
    // The Intent KEY used to store the original target Activity
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base); }}Copy the code

In order to validate an Activity’s manifest file, you must perform an Intent substitution before the IActivityManager calls the startActivity method. So we use the dynamic proxy IActivityManager for IActivityManager directly

To first dynamically delegate you have to get the IActivityManager object. This object is stored in ActivityManagerNative’s property object gDefault. We call the get method on the property object gDefault(Singleton class) or get mInstance on its internal property object

MInstance is used to save an instance of a singleton object. Confusing? Go back to Singleton

//Singleton.java
public abstract class Singleton<T> {
      // Save the object instance
    private T mInstance;
    // If the Singleton object is empty, this method is called to get the instance
    protected abstract T create(a);

    public final T get(a) {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            returnmInstance; }}}Copy the code

So let’s get the gDefault object and see how ActivityManagerNative declares it

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create(a) {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager"."default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager"."default service = " + am);
            }
            returnam; }};Copy the code

We found that it was a static class, so we should laugh, static classes are easy to get object instances through reflection

  • So you get the following code:
public class APP extends Application { private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT"; private static final String TAG = "APP"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { Class<? > ams_class = Class.forName("android.app.ActivityManagerNative"); Field gDefault = ams_class.getDeclaredField("gDefault"); Gdefault.setaccessible (true); // Since gDefault is private, use the emission mechanism to break access restrictions. GDefault_instance = gdefault. get(null); gDefault_instance = gdefault. get(null); // singleton tool class class <? > singleton_class = Class.forName("android.util.Singleton"); // A singleton tool class holds an IActivityManager instance Field mInstance = singleton_class.getDeclaredField("mInstance"); // Break the encapsulation access mInstance. SetAccessible (true); // Pass gDefault to get IActivityManager final Object mInstance_instance = mInstance. Get (gDefault_instance); // Dynamic proxy. This method returns an instance Object that implements the IActivityManager interface // Replace mInstance with the singleton utility class gDefault to implement Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// IActivityManager interface method too many we only care about it to start the activity interface method if (method.getName().equals("startActivity")) {// Get the parameters that call this method. i < args.length; i++) { Object arg = args[i]; if (arg instanceof Intent) { Intent intent = new Intent(); So, whether it is a placeholder, or not, you create a new Intent that replaces the original Intent of the SecondActivity because it is registered in the manifest file. Use the / / and save it to the new Intent in the ComponentName the ComponentName = new the ComponentName (" com. Example. Fmy. Myapplication ", PlaceholdActivity.class.getName()); intent.setComponent(componentName); intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg)); args[i] = intent; return method.invoke(mInstance_instance, args); } } } return method.invoke(mInstance_instance, args); }}); // Replace mInstance of singleton tool class gDefault to implement dynamic proxy mInstance. Set (gDefault_instance, proxyInstance); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

So when you run the code and you find out that no matter what you put in startActivity the intention is to start placeholder Activity you succeed, right

Activity start process analysis Handler source code analysis chapter 2

I’m just going to talk a little bit about it and not overdo it. It then disengage from the main Looper loop, endlessly fetching the mesaage from the corresponding thread MassgaeQueue and sending it to the corresponding handler

The handler will call the following method if it has a message:

    //Handler.java
    public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; }}If mCallback is not empty and returns true, we will not be called to write our own handlerMessage to handle the message.handleMessage(msg); }}Copy the code
  • We’ll replace the Intent again later with this mCallback

We know that when learning Javase we program from the main method to enter, so Android app entry method where? The main method that exists in ActivityThread. Java is

//ActivityThread.java
public static void main(String[] args) {

        / /... The code is omitted
        // Some initialization of the Looper class takes the current thread's Looper from the ThreadLocal object
        // We won't talk too much about ThreadLocal, just copy it with thread resources.
        // Suppose you have a variable a and you can make three copies to different threads in ThreadLocal
        Looper.prepareMainLooper();
        / /... The code is omitted
        ActivityThread thread = new ActivityThread();
         // Start a subthread, also called binder thread, for AMS (ActivityManagerServer communicates with AIDL and sends messages from the subthread to the main thread handler for processing)
        thread.attach(false);

       / /... The code is omitted
           // Start an infinite loop through the messageQueue (where the messager is stored).
        Looper.loop();

       / /... The code is omitted
    }
Copy the code

Look at the thread. The attach (false); methods

//ActivityThread.java
  private void attach(boolean system) {
        sCurrentActivityThread = this;
          / /... Ignore the code
      }
Copy the code

We only see the method that saves the current ActivityThread to the sCurrentActivityThread which we will use later so this is explained here.

We saw in the first chapter that the startActivity method was sent to ActivityManagerServer via IActivityManager, and then we knew that the child thread was started in the ActivityThread class before the main method loops. This thread receives information from ActivityManagerServer, encapsulates it in a Message and adds it to the main thread MeassgeQueue, which passes it to the main thread Handler when the main Loop loops through the Message.

  • When ActivityManagerServer receives the startActivity message, it sends a message to the Handler. Message. what=LAUNCH_ACTIVITY, LAUNCH_ACTIVITY=100

Let’s see where is the Handler in our Activity

public class AcitvityThread{ final H mH = new H(); Private class H extends Handler {//... Code omitted}}Copy the code

How does mH extends Handler handle a private Class H extends Handler event

//ActivityThread.java
class H extends Handler{
/ /...
public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                   // Take the ActivityClientRecord object from the MSG that contains the Intent that starts the activity
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                            // Start the activity with an ActivityClientRecord object
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break; }}}Copy the code

You can get an ActivityClientRecord object from an MSG that contains information such as the Intent to start an Intent. Remember when we saved an Intent to a new Intent? Now you can actually pull it out of this Intent. Call the handleLaunchActivity to start it, and finally you want to explore the lifecycle on your own.

//ActivityThread.java
    static final class ActivityClientRecord {
        IBinder token;
        int ident;
        Intent intent;
        String referrer;
        //....
        }
Copy the code

Let’s clean up the callback process:

So we can assign a value to handler’s mCallback, and then when handler calls back to lancherActivity, we know that mCallback is not null, The mCallback is called and the value it returns is used to determine whether or not to implement the handlerMessage.

So let’s get the handler object that resides in ActivityThread and see what activityThread.java declares for it

//ActivityThread.java
public final class ActivityThread {
 final H mH = new H();
 }
Copy the code

Because only mH is not a static type, you need to get the ActivityThread object before you can get the mH instance. Remember the main method we used to analyze ActivityThread earlier? Internal calls

public static void main(String[] args) {
/ /...
 ActivityThread thread = new ActivityThread();
        thread.attach(false);
    / /...
}
Copy the code

Look at the thread. The attach (false); methods

//ActivityThread.java
private void attach(boolean system) {
        sCurrentActivityThread = this;
        //....
        }
Copy the code

The ActivityThread property object sCurrentActivityThread is saved in the ActivityThread property object

//ActivityThread.java
public final class ActivityThread {
 / /...
  private static ActivityThread sCurrentActivityThread;
/ /...
}
Copy the code

Static property proof you know, you can use reflection to get objects

Therefore, the code synthesis above can be concluded as follows:

//APP.java package com.example.fmy.myapplication; public class APP extends Application { private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT"; private static final String TAG = "APP"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // check code omitted Class<? > activityThread_class = Class.forName("android.app.ActivityThread"); Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThread_field.setAccessible(true); Object activityThread_instance = scurrenTactivityThread_field. get(null); Field mH_field = activityThread_class.getDeclaredField("mH"); mH_field.setAccessible(true); Object mH_insance = mh_field. get(activityThread_instance); Field mCallback_field = Handler.class.getDeclaredField("mCallback"); mCallback_field.setAccessible(true); // Add mCallback McAllback_field. set(mH_insance, new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == 100) { // Get ActivityClientRecord Object obj = msg.obj; Intent_field = obj.getClass().getDeclaredField(" Intent "); intent_field.setAccessible(true); Intent intent = (Intent) intent_field.get(obj); / / take out ahead of us in the Intent was not registered in the manifest file of the Intent of the Activity of Intent target_intent = Intent. GetParcelableExtra (KEY_EXTRA_TARGET_INTENT);  if (target_intent ! = null) { intent.setComponent(target_intent.getComponent()); } } catch (Exception e) { e.printStackTrace(); } } return false; }}); } catch (Exception e) { e.printStackTrace(); } } @Override public void onCreate() { super.onCreate(); }}Copy the code

Finally, integrate the verified code as follows:

//APP.java
package com.example.fmy.myapplication;

public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try{ Class<? > ams_class = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ams_class.getDeclaredField("gDefault");
            // Since gDefault is private, use the emission mechanism to break access restrictions
            gDefault.setAccessible(true);
            // Get the gDefault instance object
            Object gDefault_instance = gDefault.get(null);
            // singleton utility classClass<? > singleton_class = Class.forName("android.util.Singleton");
            // A property object of the singleton utility class holds the IActivityManager object instance
            Field mInstance = singleton_class.getDeclaredField("mInstance");
            // Break encapsulated access
            mInstance.setAccessible(true);
            // Pass the gDefault object instance to get the IActivityManager object instance
            final Object mInstance_instance = mInstance.get(gDefault_instance);
            // Dynamic proxy. This method returns an instance object that implements the IActivityManager interface
            // Replace mInstance with this object for the singleton utility class gDefault
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // IActivityManager has too many interface methods. We only care about the interface method that starts the activity
                    if (method.getName().equals("startActivity")) {
                        // Get the parameters passed in for calling this method. We only need Intent substitution, so we only need Intent substitution
                        for (int i = 0; i < args.length; i++) {
                            Object arg = args[i];
                            if (arg instanceof Intent) {
                                Intent intent = new Intent();
                                So, whether it is a placeholder, or not, you create a new Intent that replaces the original Intent of the SecondActivity because it is registered in the manifest file. Use the back
                                // Save it in a new Intent
                                ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
                                intent.setComponent(componentName);
                                intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
                                args[i] = intent;
                                returnmethod.invoke(mInstance_instance, args); }}}returnmethod.invoke(mInstance_instance, args); }});// Replace mInstance of the singleton utility class gDefault for dynamic proxy
            mInstance.set(gDefault_instance, proxyInstance);

            // The inspection is completeClass<? > activityThread_class = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread_field.setAccessible(true);
            // Get an instance of a static property object
            Object activityThread_instance = sCurrentActivityThread_field.get(null);

            Field mH_field = activityThread_class.getDeclaredField("mH");

            mH_field.setAccessible(true);
            // Get the handler object instance
            Object mH_insance = mH_field.get(activityThread_instance);

            Field mCallback_field = Handler.class.getDeclaredField("mCallback");


            mCallback_field.setAccessible(true);
            // Add mCallback to handler
            mCallback_field.set(mH_insance, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        / / get ActivityClientRecord
                        Object obj = msg.obj;
                        try {
                            // Get the Intent object
                            Field intent_field = obj.getClass().getDeclaredField("intent");
                            intent_field.setAccessible(true);
                            Intent intent = (Intent) intent_field.get(obj);
                            // Retrieve the Intent in which we previously stored the Activity that was not registered in the manifest
                            Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);

                            if(target_intent ! =null) { intent.setComponent(target_intent.getComponent()); }}catch(Exception e) { e.printStackTrace(); }}return false; }}); }catch(Exception e) { e.printStackTrace(); }}@Override
    public void onCreate(a) {
        super.onCreate(); }}Copy the code

GIT source attached with the source link