通过反射或代理通过运行时注解处理多个view的点击事件

正文

反射的性能较低。不建议这么使用哈。这么写主要是表述可以这么设计。基于这种设计,我们可以通过编译时技术去通过注解信息去改变对应的class 生成对应的类。 我们知道APT技术可以生成java 文件。如果我们不想使用编译时技术呢?

回归到正题,在开发过程中,我们经常对于view设置点击事件,那么我们就需要写很多onClick 事件。我们知道反射的时候可以获取到对应的函数,那么通过反射+注解如何去实现这个功能呢? 我们将需求拆分下:

  • 一个类中有很多的函数,所以我们需要一个函数上的标记,标记这个函数需要通过反射调用。
  • 估计有很多view调用同一个点击事件,所以标记里面需要存id.因为id 是常量。view的变量。
  • 通过函数注解获取到的是id,不是view。而点击事件需要设置到view 上,所以我们需要通过反射获取到view,所以需要反射findviewByid()
  • 因为我们不想写onClick 事件。但是这个代码不能没有,所以得有人帮我们写。
  • 我们需要再写onClick 的地方 调用我们标记的函数。

第一步定义注解

反射是运行时的,所以注解的生命周期应该也是运行时,才能够获取到注解,我们需要获取到多个id,所以需要存储多个值。

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int [] ids() default {-1};
}

获取到函数

java 复制代码
public class InjetUtils {
    public static void injectClick(Object context){
        Class<?> aClass = context.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method: methods){
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (null==onClick){
                continue;
            }
            int[] ids = onClick.ids();
            try {
                Method findViewById = aClass.getMethod("findViewById", int.class);
                for (int id: ids){
                   View view=(View)findViewById.invoke(context,id);
                   view.setOnClickListener(new View.OnClickListener() {
                       @Override
                       public void onClick(View v) {
                           try {
                               method.invoke(context,v);
                           } catch (IllegalAccessException e) {
                               e.printStackTrace();
                           } catch (InvocationTargetException e) {
                               e.printStackTrace();
                           }
                       }
                   });
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

动态代理

动态代理特性,持有谁就是代理谁。那么我们基于动态代理去做呢? 我们基于上面的诉求继续拆解。

  • 我们依旧需要在某个地方调用我们标记的函数。
  • 所以我们动态代理生成的class 只能说点击事件的class.
  • 我们还是需要将动态代理生成的对象设置到view 上。

定义 InvocationHandler

我们知道,动态代理对象的函数都会执行到InvocationHandler的invoke里面。基于上面的诉求,那么我们就需要再invoke里面调用我们被标记的函数。

java 复制代码
public class ListenerInvocationHandler implements InvocationHandler {
    // 这个是我们的activity
    private Object object;
    // 这个是我们注解标记的对象函数
    private Method activityMethod;

    public ListenerInvocationHandler(Object object, Method activityMethod) {
        this.object = object;
        this.activityMethod = activityMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e("111111111", "invoke: "+method.getName() );
        return activityMethod.invoke(object,args);
    }
}

所以我们将对象和函数传递进来。因为我们Demo的函数的入参和onOnclick 入参一样。所以可以直接这么调用。

生成 OnClickListener的代理对象并且设置上去

java 复制代码
Object proxy = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new ListenerInvocationHandler(context, method));
                    view.setOnClickListener((View.OnClickListener) proxy);

和反射进行对比

我们只是将

java 复制代码
view.setOnClickListener(new View.OnClickListener() {
                       @Override
                       public void onClick(View v) {
                           try {
                               method.invoke(context,v);
                           } catch (IllegalAccessException e) {
                               e.printStackTrace();
                           } catch (InvocationTargetException e) {
                               e.printStackTrace();
                           }
                       }
                   });

替换成了:

java 复制代码
Object proxy = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new ListenerInvocationHandler(context, method));
                    view.setOnClickListener((View.OnClickListener) proxy);

这么写好处就是看起来没有了OnClickListener,但是代码是有的。

使用

java 复制代码
  @OnClick(ids = {R.id.tvBtn1,R.id.tvBtn2,R.id.tvBtn3})
    public void demo(View view){
        Toast.makeText(this,"---------",Toast.LENGTH_SHORT).show();
    }

结束

因为是调用函数,所以说 demo函数是私有的,就会报错。这种可以功能抽离成一个class的,就可以用APT去生成class,而不是使用反射调用。比如说现在的viewbinding 就会基于xml去生成一个class文件。而我们这种思路却是需要在需要的代码里面主动调用一次:InjetUtils.injectClick(this),而且标记的函数也只能有一个。

相关推荐
何盖(何松影)25 分钟前
Android T startingwindow使用总结
android
小李飞飞砖2 小时前
Android 依赖注入框架详解
android
SUNxuetian2 小时前
【Android Studio】升级AGP-8.6.1,Find Usage对Method失效的处理方法!
android·ide·gradle·android studio·安卓
阿华的代码王国2 小时前
【Android】搭配安卓环境及设备连接
android·java
__water3 小时前
RHA《Unity兼容AndroidStudio打Apk包》
android·unity·jdk·游戏引擎·sdk·打包·androidstudio
一起搞IT吧5 小时前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@5 小时前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组7 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19967 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸7 小时前
Flutter 生命周期完全指南
android·flutter·ios