Android中的自定义注解(反射实现-运行时注解)

2023-05-16

预备知识:
Java注解基础
Java反射原理
Java动态代理

一、布局文件的注解
我们在Android开发的时候,总是会写到setContentView方法,为了避免每次都写重复的代码,我们需要使用注解来代替我们做这个事情,只需要在类Activity上声明一个ContentView注解和对应的布局文件就可以了。

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewUtils.injectContentView(this);
    }
}

从上面可以看到,上面代码在MainActivity上面使用到了ContentView注解,下面我们来看看ContentView注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

这个注解很简单,它有一个int的value,用来存放布局文件的id,另外它注解的对象为一个类型,需要说明的是,注解的生命周期会一直到运行时,这个很重要,因为程序是在运行时进行反射的,我们来看看ViewUtils.injectContentView(this)方法,它进行的就是注解的处理,就是进行反射调用setContentView()方法。

    public static void injectContentView(Activity activity) {
        Class a = activity.getClass();
        if (a.isAnnotationPresent(ContentView.class)) {
            // 得到activity这个类的ContentView注解
            ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
            // 得到注解的值
            int layoutId = contentView.value();
            // 使用反射调用setContentView
            try {
                Method method = a.getMethod("setContentView", int.class);
                method.setAccessible(true);
                method.invoke(activity, layoutId);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

如果对Java注解比较熟悉的话,上面代码应该很容易看懂。

二、字段的注解
除了setContentView之外,还有一个也是我们在开发中必须写的代码,就是findViewById,同样,它也属于简单但没有价值的编码,我们也应该使用注解来代替我们做这个事情,就是对字段进行注解。

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.btn1)
    private Button mButton1;
    @ViewInject(R.id.btn2)
    private Button mButton2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewUtils.injectContentView(this);
        ViewUtils.injectViews(this);
    }
}

上面我们看到,使用ViewInject对两个Button进行了注解,这样我们就是不用写findViewById方法,看上去很神奇,但其实原理很简单。我们先来看看ViewInject注解。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int value();
}

