Definition and composition of reflection

JAVA reflection is a runtime mechanism that allows you to know all the properties and methods of any given class. For any object, you can call any of its methods and properties; The ability to dynamically retrieve information and call object methods is called the Java language’s reflection mechanism. The function of reflection is to dynamically interact with classes, such as fetching hidden properties, modifying properties, retrieving objects, creating objects or methods.

Reflection is a mechanism that has the ability to interact dynamically with classes so why emphasize dynamic interaction? Because usually are dynamically loaded, namely at run time to load, rather than at compile time, when you need to get loaded, or you can load at any time a nonexistent classes into memory, and then do all kinds of interaction, or get all the information of a class that is not publicly, in other words, Developers can take advantage of reflection to do special things dynamically at any time and at will.

Composition of reflection

Since reflection must eventually involve classes, reflection generally consists of the following aspects:

1. Java.lang.class. Java: Class object;

2. Java. Lang. Reflect. Constructor. Java: the Constructor of a class object;

3. Java. Lang. Reflect. Method. Java: class Method of object;

4. Java. Lang. Reflect. Field. Java: attributes of a class object;

The following picture illustrates the relationship:

According to the working principle of the virtual machine, under normal circumstances, the class takes: load – > validate – > prepare initialization – > analysis – > – > – > uninstall this process, if you need the reflection of the class does not in memory, then the first after loading the process, and in generating a class object in the memory, have the class object of reference, You can use your developer’s imagination and do what you want.

Function of reflection

Reflection is a mechanism that has the ability to interact dynamically with Java classes. In Java and Android development, reflection is commonly used in the following scenarios.

● Need to access hidden properties or call methods to change the original logic of the program, this is very common in development, for some reason, the system does not open some interface, this time using reflection is an effective solution

● Custom annotations, which are retrieved at run time using reflection.

● In the development of dynamic loading classes, such as dynamic loading in Android to solve 65K problems, modular and plug-in are inseparable from reflection, without reflection at a step.

How reflection works

As we know, each Java file is eventually compiled into a. Class file. These class objects hold all the information about the class, including its parent classes, interfaces, constructors, methods, properties, etc. These class files are loaded by the ClassLoader into the VIRTUAL machine when the program is run. When a Class is loaded, the Java virtual machine automatically generates a Class object in memory. When we use new to create an object, it is essentially the same, but the underlying principles are transparent to us. Given Method,Field, and Constructor information, Java references to objects have everything; the rest is up to the developer’s imagination.

A simple example of reflection

So much for the theory, let’s put it into practice

public class Student { private int age; // Age private String name; // Private String address; Private static String sTest; publicStudent() {
         throw new IllegalAccessError("Access to default Constructor Error!");
    }

    private Student(int age, String name, String address) {
        this.age = age;
        this.name = name;
        this.address = address;
         sTest = "Test reflex";
    }

    private int getAge() {
        return age;
    }
    
    private void setAge(int age) {
        this.age = age;
    }

    private String getName() {
        return name;
    }

    private void setName(String name) {
        this.name = name;
    }

    private String getAddress() {
        return address;
    }

    private void setAddress(String address) {
        this.address = address;
    }
    private static String getTest() {
        returnsTest; }}Copy the code

I’m going to use private as an exercise for member variables and methods and I’m going to use constructor, method and property and static method,

public class StudentClient { public static void main(String[] args) throws Exception{ Class<? > clazz=Class.forName("ClassLoader.Student");
        Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
        constructors.setAccessible(true); MStudent =constructors. NewInstance (27,"Essays".No.xx, Haiding District, Beijing); System.out.println(mStudent.toString()); Field mAgeField=clazz.getDeclaredField("age");
        mAgeField.setAccessible(true);
        int age= (int) mAgeField.get(mStudent);
        System.out.println("Age :"+age); Method getAddressMethod=clazz.getDeclaredMethod()"getAge");
        getAddressMethod.setAccessible(true);
        int newage= (int) getAddressMethod.invoke(mStudent);
        System.out.println("Age :"+newage); GetTestMethod =clazz.getDeclaredMethod()"getTest");
        getTestMethod.setAccessible(true);
        String result= (String) getTestMethod.invoke(null);
        System.out.println("Calling static methods :"+result); }}Copy the code

The results are as follows:

Of course, the role of reflection is not only these, in the array, generics, design patterns and other aspects still play a huge role, but the principle is not separated from the above said, readers can view the relevant source code learning, source code is the best learning resources.

Reflection in the Android framework layer application

This is the focus of this article, as we all know, the Android FrameWork is written in the Java language, naturally cannot leave some reflection of the shadow, and the use of reflection is to achieve some of our conventional methods difficult to achieve the purpose, and reflection is also an important means of Hook in the Java layer, Current plugins make a lot of use of reflection.

Start with requirements: How do you monitor the Activity creation and startup process? I can rewrite the lifecycle method in the Activity. In fact this is short of demand, because it is very simple, the life cycle method invocation is after create and start a long time, the inside of the life cycle method relative to the entire Activity is relatively behind, to solve the problem, must want to know how the Activity, which process after the middle, Binder starts an Activity as an IPC process that goes through the local process ->AMS process -> back to the local process. Here’s an example:

