Java夯实基础(Android注解全解析)

我们继续我们上一节内容 Java夯实基础(注解一)往下走……

上一节呢我们介绍了什么是注解,然后还说了它的使用方法,还遗漏了一点点内容,废话不多说了哈,开撸~~~

上一节说了,定义注解的注解叫:元注解,于是我们认识了一下@Retention:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
     String name();
     String info() default "Hello EveryBody!";
}

还有两个注解我们没有讲,那就是@Documented跟
@Target。

1、@Documented注解

此注解表示的是文档化,可以在生成doc文档的时候添加注解,也没有啥可讲的。

2、@Target注解
@Target注解表示的是一个Annotation的使用范围,例如:之前定义的MyAnnotation,可以在任意的位置上使用(因为我们没有给出其使用范围的注解)。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE
}
范围描述
ElementType.TYPE只能在类或接口或枚举上使用
ElementType .METHOD只能在方法上使用
PARAMETER在参数上使用
CONSTRUCTOR在构造方法上使用
LOCAL VARIABLE在局部变量上使用
ANNOTATION TYPE在注解上使用
PACKAGE在包中使用
filed在类成员变量中使用

例如我们这里的:

package com.yasin.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
     String name();
     String info() default "Hello EveryBody!";
}

@Target(ElementType.TYPE)表示在类上使用,我们的程序也是没问题的,如果我们改为@Target(ElementType.method)我们再试试:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

我们编译器直接报错了:

这里写图片描述

好啦~到此,我们注解的基础部分就算是结束了,接下来我们来实战实战了,我们结合我们android注解来试试水,小伙伴跟紧啦~~~~!

先看看我们要实现的效果,我们在android中的findviewbyid就可以这样写了:

@ViewInject(R.id.id_tradingpwddetails_pic)
    private ImageView iv_cardImg;
    @ViewInject(R.id.id_tradingpwddetails_name)
    private TextView tv_cardName;
    @ViewInject(R.id.id_tradingpwddetails_no)
    private TextView tv_cardNo;
    @ViewInject(R.id.id_tradingpwddetails_ssv)
    private SlideSwitchView ssv_open;
    @ViewInject(R.id.id_tradingpwddetails_pwd)
    private View viewTBypWD;

我们给一个view设置点击事件就可以这样了:

/**
     * 点击事件
     */
    @OnClick({
            R.id.id_tradingpwddetails_setting,
            R.id.id_tradingpwddetails_update
    })
    public void OnClick(View view) {
        int id = view.getId();
        if (id == R.id.id_tradingpwddetails_setting) {
        } else if (id == 
        }
    }

一、ViewInject

1、首先我们定义一个叫ViewInject的注解,因为我们的注解需要用在成员变量上使用,所以我们声明Target为filed,因为我们需要在程序运行的时候用到注解,所以我们将其retention设置为runtime。

package com.yasin.annotationdemo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by leo on 17/3/21.
 */
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
}

我们需要传入一个id,所以我们需要定义一个int类型的属性。

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

好啦~我们定义一个布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.yasin.annotationdemo.MainActivity">

    <TextView
        android:id=@+id/id_tv_hello
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</RelativeLayout>

布局很简单,就是一个helloword,然后给了一个id。

然后就是我们的Activity了:

public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.id_tv_hello)
    private TextView mTextView;

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

也是很简单,就是定义了一个mTextView然后使用了ViewInject注解,把id值给了textview,这样就ok了吗???哈哈~~太单纯了哈,我们才刚刚定义好注解,下面我们就通过反射来实现下它。

2、反射实现view的注入

我们现在是有了id跟注解了,然后我们什么时候给view注入呢?得我们告诉程序才行额,所以我们定义一个工具类叫:

/**
 * Created by leo on 17/3/21.
 */

public class AnnoUtils {

    public static void inject(Activity activity){
        //do our things
    }
}

然后我们的Activity就只需要告诉AnnoUtils什么时候给view注入就可以了:

public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.id_tv_hello)
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //开始给view注入
        AnnoUtils.inject(this);
    }
}

好啦~~ 我们下面就实现下我们AnnoUtils里面的inject方法了:

package com.yasin.annotationdemo.annotation;

import android.app.Activity;
import android.util.Log;
import android.view.View;

import java.lang.reflect.Field;

/**
 * Created by leo on 17/3/21.
 */

public class AnnoUtils {

    private static final String TAG = "AnnoUtils";

