Android中注解的实际运用

来源:互联网 发布:网络上找不到打印机 编辑:程序博客网 时间:2024/06/05 00:17

      • 什么是注解
      • 注解有哪些优缺点
      • 注解的讲解
        • 元注解
          • Target
          • Retention
          • Documented
          • Inherited
        • 自定义注解
          • 案列1布局文件的注解
          • 案列2字段注解
          • 案列3字段注解
          • 案列4事件的注解

首先先把源码添出来:源码下载链接
你的支持是我前进的动力,欢迎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.有问题欢迎留言。

原创粉丝点击