Before you go to bed, in order to better sleep, let’s learn the combination of reflection + annotation + dynamic proxy posture. In the last article we talked briefly about dynamic proxies. Today we’ll take a look at them with reflection and annotations. First will simply look at the reflection and annotations, dynamic proxy please refer to the last blog: www.jianshu.com/p/b00ef12d5… Then it will be integrated through an Android chestnut for actual combat.
Reflection first reserve theoretical knowledge, the specific application can see the chestnuts. Java’s reflection lets you get all of a class’s internal member methods/fields and constructors at run time, and even instantiate them to call internal methods. Don’t you look great? More commonly used methods:
GetDeclaredFields () : you can get a class member variable getDeclaredMethods () : you can get a member of the class methods getDeclaredConstructors () : you can get a class constructor
Java code passes through three phases: code writing, compilation, reading, and JVM execution. There are three types of annotations for each phase:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}Copy the code
SOURCE: for code writing, such as the @override annotation CLASS: RUNTIME: this is for reading into the JVM RUNTIME. This can be used in conjunction with reflection. The annotations we use today are also used in this phase.
Use annotations also need to specify the object used by the annotation, we look at the source code to know that there are several classes:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation typedeclaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration ** @hide 1.8 */ TYPE_PARAMETER, /** * Use of atype* * @hide 1.8 */ TYPE_USE}Copy the code
Fainting? Is that a little too much? Do not fear, in fact, we often use also so a few
TYPE Object class/interface/enumeration FIELD member variable METHOD Member METHOD PARAMETER Annotation for METHOD PARAMETER ANNOTATION_TYPE annotation
So how do you define an annotation? Much like defining an interface, let’s go straight to the annotations we used in our tutorial. Three types of annotations are defined in chestnuts:
InjectView is used to inject views, it’s used instead of the findViewById method Target specifies InjectView annotation object is a member variable Retention specifies the annotation validity until runtime value is used to specify id, That’s the argument to findViewById
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}Copy the code
The onClick annotation is used to inject click events, it’s actually used instead of the setOnClickListener method Target specifies that the onClick annotation object is the member method Retention specifies that the onClick annotation is valid until runtime value specifies the ID, That’s the argument to findViewById
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface onClick {
int[] value();
}Copy the code
As you can see, the onClick annotation also has a custom annotation EventType, which is defined as follows:
Target specifies EventType the annotation object is the annotation, the annotation’s annotation Retention specifies the annotation validity of EventType until runtime listenerType is used to specify the tap listening type, For example, OnClickListener listenerSetter is used to specify methods that set click events, and setOnClickListener methodName is used to specify methods that will be called back when a click event occurs, such as onClick
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
Class listenerType();
String listenerSetter();
String methodName();
}Copy the code
###3. Dynamic proxy
About dynamic agent please refer to the last blog: www.jianshu.com/p/b00ef12d5…
### the theory is falling asleep. Let’s take a break:
Ok, let’s take a look at a simple combination of reflection/annotation/dynamic proxy mentioned above. Let’s take a look at the layout, which is very simple: the two buttons are bound by annotation, and the click event is monitored by annotation + reflection + dynamic proxy.
<RelativeLayout
android:id="@+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.juexingzhe.proxytest.MainActivity">
<Button
android:id="@+id/bind_view_btn"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bind_view"/>
<Button
android:id="@+id/bind_click_btn"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bind_click"/>
</RelativeLayout>Copy the code
####- control binding instantiation let’s first look at how to bind the button member variable.
1. Declare a member variable and add InjectView annotation to the variable declaration. Value is the button ID: R.i.bin_view_btn
@InjectView(R.id.bind_view_btn) Button mBindView;Copy the code
2. Invoke injection in onCreate
Utils.injectView(this);Copy the code
FindViewById, findViewById, findViewById, findViewById, findViewById, findViewById, findViewById Utils. InjectView (this); Utils. InjectView (this); Is it awesome to nail *?? InjectView () {Utils. InjectView () {Utils. InjectView () {Utils. InjectView () {Utils.
2. Find the member variable annotated by the InjectView. Then get the button id 3. Get the findViewById method 4 by reflecting ActivityClass.getMethod. Call findViewById, and the argument is id. As you can see, the control is ultimately instantiated through findViewById
public static void injectView(Activity activity) {
if (null == activity) return;
Class<? extends Activity> activityClass = activity.getClass();
Field[] declaredFields = activityClass.getDeclaredFields();
for (Field field : declaredFields) {
if(field. IsAnnotationPresent (InjectView. Class)) {/ / find InjectView annotation field = InjectView an annotation field.getAnnotation(InjectView.class); Int value = annotation. Value (); FindViewByIdMethod = activityClass.getMethod(findViewById = activityClass."findViewById", int.class);
findViewByIdMethod.setAccessible(true); findViewByIdMethod.invoke(activity, value); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code
Next, look at the binding of click events
####- Click the event binding again in two steps
1. Define the callback method when a click event occurs. Controls that need to bind to a click event simply assign an ID to the onClick annotation.
@onClick({R.id.bind_click_btn, R.id.bind_view_btn}) public void InvokeBtnClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(this); switch (view.getId()) { case R.id.bind_click_btn: Log.i(Utils.TAG, "bind_click_btn Click"); builder.setTitle(this.getClass().getSimpleName()) .setMessage("button onClick") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); break; case R.id.bind_view_btn: Log.i(Utils.TAG, "bind_view_btn Click"); builder.setTitle(this.getClass().getSimpleName()) .setMessage("button binded") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); break; }}Copy the code
2. Invoke injection in onCreate
Utils.injectEvent(this);Copy the code
InjectEvent = reflection + annotation + dynamic proxy = injectEvent = reflection + annotation + dynamic proxy = injectEvent
1. The first step is to get all the activity member methods getDeclaredMethods 2. Find the method that has the onClick annotation and get the value which is the annotation click on the id 3 of the event button. Get the EventType parameter of the onClick annotation, from which you can get setOnClickListener + OnClickListener+ callback method onClick 4. The Android system will trigger the onClick event when the click event occurs, and we need to call back the event processing to the annotation method InvokeBtnClick, which is the idea of proxy 5. Instantiate a Proxy that implements the OnClickListener interface through the dynamic Proxy proxy. newProxyInstance. 6.RealSubject is an activity, so we pass in ProxyHandler to instantiate an InvocationHandler. The method used to map the onClick event to our annotated InvokeBtnClick 7 in the activity. By reflecting instantiation Button, findViewByIdMethod. Invoke 8. Through the Button. SetOnClickListener (an OnClickListener) to set the click event listeners.
public static void injectEvent(Activity activity) {
if (null == activity) {
return;
}
Class<? extends Activity> activityClass = activity.getClass();
Method[] declaredMethods = activityClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if(method.isAnnotationPresent(onClick.class)) { Log.i(Utils.TAG, method.getName()); onClick annotation = method.getAnnotation(onClick.class); // Get button id int[] value = annotation. Value (); / / get EventType EventType EventType = annotation. AnnotationType () getAnnotation (EventType. Class); Class listenerType = eventType.listenerType(); String listenerSetter = eventType.listenerSetter(); String methodName = eventType.methodName(); // Create the InvocationHandler and the dynamic proxy (to implement listenerType, ProxyHandler = new ProxyHandler(activity); Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, proxyHandler); proxyHandler.mapMethod(methodName, method); try {for(int id: value) {// findViewByIdMethod = ActivityClass.getMethod ("findViewById", int.class);
findViewByIdMethod.setAccessible(true); View btn = (View) findViewByIdMethod.invoke(activity, id); ListenerSetMethod = btn.getClass().getMethod(listenerSetMethod) listenerType); listenerSetMethod.setAccessible(true); listenerSetMethod.invoke(btn, listener); } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code
Note the steps above:
. Why the Proxy newProxyInstance dynamically generated Proxy can be passed to the Button. The setOnClickListener? Since the listenerType parameter passed by Proxy is OnClickListener, the Java Proxy generated for us will implement this interface, calling the Invoke method in ProxyHandler when the onClick method is called, To call back to the annotated method in the activity.
ProxyHandler is mainly the Invoke method, which prints the method name and parameters when the method is called.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(Utils.TAG, "method name = " + method.getName() + " and args = " + Arrays.toString(args));
Object handler = mHandlerRef.get();
if (null == handler) returnnull; String name = method.getName(); // Map the onClick call to the InvokeBtnClick() Method realMethod = mmethodhashmap.get (name);if(null ! = realMethod){return realMethod.invoke(handler, args);
}
return null;
}Copy the code
Click run result:
###5. Summary of the article used in the more simple, but also clarified the use of reflection + annotation + dynamic proxy, can be found through this way will have a better expansion, even after the increase of control is just add member variable declaration. Dynamic proxy this article talked about the less, or advice to see www.jianshu.com/p/b00ef12d5…
As for the chestnut in the article, I have also put it on Github if I need it: github.com/juexingzhe/…
Thank you very much!