The original link: www.woaitqs.cc/android/201…

Introduction to Java Reflection

The JVM provides the ability to run bytecodes on the fly. In addition to linking and loading bytecodes, the JVM provides the ability to run bytecodes on the fly. Reflection also gives us the ability to modify dynamically. Reflection allows us to view the interface, method, and field information without knowing the class or method name at run time. On the other hand, we can dynamically invoke methods, create new objects, and even tamper with field values.

Now that we’ve introduced what reflection is, how does it work in the real world? The Android system is designed to use Java permission-related things (private, package, etc., and the @hide annotation) to prevent us from accessing certain fields or methods for security and architectural reasons. In real development, however, these hidden fields or methods can provide much desired features. In this paradoxical situation, reflection serves our needs, like a key to unlock a hidden level.

In summary, reflection provides a mechanism for dynamic interaction with Class files. For example, in the entry function below, you can see all the methods in HashMapClass.

public class HashMapClass extends HashMap {

  public static void main(String[] args) {
    Method[] methods = HashMapClass.class.getMethods();

    for (Method method : methods) {
      System.out.println("method name is "+ method.getName()); }}}Copy the code

Class Class introduction

In the following reflection tutorial, you should first learn about Class Objects. All types in Java, including basic types like int and float, have Class objects associated with them. If the corresponding Class name is known, Class.forname () can be used to construct the corresponding Class object. If there is no corresponding Class, or if it is not loaded, a ClassNotFoundException object will be thrown.

Class encapsulates the information contained in a Class. The main interfaces are as follows:

    try {
      Class mClass = Class.forName("java.lang.Object");

      // A name that does not contain the package name prefix
      String simpleName = mClass.getSimpleName();

      // Type modifiers, private, protect, static etc.
      int modifiers = mClass.getModifiers();
      // Modifier provides some static methods for reading types.
      Modifier.isPrivate(modifiers);

      // Information about the parent class
      Class superclass = mClass.getSuperclass();

      // constructor
      Constructor[] constructors = mClass.getConstructors();

      // Field type
      Field[] fields = mClass.getFields();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }Copy the code

Common reflection methods

Here are some common scenarios for reflection, starting with the Student class.

public class Student {

  private final String name;
private int grade = 1;

  public Student(String name) {
    this.name = name;
  }

  public String getName(a) {
    return name;
  }

  private int getGrade(a) {
    return grade;
  }

  private void goToSchool(a) {
    System.out.println(name + " go to school!"); }}Copy the code

1) Reflection builds Student object

try {
  Class studentClass = Student.class;

  // A constructor of type String
  Constructor constructor = studentClass.getConstructor(new Class[]{String.class});

  Instantiate the student object
  Student student = (Student)constructor.newInstance("Li Lei");
  System.out.print(student.getName());
} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}Copy the code

2) Reflection modifies private variables

try {
  Student student = new Student("Han MeiMei");
  System.out.println("origin grade is " + student.getGrade());

  Class studentClass = Student.class;
  GetField: getDeclaredField: getDeclaredField
  Field gradeField = studentClass.getDeclaredField("grade");

  // If you have private or package permissions, be sure to grant access to them
  gradeField.setAccessible(true);

  // Change the Grade field in the student object
  gradeField.set(student, 2);
  System.out.println("after reflection grade is " + student.getGrade());

} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}Copy the code

3) Reflection calls private methods

try {
  Student student = new Student("Han MeiMei");

  // Get private methods, also notice the difference between getMethod and getDeclaredMethod
  Method goMethod = Student.class.getDeclaredMethod("goToSchool".null);
  // Grant access permission
  goMethod.setAccessible(true);

  // Call goToSchool.
  goMethod.invoke(student, null);

} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}Copy the code

Android reflection example application

Now let’s take a look at some examples of how Java’s reflection features can be used to achieve some awesome functionality. Imagine that we want to start an unregistered Activity as a plug-in. One of the issues involved is how to give these plug-ins an Activity life cycle. This example is a manual notification of the Activity lifecycle via reflection.

1) Understand and familiarize yourself with code details

To implement the above functionality, the first step is to understand how the Activity life cycle works, and to understand the code details. Because the objects that reflection operates on are concrete Class objects, it is impossible to speak of reflection without knowing the source details.

Space is limited, the specific principle is more complex, here is a link to the Android plug-in principle analysis – Activity lifecycle management, interested students can view, in this general explanation.

