Android中注解的实际运用

版权声明:本文为博主原创文章,转载请声明。 https://blog.csdn.net/qq_32175491/article/details/78261506

首先先把源码添出来:源码下载链接
你的支持是我前进的动力,欢迎star.
接下来进入正题:在学习使用注解之前一定要理解注解的定义,明白注解的各部分构成。文章也主要从这几个方面讲解,后面附带几个小案例。


什么是注解:

  • 百度百科 —— ​java.lang.annotation.Retention可以在您定义Annotation型态时,指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯。
  • 个人理解:注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响.

注解有哪些优缺点?

  • 优点:
    越大的项目,使用注解就越清晰,代码可读性越高,维护起来就越简单。简单来说,通过注解,可以使我们的开发更方便简
  • 缺点: 理解起来有一定的难度……

注解的讲解?

元注解

Java内置的注解有Override, Deprecated, SuppressWarnings等.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

Overrider上的两个注解我们称之为元注解。简单来说就是用来定义注解的注解我们叫做元注解。其作用就是定义注解的作用范围,使用在什么元素上面。

元注解一共有四种:
@Retention,@Target,@Inherited,@Documented

@Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用: 用来描述注解可以在什么地方使用(使用范围)
取值(ElementType):
1. CONSTRUCTOR:用于描述构造器
2. FIELD:用于描述域
3. LOCAL_VARIABLE:用于描述局部变量
4. METHOD:用于描述方法
5. PACKAGE:用于描述包
6. PARAMETER:用于描述参数
7. TYPE:用于描述类、接口(包括注解类型) 或enum声明

@Retention

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:用来描述注解的生命周期(注解的使用范围)

取值(RetentionPoicy):
1. SOURCE:在源文件中有效(即源文件保留)
2. CLASS:在class文件中有效(即class保留)
3. RUNTIME:在运行时有效(即运行时保留)

@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

@Inherited:

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

 注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。 当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

这其中, @Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)


自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

自定义注解的格式:

public @interface 注解名 {定义体}

注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组


案列1(布局文件的注解)

简单的Activity布局设置

注解

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

反射类:

public class ViewUtils {
    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();
            }
        }
    }
}

使用类:

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

案列2(字段注解)

Butterknife相信大家都用过,怎么实现的呢?下面我们来看看如何通过注解反射来实现一个简单的ButterKnife:

注解:

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

