通过反射或代理通过运行时注解处理多个view的点击事件
  FyeYl0ESQHUh 2023年11月02日 34 0


正文

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

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

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

第一步定义注解

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

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

获取到函数

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里面调用我们被标记的函数。

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的代理对象并且设置上去

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

和反射进行对比

我们只是将

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();
                           }
                       }
                   });

替换成了:

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

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

使用

@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),而且标记的函数也只能有一个。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

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


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
FyeYl0ESQHUh