通过反射或代理通过运行时注解处理多个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),而且标记的函数也只能有一个。

相关推荐
似霰6 小时前
AIDL Hal 开发笔记2----AIDL HAL 实例分析light hal
android·framework·hal
—Qeyser7 小时前
Flutter 颜色完全指南
android·flutter·ios
2501_916008898 小时前
iOS 上架需要哪些准备,账号、Bundle ID、证书、描述文件、安装测试及上传
android·ios·小程序·https·uni-app·iphone·webview
摘星编程10 小时前
React Native for OpenHarmony 实战:DatePickerAndroid 日期选择器详解
android·react native·react.js
花卷HJ12 小时前
Android 沉浸式全屏实践:主题 + 状态栏文字颜色完整方案
android
花卷HJ13 小时前
Android 项目中 BaseActivity 封装实践(支持 ViewBinding、PermissionUtils动态权限、加载弹窗和跳转动画)
android
消失的旧时光-194315 小时前
Android 接入 Flutter(Add-to-App)最小闭环:10 分钟跑起第一个混合页面
android·flutter
城东米粉儿15 小时前
android StrictMode 笔记
android
Zender Han15 小时前
Flutter Android 启动页 & App 图标替换(不使用任何插件的完整实践)
android·flutter·ios
童无极15 小时前
Android 弹幕君APP开发实战01
android