反射

  public static void injectViews(Activity activity){
        Class a = activity.getClass();
        Field[] fields = a.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(ViewInject.class)) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                int viewId = viewInject.value();
                try {
                    Method method = a.getMethod("findViewById", int.class);
                    method.setAccessible(true);
                    Object invoke = method.invoke(activity, viewId);
                    field.setAccessible(true);
                    field.set(activity,invoke);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

使用方法:

@ContentView(R.layout.activity_annotation2)
public class Annotation2Activity extends AppCompatActivity {

  @ViewInject(R.id.annotation_btn1)
    Button mButton1;
    @ViewInject(R.id.annotation_btn2)
    Button mButton2;
    @ViewInject(R.id.annotation_text)
    TextView mTextView;

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

案列3(字段注解)

在请求网络数据时,会提供接口地址、请求数据等等一些参数,接下来展示的时如何利用反射和注解来封装我们的请求部分:

枚举

public enum  Host {

    Aserbao_1("https://One"),
    Aserbao_2("https://Two"),
    Aserbao_3("https://Three");

    private String host;
    Host(String host){
        this.host = host;
    }

    public String getHost(){
        return host;
    }
}

参数注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RequestParamsKey {
    String key();
}

类注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RequestParamsUrl {
    //接口地址
    String url();

    //端口
    Host host() default Host.Aserbao_1;

    //缓存
    boolean isCache() default false;
}

输入参数类:

@RequestParamsUrl(url = "getLocation.php", isCache = true, host = Host.Aserbao_2)
public class LocationReq {
    @RequestParamsKey(key = "lat_key")
    public String lat;
    @RequestParamsKey(key = "lan_key")
    public String lan;
}

反射解析类:

public class RequestParam {
    public RequestParam() {
    }
    public static String getParam(Class<?> _clazz,Object _object){
        Class<?> clazz = _clazz;
        Field[] fields = clazz.getDeclaredFields();
        try {
            return requestParam(fields, clazz, _object);
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 获取请求路径
     *
     * @param fields
     * @param clazz
     * @param _object
     * @return
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    private static String requestParam(Field[] fields, Class<?> clazz, Object _object) throws IllegalArgumentException, IllegalAccessException {
        StringBuilder request = new StringBuilder();
        RequestParamsUrl requestParamsUrl = (RequestParamsUrl) clazz.getAnnotation(RequestParamsUrl.class);
        if (requestParamsUrl != null) {
            String url = requestParamsUrl.url();
            boolean isCache = requestParamsUrl.isCache();
            String host = requestParamsUrl.host().getHost();
            request.append(host);
            request.append(url);
            request.append("?");
            System.out.println("请求端口:" + host);
            System.out.println("请求地址:" + url);
            System.out.println("是否缓存:" + isCache);
        }
        for (Field field : fields) {
            RequestParamsKey requestParamsKey = field.getAnnotation(RequestParamsKey.class);
            if (requestParamsKey != null) {
                String key = requestParamsKey.key();
                String Value = (String) field.get(_object);
                request.append(key);
                request.append("=");
                request.append(Value);
                request.append("&");
            }
        }
        request.deleteCharAt(request.length() - 1);
        System.out.println("请求路径:" + request.toString());
        return request.toString();
    }
}

结果展示类:

public class AnnationActivity extends AppCompatActivity {
    public LocationReq mLocation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annation);
    }

    public void btn_one(View view) {
        config();
        Toast.makeText(AnnationActivity.this, RequestParam.getParam(mLocation.getClass(),mLocation), Toast.LENGTH_SHORT).show();
    }
     /**
     * 设置请求参数
     */
    private void config() {
        mLocation = new LocationReq();
        mLocation.lan = "123.09";
        mLocation.lat = "232.34";
    }
}

案列4(事件的注解)

注解:

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

反射

  public static void injectEvents(Activity activity){
        Class<? extends Activity> aClass = activity.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            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);
                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 = aClass.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<>(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 proxy, Method method, Object[] args) 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,args);
            }
        }
        return null;
    }
}

使用方法:

@ContentView(R.layout.activity_annotation2)
public class Annotation2Activity extends AppCompatActivity {

    @ViewInject(R.id.annotation_btn1)
    Button mButton1;
    @ViewInject(R.id.annotation_btn2)
    Button mButton2;
    @ViewInject(R.id.annotation_text)
    TextView mTextView;

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

    @Override
    protected void onResume() {
        super.onResume();
        init();
    }

    private void init() {
        mTextView.setText("The second part is already done");
    }

    @OnClick({R.id.annotation_btn1,  R.id.annotation_btn2})
    public void clickBtnInvoked(View view){
        switch (view.getId()){
            case R.id.annotation_btn1:
                Toast.makeText(this, "toast num one", Toast.LENGTH_SHORT).show();
                break;
            case R.id.annotation_btn2:
                Toast.makeText(this, "toast num two", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

这里写一下ViewUtils的代码:

public class ViewUtils {
    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();
            }
        }
    }

    public static void injectViews(Activity activity){
        Class a = activity.getClass();
        Field[] fields = a.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(ViewInject.class)) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                int viewId = viewInject.value();
                try {
                    Method method = a.getMethod("findViewById", int.class);
                    method.setAccessible(true);
                    Object invoke = method.invoke(activity, viewId);
                    field.setAccessible(true);
                    field.set(activity,invoke);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void injectEvents(Activity activity){
        Class<? extends Activity> aClass = activity.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            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);
                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 = aClass.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();
                    }
                }
            }
        }
    }
}

源码下载链接你的支持是我前进的动力,欢迎star.有问题欢迎留言。

猜你喜欢

转载自blog.csdn.net/qq_32175491/article/details/78261506