    public static void inject(Activity activity) {
        if (activity == null) return;
        try {
            //获取当前activity的镜像
            Class<?> clazz = activity.getClass();
            //获取当前activity中所有的字段
            Field[] fields = clazz.getDeclaredFields();
            //开始遍历所有的字段
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
                    //判断当前字段是否支持该注解
                    if (field.isAnnotationPresent(ViewInject.class)) {
                        //获取ViewInject注解对象
                        ViewInject annotation = field.getAnnotation(ViewInject.class);
                        if (annotation != null) {
                            int id = annotation.value();
                            View view = activity.findViewById(id);
                            if (view != null) {
                                //把view赋给字段filed
                                field.setAccessible(true);
                                field.set(activity, view);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, e.getMessage());
        }
    }
}

我们修改一下我们的mTextView的内容:

public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.id_tv_hello)
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //开始给view注入
        AnnoUtils.inject(this);
        mTextView.setText("hello yasin!");
    }
}

可以看到,我们没有findviewbyid然后就直接使用mTextView。

运行效果:

这里写图片描述

好啦~ 我们的findviewbyid已经用我们的注解方式干掉了,然后我们实现一下点击事件:

二、点击事件的注入

知道了如果去注入findview,那么onclick事件无非就是把注解加载method上,那么问题来了,我们都知道,我们给一个view设置点击事件的时候是这样的:

mTextView3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

我们需要实现的结果:

/**
     * 点击事件
     */
    @OnClick({
            R.id.id_tradingpwddetails_setting,
            R.id.id_tradingpwddetails_update
    })
    public void OnClick(View view) {
        int id = view.getId();
        if (id == R.id.id_tradingpwddetails_setting) {
        } else if (id == 
        }
    }

要让系统调我们的OnClick方法,我们该怎么做呢???

三、认识动态代理模式

在此之前呢,我们了解java中的一种设计模式(动态代理模式),听起来有点高大上哦,让我们来认识一下它吧,感觉自己好多天都没剪头发了,等我先去剪个头发哈~~~

于是我们创建一个主题叫Subject:

package com.yasin.proxy;

public interface Subject {
    void beforeHaircut();
    void afterHaircut();
    void haircut(String opt);
}

然后我们具体的去实现下它:
HairCut.java:

package com.yasin.proxy;

public class HairCut implements Subject{

    @Override
    public void beforeHaircut() {
        // TODO Auto-generated method stub
        System.out.println(我觉得我应该要去剪个头发了~~~);
    }

    @Override
    public void afterHaircut() {
        // TODO Auto-generated method stub
        System.out.println(嗯嗯!!帅呆了~~);
    }

    @Override
    public void haircut(String opt) {
        System.out.println(opt);
    }
}

既然是剪头发对吧,我们得有一个理发师:

HairDresserHandler.java:

package com.yasin.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class HairDresserHandler implements InvocationHandler{
    private Subject subject;

    public HairDresserHandler(Subject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        subject.beforeHaircut();
        Object obj=method.invoke(subject, args);
        subject.afterHaircut();
        return obj;
    }
}

理发师肯定得问要剪啥样子的头发的对吧,所以我们传递一个
subject个给理发师。

然后我们就可以去剪头发啦~~~

我们创建一个测试类:

package com.yasin.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        //谁要去剪头发
        Subject subject=new HairCut();
        //指定一个理发师
        InvocationHandler handler=new HairDresserHandler(subject);
        //理发师拿出剪刀
        Subject proxySubject = (Subject)Proxy.newProxyInstance(subject.getClass().getClassLoader(), 
                new Class[]{Subject.class},handler);
        //理发师咔咔咔剪完啦
        proxySubject.haircut(小伙子,头发剪完了,30块!);
    }
}

然后我们运行代码:

console打印:

我觉得我应该要去剪个头发了~~~
小伙子,头发剪完了,30块!
嗯嗯!!帅呆了~~

好啦~~ 终于剪了个头发回来了,下面我们回到我们的代码,如何让系统调onClick方法的时候,调用我们写的xxxClick方法呢?

我们在测试类中调用了剪头发方法:

//理发师咔咔咔剪完啦
        proxySubject.haircut("小伙子,头发剪完了,30块!");

这里就相当于系统调用onClickListener的onClick方法,然后我们只需要在真正剪头发(也就是理发师的invoke方法中)调用一下我们的xxxClick方法就可以了,可能有点抽象哈,下面我们来实现一下。

@Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        subject.beforeHaircut();
        Object obj=method.invoke(subject, args);
        subject.afterHaircut();
        return obj;
    }

我们开动了:

OnClick.java:

package com.yasin.annotationdemo.annotation;

