Java reflection by some Android examples (part 1)
This Thursday, I met an issue while I was fixing bugs for my company project. It was about listening the event occurred when a Spinner drop down list dismissed.
I took me 1 working day investigating the Spinner and related classes's source code. The conclusion was that a drop-down styled Spinner contained an object having an onDismissListener, we could set a listener for this object. The only problem is the object was PRIVATE declared inside Spinner, so was its class declaration.
Another day for looking for solutions. I then extended the Spinner class for setting a listener and applied reflection technique for accessing something "private". I think this experiences will be helpful for some of you. I will write down what I researched with real-code demo.
First of all, here is a little about reflection from https://docs.oracle.com/javase/tutorial/reflect/
public class Spinner extends AbsSpinner implements OnClickListener {
//...
}
and my extending code like this:
public class PopupDismissCatchableSpinner extends Spinner {
//...
public void setOnPopupDismissListener(PopupDismissListener listener) {
// TODO: we will work here
}
//...
}
In side the Spinner, a variable was declared:
public class Spinner extends AbsSpinner implements OnClickListener {
//...
private SpinnerPopup mPopup;
//...
}
To access mPopup from my extended class, I wrote these lines;
public void setOnPopupDismissListener(PopupDismissListener listener) {
//...
Field mSpinnerPopup = this.getClass().getSuperclass().getDeclaredField("mPopup");
mSpinnerPopup.setAccessible(true);
//...
}
There are things to remember:
- getClass() and getSuperclass() are methods returning objects in Class type, which are representations of Java classes in memory, they are like classes' declaration, they are not classes' instances.
- Field objects are representations of fields in memory, containing information about the field can be accessed, and the field's value can be accessed dynamically. They are like fields' declaration, they are not fields' instances.
- getDeclaredField(String fieldName) is a method returning a Field with the given name which is declared in the current class, it may throw an exception, so do try-catch carefully.
- setAccessible(boolean accessible) is a method declared in Field class. If accessible's value is true, access checks will be disabled and we could access this field.
Remember, Field mSpinnerPopup is like a field declaration of Spinner.mPopup, containing information such as location of declaring, actual class, generic type, name ... of mPopup.
To retrieve the object appearing in Spinner at runtime, do this:
public void setOnPopupDismissListener(PopupDismissListener listener) {
//...
Field mSpinnerPopup = this.getClass().getSuperclass().getDeclaredField("mPopup");
mSpinnerPopup.setAccessible(true);
Object mSpinnerPopupObj = mSpinnerPopup.get(this);
//...
}
- get(Object containingObject) is a method declared in Field class, it returns the value of the field in the specified containing object. In this situation, the containing object is my custom Spinner. The returned object is the instance of SpinnerPopup mPopup and we could cast it, manipulate it...
void setAllowScrollingAnchorParent(boolean enabled) {
mAllowScrollingAnchorParent = enabled;
}
This method has default modifier, that means it is visible only within its own package (package-private). And I want to call this method from another package, assume I have already got a PopupWindow instance named mPopup.
We can inspect the methods available within a given class. To get just the methods that are publicly available, including inherited methods, use getMethods(). However, if we want to inspect those methods specifically declared within the class (without inherited ones), whether they are public or not, use getDeclaredMethods() instead.
Both methods return an array of Method objects. Also, Method object represents a declaration of a method, information about the method can be accessed, the method can be invoked dynamically.
We could use getDeclaredMethod(String methodName, Class<?>... parameterTypes) if we know exactly the method name (in this case, "setAllowScrollingAnchorParent"). parameterTypes is an array containing classes of all parameters for the target method.
To call the target method instead of using mPopup.setAllowScrollingAnchorParent(true) (this way is not allowed), we use invoke(Object receiver, Object... args) whose
- receiver is the object on which to call this method (or null for static methods)
- args is an array of the arguments to the method
- returned value is the result of the method call
Here we go:
Method mSetAllowScrollingAnchorParent = mPopup.getClass().getDeclaredMethod("setAllowScrollingAnchorParent", new Class[] {boolean.class});
mSetAllowScrollingAnchorParent.invoke(mPopup, new Object[] {true});
Gnight all :)
I took me 1 working day investigating the Spinner and related classes's source code. The conclusion was that a drop-down styled Spinner contained an object having an onDismissListener, we could set a listener for this object. The only problem is the object was PRIVATE declared inside Spinner, so was its class declaration.
Another day for looking for solutions. I then extended the Spinner class for setting a listener and applied reflection technique for accessing something "private". I think this experiences will be helpful for some of you. I will write down what I researched with real-code demo.
First of all, here is a little about reflection from https://docs.oracle.com/javase/tutorial/reflect/
Uses of Reflection
Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.The Spinner's code, like this:
public class Spinner extends AbsSpinner implements OnClickListener {
//...
}
and my extending code like this:
public class PopupDismissCatchableSpinner extends Spinner {
//...
public void setOnPopupDismissListener(PopupDismissListener listener) {
// TODO: we will work here
}
//...
}
Finding a field
In side the Spinner, a variable was declared:
public class Spinner extends AbsSpinner implements OnClickListener {
//...
private SpinnerPopup mPopup;
//...
}
To access mPopup from my extended class, I wrote these lines;
public void setOnPopupDismissListener(PopupDismissListener listener) {
//...
Field mSpinnerPopup = this.getClass().getSuperclass().getDeclaredField("mPopup");
mSpinnerPopup.setAccessible(true);
//...
}
There are things to remember:
- getClass() and getSuperclass() are methods returning objects in Class type, which are representations of Java classes in memory, they are like classes' declaration, they are not classes' instances.
- Field objects are representations of fields in memory, containing information about the field can be accessed, and the field's value can be accessed dynamically. They are like fields' declaration, they are not fields' instances.
- getDeclaredField(String fieldName) is a method returning a Field with the given name which is declared in the current class, it may throw an exception, so do try-catch carefully.
- setAccessible(boolean accessible) is a method declared in Field class. If accessible's value is true, access checks will be disabled and we could access this field.
Retrieving an object
Remember, Field mSpinnerPopup is like a field declaration of Spinner.mPopup, containing information such as location of declaring, actual class, generic type, name ... of mPopup.
To retrieve the object appearing in Spinner at runtime, do this:
public void setOnPopupDismissListener(PopupDismissListener listener) {
//...
Field mSpinnerPopup = this.getClass().getSuperclass().getDeclaredField("mPopup");
mSpinnerPopup.setAccessible(true);
Object mSpinnerPopupObj = mSpinnerPopup.get(this);
//...
}
- get(Object containingObject) is a method declared in Field class, it returns the value of the field in the specified containing object. In this situation, the containing object is my custom Spinner. The returned object is the instance of SpinnerPopup mPopup and we could cast it, manipulate it...
Calling an arbitrary method
The situation is android.widget.PopupWindow has a method:void setAllowScrollingAnchorParent(boolean enabled) {
mAllowScrollingAnchorParent = enabled;
}
This method has default modifier, that means it is visible only within its own package (package-private). And I want to call this method from another package, assume I have already got a PopupWindow instance named mPopup.
We can inspect the methods available within a given class. To get just the methods that are publicly available, including inherited methods, use getMethods(). However, if we want to inspect those methods specifically declared within the class (without inherited ones), whether they are public or not, use getDeclaredMethods() instead.
Both methods return an array of Method objects. Also, Method object represents a declaration of a method, information about the method can be accessed, the method can be invoked dynamically.
We could use getDeclaredMethod(String methodName, Class<?>... parameterTypes) if we know exactly the method name (in this case, "setAllowScrollingAnchorParent"). parameterTypes is an array containing classes of all parameters for the target method.
To call the target method instead of using mPopup.setAllowScrollingAnchorParent(true) (this way is not allowed), we use invoke(Object receiver, Object... args) whose
- receiver is the object on which to call this method (or null for static methods)
- args is an array of the arguments to the method
- returned value is the result of the method call
Here we go:
Method mSetAllowScrollingAnchorParent = mPopup.getClass().getDeclaredMethod("setAllowScrollingAnchorParent", new Class[] {boolean.class});
mSetAllowScrollingAnchorParent.invoke(mPopup, new Object[] {true});
Conclusion for part 1
There are some other things I researched such as inspecting a constructor, listing all inner classes of a class, getting the modifier of a field/method/class... and I will write another short post about all of them, because today I feel a bit exhausted.Gnight all :)
Comments
Post a Comment