• Android利用注解自定义一个超级简单的ButterKnife


    一、概述

      自从Java1.5后,其提供了一个非常强大的功能那就是注解。在普通的开发中可能不会自定义注解,甚至有些时候一个app开发下来完全不需要注解。但是想要在技术方面做一个纵深,自己封装框架,成为高级工程师,专家或者架构师,注解这块的知识是绕不开的。所以学习一下java中的注解对自己的内功修炼是非常有必要的。

      现在主流的开源框架大多数都会用到注解,不同的框架注解的参与程度也不一样。像:EventBus、ButterKnife、Retrofit2等框架其注解的参与程度是比较深的,如果想要在注解方面有更快的提升,建议各位看官去看这些框架的源代码。

      本节会先讲一下java&android中常用的元注解。然后通过两个小例子来系统的说一下注解的具体用法:自定义ButterKnife和获取类似retrofit中interfaceservice中的方法信息,来加强各位看官在注解方面上的理解。

    二、元注解

      网上将Java元注解的文章非常的多,本节只将在作者看来最重要的两个,@target和@retention。其他的大家自行学习。本节的重点在于实践,其理论只是相对较少。

      1.@Target,其定义了注解可以在什么地方使用:如:类、属性、方法、参数、构造方法、局部变量、包声明等。

        @Target注解有以下几种值可以使用,当然,多种值可以组合使用

        a.TYPE:类、接口、枚举声明

        b.FIELD:在属性上声明

        c.METHOD:在方法上声明

        d.PARAMETER:参数参数声明

        e.CONSTRUCTOR:构造方法声明

        f.ANNOTATION_TYPE:注解类型声明

        g.LOCAL_VARIABLE:局部变量声明

        h.PACKAGE:包声明

      2@Retention,其定义了注解在那些阶段是可以用的,如:源码阶段、Class字节码阶段、运行时阶段(虚拟机)

        @Retention有三种阶段可以选择:

        a.RetentionPolicy.SOURCE,注解只存在源码中

        b.RetentionPolicy.CLASS,注解可以一直存活到字节码阶段

        c.RetentionPolicy.RUNTIME,注解可以一直存活,即使程序已经运行起来了

    三、创建一个超级简单的ButterKnife

      本例我们将创建一个非常简单的ButterKnife。利用bind方法进行绑定view并给view赋值,而且还可以选择性的设置view的点击事件。

      类介绍:

      1.BindView.java绑定view的注解

      2.OnClick.java绑定点击事件的注解

      3.ButterKnife.java注解工具

      4.BindViewTestActivity.java测试类

      BindView:

    package com.yw.annotationlib;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 模仿ButterKnife实现View的绑定
     * create by yangwei
     * on 2020-02-24 13:09
     */
    @Target(ElementType.FIELD)//标记该注解运行在字段上
    @Retention(RetentionPolicy.RUNTIME)//标记该字段一直到程序运行时都有效
    public @interface BindView {
        int value() default -1;//View的Id,如:R.id.btn
    }
    

      OnClick.java

    package com.yw.annotationlib;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * create by yangwei
     * on 2020-02-24 13:38
     */
    @Target(ElementType.METHOD)//标记注解应用在方法上
    @Retention(RetentionPolicy.RUNTIME)//标记注解直到运行时都可以存活
    public @interface OnClick {
        int value() default -1;//比较view的id值
    }
    

      ButterKnife.java

    package com.yw.annotationlib;
    
    import android.app.Activity;
    import android.os.Build;
    import android.view.View;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import androidx.annotation.RequiresApi;
    
    /**
     * create by yangwei
     * on 2020-02-24 13:12
     */
    public class ButterKnife {
    
        /**
         * 绑定对象用注解标注的所有方法和属性
         *
         * @param activity
         */
        @RequiresApi(api = Build.VERSION_CODES.N)
        public static void bind(final Activity activity) {
            try {
    
    
                Class clazz = activity.getClass();
                //获取class中的所有的注解
                Method[] methods = clazz.getDeclaredMethods();
                for (final Method method : methods) {
                    final OnClick onClick = method.getDeclaredAnnotation(OnClick.class);
                    if (onClick != null) {
                        final View view = activity.findViewById(onClick.value());
                        method.setAccessible(true);
                        //设置view的点击事件
                        view.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                try {
                                    method.invoke(activity, view);
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                } catch (InvocationTargetException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                }
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    BindView bindView = field.getDeclaredAnnotation(BindView.class);
                    if (bindView != null) {
                        View view = activity.findViewById(bindView.value());
                        field.setAccessible(true);
                        field.set(activity, view);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

      

    BindViewTestActivity.java
    package com.yw.rxjava3demo;
    
    import android.app.Activity;
    import android.os.Build;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.yw.annotationlib.BindView;
    import com.yw.annotationlib.ButterKnife;
    import com.yw.annotationlib.OnClick;
    import com.yw.annotationlib.retrofit.FieldMap;
    import com.yw.annotationlib.retrofit.POST;
    import com.yw.annotationlib.retrofit.RetrofitAnnotationBind;
    
    import java.util.Arrays;
    import java.util.Map;
    
    import androidx.annotation.Nullable;
    import androidx.annotation.RequiresApi;
    
    /**
     * create by yangwei
     * on 2020-02-24 13:21
     */
    public class BindViewTestActivity extends Activity {
        @BindView(R.id.tv_name)
        TextView tv_name;
        @BindView(R.id.btn_click)
        Button btn_click;
    
        @RequiresApi(api = Build.VERSION_CODES.N)
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.bindviewtest_layout);
            ButterKnife.bind(this);
            tv_name.setText("我是杨洛峋,是一个小宝宝");
    
            //测试参数注解和方法注解
            RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this);
            Log.e("获取注解中的请求方式:", serviceMethod.getMethod());
            Log.e("获取请求方法中的值:", serviceMethod.getParams());
    
    
        }
        @POST("POST")
        public void retrofitTest(@FieldMap Map<String,String> map){
    
        }
    
        @OnClick(R.id.btn_click)
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.btn_click:
                    Toast.makeText(BindViewTestActivity.this, "点击事件执行了", Toast.LENGTH_LONG).show();
                    break;
            }
    
        }
    }
    

      xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我是杨洛峋" />
    
        <Button
            android:id="@+id/btn_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击我试试" />
    
    </LinearLayout>

      其最终的结果是TextView和Button按钮成功绑定上了findViewById。且Button按钮可以执行其注解标注的点击事件。  

    四、模仿retrofit获取注解方法参数以及方法注解参数上的值

      本小节的主要内容是操作activty中的一个方法retrofitTest并的到这个方法注解的值和参数注解中的值并返回

      FieldMap.java参数注解

      POST.java方法注解

      RetrofitAnnotationBind.java注解解析器

      FieldMap.java

    package com.yw.annotationlib.retrofit;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 模仿Retrofit的FieldMap参数注解
     * create by yangwei
     * on 2020-02-24 14:03
     */
    @Target(ElementType.PARAMETER)//标记注解附着到参数上
    @Retention(RetentionPolicy.RUNTIME)//标记注解一直到运行时都有效果
    public @interface FieldMap {
        boolean encoded() default false;
    }
    

      POST.java

    package com.yw.annotationlib.retrofit;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * create by yangwei
     * on 2020-02-24 14:01
     */
    @Target(ElementType.METHOD)//在方法上运行
    @Retention(RetentionPolicy.RUNTIME)//标记注解一直能存活到运行时
    public @interface POST {
        String value() default "post_hello";
    }
    

      

    RetrofitAnnotationBind.java
    package com.yw.annotationlib.retrofit;
    
    import android.os.Build;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    import androidx.annotation.RequiresApi;
    
    /**
     * create by yangwei
     * on 2020-02-24 14:06
     */
    public class RetrofitAnnotationBind {
        /**
         * 拿到方法注解和参数注解
         *
         * @param obj
         */
        @RequiresApi(api = Build.VERSION_CODES.N)
        public static ServiceMethod bind(Object obj) {
            ServiceMethod serviceMethod = new ServiceMethod();
            Class clazz = obj.getClass();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                POST post = method.getDeclaredAnnotation(POST.class);
                //说明此方法上有POST注解,拿到post上的值
                if (post != null) {
                    String psotValue = post.value();
                    serviceMethod.setMethod(psotValue);
                    Annotation[][] annotations = method.getParameterAnnotations();
                    if (annotations != null) {
                        StringBuffer sb = new StringBuffer();
                        for (Annotation[] paramsAnnotations : annotations) {
                            for (Annotation annotation : paramsAnnotations) {
                                if (annotation instanceof FieldMap) {
                                    FieldMap fieldMap = (FieldMap) annotation;
                                    sb.append(fieldMap.encoded()).append(",");
                                }
                            }
                        }
                        serviceMethod.setParams(sb.toString());
                    }
                }
            }
            return serviceMethod;
        }
    
        public static class ServiceMethod {
            private String method;
            private String params;
    
            public ServiceMethod() {
            }
    
            public ServiceMethod(String method, String params) {
                this.method = method;
                this.params = params;
            }
    
            public String getMethod() {
                return method;
            }
    
            public void setMethod(String method) {
                this.method = method;
            }
    
            public String getParams() {
                return params;
            }
    
            public void setParams(String params) {
                this.params = params;
            }
        }
    }
    

      测试类中的测试方法,解析的就是这个方法

     @POST("POST")
        public void retrofitTest(@FieldMap Map<String,String> map){
    
        }
    

      本例的最终结果会的到一个ServiceMethod类。此类中存储了POST注解的值和FieldMap注解中的值。

    其打印结果为POST,false

     //测试参数注解和方法注解
            RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this);
            Log.e("获取注解中的请求方式:", serviceMethod.getMethod());
            Log.e("获取请求方法中的值:", serviceMethod.getParams());
    

      

    总结:本节的内容讲到这里就结束了,希望大家能够对注解的使用方式有一点自己的体会。如果想要更深入的了解注解,建议大家去看下相关框架的源代码。

    ps:一旦你深入了,你会发现注解其实法力还是很大的。

  • 相关阅读:
    bzoj3622: 已经没有什么好害怕的了
    BSGS
    LOJ#2320 生成树计数
    ??? cliquers
    生成函数
    洛谷P5206 数树
    01分数规划
    差分约束系统
    51nod1238 最小公倍数之和 V3
    51nod1237 最大公约数之和 V3
  • 原文地址:https://www.cnblogs.com/tony-yang-flutter/p/12357988.html
Copyright © 2020-2023  润新知