import android.view.View;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by leo on 17/3/21.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
    int[]value();
    String listenerSetter() default setOnClickListener;
    Class listenerType() default View.OnClickListener.class;
    String methodName() default onClick;
}

代理处理类(理发师)
ViewHandler.java:

package com.yasin.annotationdemo.annotation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * Created by leo on 17/3/21.
 */

public class ViewHandler implements InvocationHandler {
    private Object object;
    private Method mMethod;
    private String methodName;
    public ViewHandler(Object object,Method method,String methodName){
        this.object=object;
        this.mMethod=method;
        this.methodName=methodName;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //当系统调用onclick方法的时候,我们就调用我们在activity中定义的方法
        if(method.getName().equals(methodName)){
            Object obj = mMethod.invoke(object, args);
            return obj;
        }
        return null;
    }
}

然后就是我们的:
AnnoUtils.java:

package com.yasin.annotationdemo.annotation;

import android.app.Activity;
import android.util.Log;
import android.view.View;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by leo on 17/3/21.
 */

public class AnnoUtils {

    private static final String TAG = "AnnoUtils";

    public static void inject(Activity activity) {
        if (activity == null) return;
        try {
            //获取当前activity的镜像
            Class<?> clazz = activity.getClass();
            //获取当前activity中所有的字段
            Field[] fields = clazz.getDeclaredFields();
            //开始遍历所有的字段
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
                    //判断当前字段是否支持该注解
                    if (field.isAnnotationPresent(ViewInject.class)) {
                        //获取ViewInject注解对象
                        ViewInject annotation = field.getAnnotation(ViewInject.class);
                        if (annotation != null) {
                            int id = annotation.value();
                            View view = activity.findViewById(id);
                            if (view != null) {
                                //把view赋给字段filed
                                field.setAccessible(true);
                                field.set(activity, view);
                            }
                        }
                    }
                }
            }

            //获取activity中所有的方法
            Method[] methods=clazz.getDeclaredMethods();
            if(methods!=null&&methods.length>0){
                //遍历所有的方法
                for (Method method:methods) {
                    //找到带有OnClick注解标记的方法
                    if(method.isAnnotationPresent(OnClick.class)){
                        OnClick annotation=method.getAnnotation(OnClick.class);
                        //获取所有的id
                        int[] ids = annotation.value();
                        //遍历所有的id
                        for (int id: ids) {
                            //找到相应的view
                            View view=activity.findViewById(id);
                            if(view!=null){
                                method.setAccessible(true);
                                //获取onclicklistener镜像
                                Class<?> listenerType = annotation.listenerType();
                                //获取view中的setOnClickListener方法名字
                                String listenerSetter = annotation.listenerSetter();
                                //系统调用onClick方法名
                                String methodName = annotation.methodName();
                                //创建代理对象帮助类
                                ViewHandler handler=new ViewHandler(activity,method,methodName);
                                //获取onClickListener代理对象
                                Object onClickListener=Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, handler);
                                //获取view的setOnClickListener方法
                                Method setOnListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                                //实现setOnClickListener方法
                                setOnListenerMethod.invoke(view,onClickListener);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, e.getMessage());
        }
    }
}

最后测试我们的代码:

package com.yasin.annotationdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.yasin.annotationdemo.annotation.AnnoUtils;
import com.yasin.annotationdemo.annotation.OnClick;
import com.yasin.annotationdemo.annotation.ViewInject;

public class MainActivity extends Activity {
    @ViewInject(R.id.id_tv_hello)
    private TextView mTextView;
    @ViewInject(R.id.id_tv_hello2)
    private TextView mTextView2;
    @ViewInject(R.id.id_tv_hello3)
    private TextView mTextView3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //开始给view注入
        AnnoUtils.inject(this);
        mTextView.setText(hello yasin!);
        mTextView2.setText(hello yasin2!);
        mTextView3.setText(hello yasin3!);

    }
    @OnClick({R.id.id_tv_hello,R.id.id_tv_hello2,R.id.id_tv_hello3})
    public void onClick(View view){
        TextView tv= (TextView) view;
        Toast.makeText(this,tv.getText().toString(),Toast.LENGTH_SHORT).show();
    }
}

然后运行代码:

这里写图片描述

好啦~~~本节还是有点长啊,不过我们还是实现了我们的效果,我们一篇博客写下来,感觉注解太tm爽了,有木有???
但是呢?我们可以看到我们写的代码,其中的判断跟遍历很是很多的,与其这样一个一个找,还不如在activity中定义好都不用找,所以考虑到程序的性能等方面,还是不推荐大家使用注解的。
最后附上项目的github链接:

https://github.com/913453448/AnnotationDemo

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值