Personal blog

www.milovetingting.cn

Dynamic proxy + annotation + reflection to achieve View click event binding

The proxy mode is to provide an object with a proxy object that controls references to the original object. The proxy mode includes static proxy and dynamic proxy.

Static agent

Defines the interface

public interface Player {

    void play(a);

}
Copy the code

Define concrete implementation classes

public class PlayerImpl implements Player {

    @Override
    public void play(a) {
        System.out.println("PlayerImpl play..."); }}Copy the code

Defining proxy classes

public class ProxyImpl implements Player {

    private Player player;

    public ProxyImpl(Player player) {
        this.player = player;
    }

    @Override
    public void play(a) { player.play(); }}Copy the code

As you can see, both the concrete implementation class and the proxy class implement the same interface class, and in the implementation of the proxy class, the concrete implementation class is referenced.

A dynamic proxy

Static proxies are defined before they run. Dynamic proxy is the dynamic creation of proxies and instances at run time. The JDK provides the Proxy class to create dynamic proxies

public static Object newProxyInstance(ClassLoader loader,Class
       [] interfaces,InvocationHandler h)throws IllegalArgumentException{}Copy the code

First look at the specific use

Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Player.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                returnmethod.invoke(player, args); }});Copy the code

NewProxyInstance corresponds to three parameters:

  • Class loader

  • The class of the interface, Proxy generates the Proxy class from this class

  • InvocationHandler, the callback to a method that is called by a method of the proxy class

The complete code for the dynamic proxy class

public class DynamicProxy {

    private Player player;

    public DynamicProxy(Player player) {
        this.player = player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    public Player getProxy(a) {
        return (Player) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Player.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                returnmethod.invoke(player, args); }}); }}Copy the code

The proxy. newProxyInstance method ends up calling ProxyGenerator

public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
    }
Copy the code

Proxy classes generated by dynamic proxies exist in memory.

Implement the binding of click events

The following is based on dynamic proxy + annotation + reflection, control click event binding.

Start by defining annotations for the event type

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {

    Class listenerType(a);

    String listenerSetter(a);
}
Copy the code

Then define annotations for click and hold events respectively

@EventType(listenerType = View.OnClickListener.class,listenerSetter = "setOnClickListener")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    @IdRes int[] value();
}
Copy the code
@EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
    @IdRes int[] value();
}
Copy the code

Click events are then defined in the Activity

@OnClick({R.id.btn1, R.id.btn2})
    public void click(Button view) {
        Toast.makeText(getApplicationContext(), view.getText(), Toast.LENGTH_SHORT).show();
    }

    @OnLongClick({R.id.btn1, R.id.btn2})
    public boolean longClick(Button view) {
        Toast.makeText(getApplicationContext(), view.getText() + "-LongClick", Toast.LENGTH_SHORT).show();
        return true;
    }
Copy the code

Bind in the Activity

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectHelper.inject(this);
    }
Copy the code

The realization of the InjectHelper

public class InjectHelper {

    public static void inject(final Activity target) {
        if (target == null) {
            return;
        }
        Class<? extends Activity> clz = target.getClass();
        Method[] declaredMethods = clz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType.isAnnotationPresent(EventType.class)) {
                    EventType eventType = annotationType.getAnnotation(EventType.class);
                    Class listenerType = eventType.listenerType();
                    String listenerSetter = eventType.listenerSetter();
                    try {
                        Method valueMethod = annotationType.getDeclaredMethod("value");
                        int[] ids = (int[]) valueMethod.invoke(annotation);
                        method.setAccessible(true);
                        ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(method, target);
                        Object proxyInstance = Proxy.newProxyInstance(target.getClassLoader(), new Class[]{listenerType}, invocationHandler);
                        for (intid : ids) { View view = target.findViewById(id); Method setter = view.getClass().getMethod(listenerSetter, listenerType); setter.invoke(view, proxyInstance); }}catch (Exception e) {

                    }
                }
            }
        }
    }

    static class ListenerInvocationHandler<T> implements InvocationHandler {

        private Method method;

        private T target;

        public ListenerInvocationHandler(Method method, T target) {
            this.method = method;
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return this.method.invoke(target, args); }}}Copy the code