directory
- What is reflection?
- What is jOOR?
- What is OkReflect?
- Where did OkReflect come from?
- How to use OkReflect to reflect?
- How else is OkReflect different from jOOR?
- How is OkReflect implemented internally?
- How do I configure OkReflect?
- conclusion
Before we get to OkReflect, let’s talk about reflection.
1. What is reflection?
Java reflection allows a program to know all the properties and methods of any given class at runtime.
For any object, you can call any of its methods and properties.
This ability to dynamically retrieve information and invoke methods on objects is called Java reflection.
To put it bluntly, just like you go to a sand county, you do not eat spicy, the result of the boss always put chili powder, through reflection, you can eat a non-spicy fried powder.
1.1 Creating A String Normally
String str = "666";
Copy the code
1.2 Creating strings using Reflection
try {
Class clazz = String.class;
Constructor constructor = clazz.getConstructor(String.class);
String instance = (String) constructor.newInstance("666");
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Copy the code
So look, reflection is too much trouble, what’s the use? But if you want to create an instance whose constructor is private and you can’t call it, then reflection comes in handy.
1.3 uses reflection third party library
If you’re an Android developer, you’ve probably heard of or used Retrofit and plugins, both of which use reflection.
Retrofit creates Http requests through dynamic proxies to help us gracefully make web requests.
The plug-in technology reflects the Android source code, modifying some key fields and calling some key methods to make the Android system change the original behavior, such as loading activities not declared in the manifest file.
1.4 Use reflection effectively
There are three levels to the developer: use the wheel, understand the wheel, and make the wheel.
Using wheels means that we can use Java, Android SDK or other third-party libraries to develop, understanding wheels means that we have some understanding of the source code of these third-party libraries, making wheels means that we not only understand these third-party libraries, but also find its shortcomings, and re-develop, re-package or modify it.
Reencapsulation can be done through inheritance and dynamic proxies like Retrofit, while modification can only be done through reflection because third-party libraries do not exist in our source code.
Building a wheel requires you to change the way you look at the source code and look at the source code and think, what are the problems with this implementation? Is there a better implementation? By doing so, you can improve not only your own productivity, but that of the entire technology community.
1.5 Using reflection to call private Members
Suppose we now have a Client class whose constructors and methods are private, we can create the instance by reflection and call its private methods.
public class Client {
private String name;
private Client(String name) {
this.name = name;
}
private void setName(String name) {
this.name = name; }}Copy the code
try {
Class clazz = Class.forName("Client");
Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Client client = (Client) constructor.newInstance("Zhang");
Method method = clazz.getDeclaredMethod("setName", String.class);
method.setAccessible(true);
method.invoke(client, "Wang");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Copy the code
Creating classes and calling methods is fine, but the code looks pretty ugly. Don’t worry, jOOR.
2. What is jOOR?
2.1 introduce jOOR
JOOR is an older reflection framework that, like OkReflect, encapsulates the operations of Java reflection.
2.2 jOOR usage
String world = on("java.lang.String") // equivalent to class.forname ()
.create("Hello World") // Call the constructor to create the instance
.call("substring".6) // Call substring
.get(); // The method returns the result
Copy the code
It’s good. All the exceptions are gone, but you can’t just use it like that, because if something goes wrong, you still get an exception.
JOOR defines a custom ReflectException, and any exception that occurs in reflection will be wrapped as a ReflectException, so you still need a try layer… The catch.
2.3 Handling Exceptions
try {
String world = on("java.lang.String")
.create("Hello World")
.call("substring".6)
.get();
} catch (ReflectException e) {
// Handle exceptions
}
Copy the code
Try… Catch, but much better than the original reflection operation, but is there a better way? Here’s OkReflect’s handle.
3. What is OkReflect?
3.1 introduce OkReflect
OkReflect is a tool that encapsulates Java reflection and is implemented in Kotlin.
3.2 Where did OkReflect come from?
Both authors mention the jOOR reflection framework in their books, Componentized Architecture for Android and A Guide to Plug-in Development for Android.
When I first saw jOOR, I thought it was more elegant than normal reflection, but when I tested it in different scenarios, I noticed a few problems and OkReflect was born in the process.
4. How to use OkReflect to reflect?
4.1 Use OkReflect for reflection
try {
String world = OkReflect.on("java.lang.String")
.create("Hello World")
.call("substring".6)
.get();
} catch (Exception e) {
// Handle exceptions
}
Copy the code
It’s not that different from jOOR. Don’t worry, OkReflect also allows you to handle exceptions in callbacks.
4.2 Handling Exceptions in Callback
String world = OkReflect.on("java.lang.String")
.create("Hello World")
.call("substring".6)
.error(new OkReflect.OkReflectErrorCallback() {
@Override
public void onError(@NotNull Exception e) {
// Handle exceptions
}
})
.get();
Copy the code
If you’re using Kotlin, you could write it this way.
4.3 Use OkReflect in Kotlin
val world: String? = OkReflect.on("java.lang.String")
.create("Hello World")
.call("substring".6)
.error{
// Handle exceptions}.get(a)Copy the code
5. What’s the difference between OkReflect and jOOR?
5.1 Calling methods continuously
In jOOR, if you call a method with a return value, the next method you call will be called with the value returned by the previous method. This default is inconvenient when we don’t care about the return value. Suppose we now have a Manager class.
public class Manager {
public List<String> items = new ArrayList<>();
public int count;
public int addData(String data) {
this.items.add(data);
count ++;
return count;
}
public String getData(int i) {
returnitems.get(i); }}Copy the code
5.1.1 Using jOOR to call methods continuously
If we use jOOR to fetch data after adding data, jOOR throws a NoSuchMethodException after calling the method. This is because jOOR calls getData using the count returned by addData. And count is Integer, and there’s really no getData method in Integer.
// jOOR
String data = on("Manager")
.create()
.call("addData"."data")
.call("getData".0)
.get();
Copy the code
5.1.2 Call methods continuously with OkReflect
This problem doesn’t occur if you use OkReflect to do this, because OkReflect defaults to calling methods using instances.
// OkReflect
String data = OkReflect.on(Manager.class)
.create()
.call("addData"."data")
.call("getData".0)
.get();
Copy the code
5.1.3 Call the next method with the return value in OkRefelct
With the callWithResult() method, you can also call the next method in OkReflect with the return value of the previous method.
String str = OkReflect
.on(String.class)
.create("Hello world")
.call("substring".6)
.callWithResult("substring".4)
.get();
Copy the code
5.2 Obtaining An Instance
What if you want to ignore the return value after adding data, but instead get the instance? In jOOR you can only get the result of the returned value, but in OkRefelct you can use the getInstance() method to get the instance instead of returning the result, such as this one
// OkReflect
Manager manager = OkReflect.on(Manager.class)
.create()
.call("addData"."data")
.getInstance();
Copy the code
5.3 Type Security
In jOOR, the type of return value retrieved is not guaranteed. The scope of a catch using a ReflectException is too small to catch a casting Exception.
However, in OkReflect, the cast exception is caught on return and will return null if it is of the wrong type. You can either use the null value to determine whether the conversion was successful or handle the exception in ErrorCallback, as shown below.
String manager = OkReflect.on(Manager.class)
.create()
.call("addData"."data")
.error(new OkReflect.OkReflectErrorCallback() {
@Override
public void onError(@NotNull Exception e) {
// Handle exceptions
}
})
.getInstance();
Copy the code
5.4 Android Final Fields
The Field fiers class in Android doesn’t have modifiers, which makes regular changes to final fields impossible in Android.
JOOR and OkReflect both use reflection to modify the Modifiers Field in Field, but OkReflect is judging the system so OkReflect can also modify final fields on Android.
5.5 External Instances
5.5.1 Calling a member of an external instance
Whereas jOOR’s on method can only pass in class information, OkReflect can pass in instances. How does that help? For example, in Android Instrumentation, it is very troublesome to create an instance to Hook, so it is usually used in the Activity of the existing Instrumentation Hook. Here is an example of using an instance to invoke a method.
Client client = new Client();
OkReflect.onInstance(client)
.call("setName"."Alex")
.get("name");
Copy the code
5.5.2 Modifying the private Field of the parent Class
As mentioned above, there is no way to pass in an external instance in jOOR, let alone modify a parent class’s private field through an external instance.
Using OkReflect’s with method, it is possible to modify the private fields of the parent class via an external instance, such as the mInstrumentation field in the Activity instance Hook Activity.
// Modify and get the private fields of the parent class
Client client = new Client();
String superName = OkReflect.on(SuperClient.class)
.with(client)
.set("superName"."Tom")
.get("superName");
Copy the code
5.6 Parameter Types
When you call a method argument with a null value, neither jOOR nor OkReflect can determine the type of the argument, and therefore cannot find the corresponding method and call it.
In OkReflect, you can pass in the corresponding class object in the callWithClass() method, which OkReflect can use to find the corresponding method and call it.
Class classes[] = {String.class, Byte.class};
Object args[] = {"Tom".null};
String name = OkReflect.on(TestClass.class)
.create()
.callWithClass("setData2", classes, args)
.get("name");
Copy the code
5.7 Asynchronous Execution
If you’re calling a function whose processing takes longer to run, you can choose to use your own asynchronous framework to perform reflection, or you can use OkReflect’s Async method to implement reflection asynchronously.
// Java
OkReflect.on(Client.class)
.create()
.call("setName"."Tom")
.field("name")
.async(result -> {
// Process the result
});
Copy the code
OkReflect.on(Client::class.java)
.create()
.call("setName"."Tom")
.field("name")
.callback<String> {
// Process the result
}
Copy the code
5.8 Compiling Strings into Java class files
JOOR supports generating classes as strings, which OkReflect does not.
6. How is OkReflect internally implemented?
6.1 single instance
JOOR creates a Reflect instance every time you call jOOR’s call method, which can cause memory jitter or even OOM if you want to Reflect too often.
And inside OkReflect, from the first time you reflect to all subsequent reflection operations, you’re doing it in the same OkReflect instance, and every time you reflect OkReflect cleans up the resources loaded at the last reflection.
6.2 Pseudo-builder pattern
OkReflect follows the pseudo-builder pattern, with the get() method equivalent to the build() method. If you want to ignore some of the actions that get() does for reflection, you can use simpleSet() and simpleCall().
String nameFromMethod = OkReflect.on(Client.class)
.create()
.simpleCall("getName");
String name = OkReflect.on(Clent.class)
.create()
.simpleSet("name"."Tom");
Copy the code
7. How to configure OkReflect?
7.1. Gradle dependency
allprojects {
repositories {
...
maven { url 'https://jitpack.io'}}}Copy the code
dependencies {
implementation 'com.github.zeshaoaaa:OkReflect:master-SNAPSHOT'
}
Copy the code
7.2 the Maven rely on
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Copy the code
<dependency>
<groupId>com.github.zeshaoaaa</groupId>
<artifactId>OkReflect</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
Copy the code
conclusion
If you need to convert strings into Java class files with Compile, you can use jOOR, but if you need some other reflection functionality and are developing with Kotlin, OkReflect is recommended.
reference
Android Plug-in Development Guide
Android Componentized Architecture