After the Activity is transferred from the local AMS to the remote AMS, the remote AMS only does the permission and property check, and then returns to the local AMS process, which starts the real creation and check. The LAUNCH_ACTIVITY class H will send a LAUNCH_ACTIVITY message after returning from the remote process to the local process. The code is as follows: The following code is in ActivityThread.java

  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");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } 
Copy the code

Then the handleLaunchActivity method is called, and the performLaunchActivity method is called inside the handleLaunchActivity method as follows:

  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if(r.profilerInfo ! = null) { mProfiler.setProfiler(r.profilerInfo); mProfiler.startProfiling(); } // Make sure we are running with the most recent config. handleConfigurationChanged(null, null);if (localLOGV) Slog.v(
            TAG, "Handling launch of "+ r); // Initialize before creating the activity WindowManagerGlobal.initialize(); Activity a = performLaunchActivity(r, customIntent); . }Copy the code

PerformLaunchActivity now creates an Activity in performLaunchActivity.

Activity activity = null; try { // java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); / / by mInstrumentation newActivity () method to create an Activity, is mInstrumentation Instrumentation instances of the class, The class of the object is: Instrumentation. Java activity = mInstrumentation. NewActivity (cl, component getClassName (), r.i ntent); 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); }}Copy the code

We now know the creation of an Activity, which is implemented by the newActivity() method of Instrumentation. Let’s look at the method:

public Activity newActivity(Class<? > clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { Activity activity = (Activity)clazz.newInstance(); ActivityThread aThread = null; activity.attach(context, aThread, this, token, 0, application, intent, info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, new Configuration(), null, null, null);return activity;
    }
Copy the code

As you can see, one of the four major components of an Activity is actually a normal object created by reflection. However, the component is a living object due to the addition of the lifecycle method. Therefore, Reflection is everywhere in Android. How do you monitor the start and creation of activities? Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

 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

Callback (H) : Callback (H) : Callback (H) : Callback (H);

public static void hookHandler(Context context) throws Exception { Class<? > activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true); / / get the main thread Object Object activityThread = currentActivityThreadMethod. Invoke (null); / / for mH fields Field mH = activityThreadClass. GetDeclaredField ("mH");
        mH.setAccessible(true); Handler Handler = (Handler) mh. get(activityThread); / / get the original mCallBack Field Field mCallBack = Handler. Class. GetDeclaredField ("mCallback");
        mCallBack.setAccessible(true); McAllback.set (handler, new UserHandler(handler)); }Copy the code
Public class UserHandler implements Callback {// This is not the only method that implements Callback. Public static final int LAUNCH_ACTIVITY = 100; private Handler origin; public UserHandler( Handler mHandler) { this.origin = mHandler; } @Override public boolean handleMessage(Message msg) {if(msg.what == LAUNCH_ACTIVITY) {// So that you can do something extra every time you start log.d ("[app]"."Do what you want.");
        }
        origin.handleMessage(msg);
        return false; }}Copy the code

Ok, so that’s how Activity start monitoring is done, usually in the attachBaseContext() method of the application, because this is the earliest. Well, let’s talk about the creation of Activity monitoring, we know that the newActivity method of Instrumentation is responsible for creating the Activity, so the breakthrough is here, create custom Instrumentation for us, Then reflect the replacement and override the newActivity method to do things like keep track of the time. Here’s the code:

public static void hookInstrumentation() throws Exception{ Class<? > activityThread=Class.forName("android.app.ActivityThread");
        Method currentActivityThread=activityThread.getDeclaredMethod("currentActivityThread");
        currentActivityThread.setAccessible(true); / / get the main thread Object Object activityThreadObject = currentActivityThread. Invoke (null); / / to get Field Instrumentation Field mInstrumentation = activityThread. GetDeclaredField ("mInstrumentation");
        mInstrumentation.setAccessible(true); Instrumentation instrumentation= (Instrumentation) mInstrumentation.get(activityThreadObject); CustomInstrumentation customInstrumentation=new CustomInstrumentation(instrumentation); / / replace the original, that is, have replaced the instrumentation system for instrumentation object mInstrumentation. Set (activityThreadObject CustomInstrumentation); Log.d("[app]"."Hook Instrumentation success");

    }
Copy the code
public class CustomInstrumentation extends Instrumentation{ private Instrumentation base; public CustomInstrumentation(Instrumentation base) { this.base = base; @override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Log.d("[app]"."you are hook! Do what you want.");
        Log.d("[app]"."className="+className+" intent="+intent);
        returnsuper.newActivity(cl, className, intent); }}Copy the code

Add attachBaseContext to the application. Of course, Instrumentation has other methods to rewrite, you can try, the following is the result of running:

There are caveats to using reflection

As you can see from the previous section, reflection is very convenient and can be used in specific situations to fulfill specific requirements, but there are a few things to note when using reflection:

● It is best to use the public modifier for reflection. There are compatibility risks with other modifiers, such as this one and not another

● We all know that the Android open source code caused compatibility problems, this is the Android system open source the biggest problem, especially those third-party ROM, to be careful to use.

● If you use a lot of reflection, the code needs to optimize the package, otherwise it is not easy to manage, write code is not only to achieve the function, but also maintenance and readability methods need to strengthen, demo can be directly so rough, in the project or need to organize the package.

That’s all for today’s article. Thank you for reading it.