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

相关推荐
技术liul31 分钟前
使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本1)
android·stm32·电脑
_祝你今天愉快2 小时前
Android FrameWork - 开机启动 & Init 进程 初探
android
2501_916007472 小时前
iOS App 上架实战 从内测到应用商店发布的全周期流程解析
android·ios·小程序·https·uni-app·iphone·webview
TimeFine3 小时前
Android 邮件发送日志
android
杨过过儿3 小时前
【Task02】:四步构建简单rag(第一章3节)
android·java·数据库
Wgllss3 小时前
Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景
android·架构·android jetpack
zzywxc7875 小时前
AI 行业应用:金融、医疗、教育、制造业领域的落地案例与技术实现
android·前端·人工智能·chrome·金融·rxjava
sTone873755 小时前
android studio之外使用NDK编译生成android指定架构的动态库
android·c++
胖虎17 小时前
Android 入门到实战(三):ViewPager及ViewPager2多页面布局
android·viewpager·viewpager2
风往哪边走8 小时前
Media3在线本地视频播放器
android