这个注解也很简单,就不说了,重点就是注解的处理了。

    public static void injectViews(Activity activity) {
        Class a = activity.getClass();
        // 得到activity所有字段
        Field[] fields = a.getDeclaredFields();
        // 得到被ViewInject注解的字段
        for (Field field : fields) {
            if (field.isAnnotationPresent(ViewInject.class)) {
                // 得到字段的ViewInject注解
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                // 得到注解的值
                int viewId = viewInject.value();
                // 使用反射调用findViewById,并为字段设置值
                try {
                    Method method = a.getMethod("findViewById", int.class);
                    method.setAccessible(true);
                    Object resView = method.invoke(activity, viewId);
                    field.setAccessible(true);
                    field.set(activity, resView);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }
    }

上面的注释很清楚,使用的也是反射调用findViewById函数。

三、事件的注解
在Android开发中,我们也经常遇到setOnClickListener这样的事件方法。同样我们可以使用注解来减少我们的代码量。

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewUtils.injectContentView(this);
        ViewUtils.injectEvents(this);
    }

    @OnClick({R.id.btn1, R.id.btn2})
    public void clickBtnInvoked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                Toast.makeText(this, "Button1 OnClick", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn2:
                Toast.makeText(this, "Button2 OnClick", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="#70DBDB"
    android:orientation="vertical"
    tools:context="statusbartest.hpp.cn.statusbartest.MainActivity">
    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test1"/>

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test2"/>
</LinearLayout>

可以看到,上面我们没有对Button调用setOnClickListener,但是当我们点击按钮的时候,就会回调clickBtnInvoked方法,这里我们使用的就是注解来处理的。下面先来看看OnClick注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface OnClick {
    int[] value();
}

可以看到这个注解使用了一个自定义的注解。

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
    Class listenerType();
    String listenerSetter();
    String methodName();
}

下面来看看注解的处理。

    public static void injectEvents(Activity activity) {
        Class a = activity.getClass();
        // 得到Activity的所有方法
        Method[] methods = a.getDeclaredMethods();
        for (Method method : methods) {
            // 得到被OnClick注解的方法
            if (method.isAnnotationPresent(OnClick.class)) {
                // 得到该方法的OnClick注解
                OnClick onClick = method.getAnnotation(OnClick.class);
                // 得到OnClick注解的值
                int[] viewIds = onClick.value();
                // 得到OnClick注解上的EventBase注解
                EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);
                // 得到EventBase注解的值
                String listenerSetter = eventBase.listenerSetter();
                Class<?> listenerType = eventBase.listenerType();
                String methodName = eventBase.methodName();
                // 使用动态代理
                DynamicHandler handler = new DynamicHandler(activity);
                Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);
                handler.addMethod(methodName, method);
                // 为每个view设置点击事件
                for (int viewId : viewIds) {
                    try {
                        Method findViewByIdMethod = a.getMethod("findViewById", int.class);
                        findViewByIdMethod.setAccessible(true);
                        View view  = (View) findViewByIdMethod.invoke(activity, viewId);
                        Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        setEventListenerMethod.setAccessible(true);
                        setEventListenerMethod.invoke(view, listener);
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

            }

        }
    }

这个代码相对上面的要复杂一些,它使用到了动态代理,关于动态代理的基本用法可以看看前面我提到的预备知识。

public class DynamicHandler implements InvocationHandler {
    private final HashMap<String, Method> methodMap = new HashMap<String, Method>(
            1);
    // 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏
    private WeakReference<Object> handlerRef;
    public DynamicHandler(Object object) {
        this.handlerRef = new WeakReference<Object>(object);
    }

    public void addMethod(String name, Method method) {
        methodMap.put(name, method);
    }
    // 当回到OnClickListener的OnClick方法的时候,它会调用这里的invoke方法
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        // 得到activity实例
        Object handler = handlerRef.get();
        if (handler != null) {
            // method对应的就是回调方法OnClick,得到方法名
            String methodName = method.getName();
            // 得到activtiy里面的clickBtnInvoked方法
            method = methodMap.get(methodName);
            if (method != null) {
                // 回调clickBtnInvoked方法
                return method.invoke(handler, objects);
            }
        }
        return null;
    }
}

基本的看注释就应该差不多了。

欢迎关注微信公众号:DroidMind
精品内容独家发布平台


呈现与博客不一样的技术干货

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android中的自定义注解(反射实现-运行时注解) 的相关文章

  • ros realsense D435i摄像头配置

    ros realsense D435i摄像头配置 之前在ubunut18中配置了ROS环境 xff0c 现在需要在此基础上 xff0c 配置D435i相机的SDK和ROS包 一 参考 https github com IntelRealSe
  • 树莓派4B(ubuntu mate系统)使用d435i运行vins

    树莓派4B xff08 ubuntu mate系统 xff09 使用d435i运行vins 提示本文为随手笔记 xff0c 并不严谨 xff0c 可参考 xff1a 博客和博客进行配置 树莓派 ubuntu mate 20系统安装ros的步
  • 树莓派3B+增加虚拟内存

    普通ubuntu系统增加虚拟内存参考 xff1a https blog csdn net weixin 42405819 article details 117886557 编译opencv时 xff0c 卡在了91 不动 xff0c 按照
  • ROS主机从机设置

    ROS主机从机设置 在ROS基础上 xff0c 配置主机和从机 xff0c 实现主机和从机的话题联通 配置hosts 在主机和从机的 etc hosts文件中 xff0c 配置如下内容 xff08 也许主机只需要写入 master xff1
  • VINS、MAVROS等的坐标系统一(草稿,未得出明确结果)

    由于不同算法之间的坐标系不同 xff0c 导致计算的结果混乱 xff0c 该博客的目的是记录和统一不同算法之间的坐标系 xff0c 保证坐标系的统一 一 VINS算法 vins算法 xff0c 使用D435I相机 该坐标方向为 xff1a
  • ROS学习笔记9-创建ros消息和服务

    该节内容主要来自于官方文档的两个小节 xff1a 1 使用rosed来编辑 2 创建ros消息的服务 先来看rosed xff1a rosed rosed命令是rosbash的一部分 xff0c 使用rosed可以直接编辑包中的一个文件 x
  • CUAV RTK初步使用体验和感受

    记录使用RTK进行无人机定位并操作的使用体验 一 RTK定位设置 使用的是CUAV制作发售的RTK xff0c 型号为C9P xff0c 目前该产品已经下架 xff0c 上新了C9PS 并不需要太多的设置 xff0c 在飞控接入RTK后 x
  • 记录一次WIN11开机在登录页面循环的问题

    记录一次由于未进行win密码设置 xff0c 导致开机后卡在登录界面无法登录进去的问题 最后完美解决了 1 背景 开机后 xff0c 显示用户登录界面 xff0c 但是和以往不同 xff0c 没有了密码输入框 xff0c 只有一个 登录 按
  • Ubuntu中增加串口的缓冲区

    增加串口缓冲区 xff0c 用于尝试解决px4 ros中显示TX溢出的问题 以下大部分代码和内容均有CHATGPT生成 xff0c 代码已经通过验证 一 通信溢出问题 PX4和ROS的通信溢出可能是由于几个原因造成的 以下是一些建议 xff
  • GRBL代码使用与修改

    下载官方grbl代码 xff0c 并进行修改使其正确 1 问题 xff1a 购买了328p单片机 xff0c 购买了grbl的底板 xff0c 但是烧录之后无法正常使用 问题发现 xff1a 引脚错误了 xff0c 官方代码中的引脚需要修改
  • ubuntu使用rc.local开机自启USB设备读写权限

    无人机飞控为ACM0 xff0c 串口USB为USB0 xff0c 使用rc为其开机自启给与权限 大部分由chagpt生成 xff0c 已验证 首先 xff0c 创建一个新的 etc rc local 文件 xff08 如果尚不存在 xff
  • Friendlycore增加inodes数量

    背景 xff1a 为Nanopim1安装了core系统 xff0c tf卡大小64G xff0c 安装后正常扩展到了整个tf卡 xff0c 但是在安装hass的docker显示磁盘空间不够 xff0c 最终发现是inode被用完了 其ino
  • UORB

    转载地址 xff1a http blog arm so armteg pixhawk 183 0503 html Pixhawk 飞控 系统是基于ARM的四轴以上飞行器的飞行控制器 xff0c 它的前身是PX4 IMU xff0c Pixh
  • rCS启动脚本分析

    转载地址 xff1a http wellmakers com p 61 401 还有一篇很重要的文章 xff0c 讲述了整个系统的大致启动过程 xff1a http blog chinaunix net uid 29786319 id 43
  • PID通俗解释

    转载地址 xff1a http blog gkong com liaochangchu 117560 ashx PID是比例 积分 微分的简称 xff0c PID控制的难点不是编程 xff0c 而是控制器的参数整定 参数整定的关键是正确地理
  • Kali Linux中安装Xfce的步骤2-1

    Kali Linux默认安装的是桌面环境是Gnome xff0c 可以通过以下方法在Kali Linux中安装Xfce xff0c 从而修改其桌面环境 1 Xfce介绍 Xfce是XForms Common Enviroment的简写 其可
  • STM32CubeMX教程之简介及基本使用

    STM32CubeMX是意法半导体推出的图形化配置工具 xff0c 通过傻瓜化的操作便能实现相关配置 xff0c 最终能够生成C语言代码 xff0c 支持多种工具链 xff0c 比如MDK IAR For ARM TrueStudio等 尤
  • Linux 文件流与目录流管理

    Linux 应用开发 04 文件流与目录流管理 本课目标 1 编程目标 xff1a a 实现磁盘文件的拷贝操作 b 缓冲区类型对磁盘真正写入操作的影响 2 理解文件流操作以及缓冲区概念 3 掌握ansi c 文件流相关操作函数 4 理解与掌
  • tensorflow InvalidArgumentError: Cannot serialize protocol buffer of type tensorflow.GraphDef 错误分析

    训练nfm模型 xff0c 每2000个step进行保存 一开始模型训练正常 xff0c 但是在使用tf train Saver的save方法保存模型时出现了如下错误 xff1a tensorflow python framework er
  • 7年厨师想转行程序员

    7年厨师想转行 xff0c 跟我学java 可是工资只有7千我惊呆了 xff0c 我还以为7年厨师工资至少1万多呢 看来每个行业都有工资高也有工资低的 xff0c 他可能属于厨师里工资低的吧

随机推荐