The Activity lifecycle is closely related to ActivityThread, so let’s take a look at how the Activity starts. When an Activity needs to be started, ActivityManagerService sends a message to the ActivityThread through its Binder mechanism, which executes the scheduleLaunchActivity method after a chain call. Let’s look at the internal implementation.

// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
        int procState, Bundle state, PersistableBundle persistentState,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

    updateProcessState(procState, false);

    ActivityClientRecord r = new ActivityClientRecord();

    r.token = token;
    r.ident = ident;
    r.intent = intent;
    r.referrer = referrer;
    r.voiceInteractor = voiceInteractor;
    r.activityInfo = info;
    r.compatInfo = compatInfo;
    r.state = state;
    r.persistentState = persistentState;

    r.pendingResults = pendingResults;
    r.pendingIntents = pendingNewIntents;

    r.startsNotResumed = notResumed;
    r.isForward = isForward;

    r.profilerInfo = profilerInfo;

    r.overrideConfig = overrideConfig;
    updatePendingConfiguration(curConfig);

    sendMessage(H.LAUNCH_ACTIVITY, r);
}Copy the code

Note the final sendMessage method, which uses a handler mechanism to communicate internally. In this article, the Android application process startup process mentioned that when the application process starts, the main method of ActivityThread is called and the corresponding message loop is initialized in this method. Subsequent messaging on the main thread is initialized internally through the H in ActivityThread, where H is a possible breakthrough.

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    public static final int SEND_RESULT             = 108;
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int NEW_INTENT              = 112;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;

    / /...

    public void handleMessage(Message msg){
        // ...}}Copy the code

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler The Handler internally maintains a callback object through which to send messages when they occur.

/** * Handle system messages here. */
public void dispatchMessage(Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code

If the callback variable can be replaced, the message can be passed to the replaced callback. Does this serve our purpose?

2) Verify whether reflection conditions are met

If we reflect an object that has multiple instances, then we need to process it in different places, which obviously adds an extra layer of implementation complexity, so we try to do reflection on singletons or static instances, and the code complexity increases quite a bit.

As mentioned above, we can replace callback to verify whether this is feasible. H is stored in the ActivityThread class, and the thread that ActivityThread runs on is the main thread. We know that every application has a main thread, so there is only one ActivityThread, which ensures that H is unique.

On the other hand, ActivityThread maintains the currentActivityThread variable internally. Although it cannot be accessed directly due to API access restrictions, it can also be retrieved by reflection.

So far, it has been proved that this approach can work in theory.

3) Implement code details and code injection where appropriate

First, we define our own callback object, which acts as the proxy of callback in H. Here we need to pay attention to the same definition of MSG as the bottom layer, and the code is as follows:

public class LaunchCallback implements Handler.Callback {

  public static final int LAUNCH_ACTIVITY = 100;

  private Handler.Callback originCallback;

  public LaunchCallback(Handler.Callback originCallback) {
    this.originCallback = originCallback;
  }

  @Override
  public boolean handleMessage(Message msg) {

    if (msg.what == LAUNCH_ACTIVITY) {
      Toast.makeText(
          VApp.getApp().getApplicationContext(),
          "activity is going to launch! ", Toast.LENGTH_SHORT).show();
    }

    returnoriginCallback.handleMessage(msg); }}Copy the code

Replace the running callback with a custom callback using the previously mentioned reflection method as follows:

public class InjectTool {

  public static void dexInject(a) {
    try {

      // Use reflection to call the static method of ActivityThread to obtain the currentActivityThreadClass<? > activityThreadClass = Class.forName("android.app.ActivityThread");
      Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
      currentActivityThreadMethod.setAccessible(true);
      Object currentActivityThread = currentActivityThreadMethod.invoke(null);

      // Get the mH in the currentActivityThread example
      Field handlerField = activityThreadClass.getDeclaredField("mH");
      handlerField.setAccessible(true);
      Handler handler = (Handler) handlerField.get(currentActivityThread);

      // Modify the callback field in mH
      Field callbackField = Handler.class.getDeclaredField("mCallback");
      callbackField.setAccessible(true);
      Handler.Callback callback = (Handler.Callback) callbackField.get(handler);

      callbackField.set(handler, new LaunchCallback(callback));
    } catch(IllegalArgumentException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) { e.printStackTrace(); }}}Copy the code

Inject into the Application to complete the implementation of the changes

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

The actual running effect is shown in the figure below:

The same can be done for other activity lifecycles, which are not covered here.

4) Reflection uses summary

When implementing reflection, the first thing to do is read the source code carefully, sort out the context, and then look for the breakthrough points, which are usually static methods or singletons, and finally code implementation.