Java夯实基础(Android注解全解析)
2017-03-21 23:21
525 查看
我们继续我们上一节内容 Java夯实基础(注解一)往下走……
上一节呢我们介绍了什么是注解,然后还说了它的使用方法,还遗漏了一点点内容,废话不多说了哈,开撸~~~
上一节说了,定义注解的注解叫:元注解,于是我们认识了一下@Retention:
还有两个注解我们没有讲,那就是@Documented跟
@Target。
1、@Documented注解
此注解表示的是文档化,可以在生成doc文档的时候添加注解,也没有啥可讲的。
2、@Target注解
@Target注解表示的是一个Annotation的使用范围,例如:之前定义的MyAnnotation,可以在任意的位置上使用(因为我们没有给出其使用范围的注解)。
例如我们这里的:
@Target(ElementType.TYPE)表示在类上使用,我们的程序也是没问题的,如果我们改为@Target(ElementType.method)我们再试试:
我们编译器直接报错了:
好啦~到此,我们注解的基础部分就算是结束了,接下来我们来实战实战了,我们结合我们android注解来试试水,小伙伴跟紧啦~~~~!
先看看我们要实现的效果,我们在android中的findviewbyid就可以这样写了:
我们给一个view设置点击事件就可以这样了:
一、ViewInject
1、首先我们定义一个叫ViewInject的注解,因为我们的注解需要用在成员变量上使用,所以我们声明Target为filed,因为我们需要在程序运行的时候用到注解,所以我们将其retention设置为runtime。
我们需要传入一个id,所以我们需要定义一个int类型的属性。
好啦~我们定义一个布局:
布局很简单,就是一个helloword,然后给了一个id。
然后就是我们的Activity了:
也是很简单,就是定义了一个mTextView然后使用了ViewInject注解,把id值给了textview,这样就ok了吗???哈哈~~太单纯了哈,我们才刚刚定义好注解,下面我们就通过反射来实现下它。
2、反射实现view的注入
我们现在是有了id跟注解了,然后我们什么时候给view注入呢?得我们告诉程序才行额,所以我们定义一个工具类叫:
然后我们的Activity就只需要告诉AnnoUtils什么时候给view注入就可以了:
好啦~~ 我们下面就实现下我们AnnoUtils里面的inject方法了:
我们修改一下我们的mTextView的内容:
可以看到,我们没有findviewbyid然后就直接使用mTextView。
运行效果:
好啦~ 我们的findviewbyid已经用我们的注解方式干掉了,然后我们实现一下点击事件:
二、点击事件的注入
知道了如果去注入findview,那么onclick事件无非就是把注解加载method上,那么问题来了,我们都知道,我们给一个view设置点击事件的时候是这样的:
我们需要实现的结果:
要让系统调我们的OnClick方法,我们该怎么做呢???
三、认识动态代理模式
在此之前呢,我们了解java中的一种设计模式(动态代理模式),听起来有点高大上哦,让我们来认识一下它吧,感觉自己好多天都没剪头发了,等我先去剪个头发哈~~~
于是我们创建一个主题叫Subject:
然后我们具体的去实现下它:
HairCut.java:
既然是剪头发对吧,我们得有一个理发师:
HairDresserHandler.java:
理发师肯定得问要剪啥样子的头发的对吧,所以我们传递一个
subject个给理发师。
然后我们就可以去剪头发啦~~~
我们创建一个测试类:
然后我们运行代码:
console打印:
好啦~~ 终于剪了个头发回来了,下面我们回到我们的代码,如何让系统调onClick方法的时候,调用我们写的xxxClick方法呢?
我们在测试类中调用了剪头发方法:
这里就相当于系统调用onClickListener的onClick方法,然后我们只需要在真正剪头发(也就是理发师的invoke方法中)调用一下我们的xxxClick方法就可以了,可能有点抽象哈,下面我们来实现一下。
我们开动了:
OnClick.java:
代理处理类(理发师)
ViewHandler.java:
然后就是我们的:
AnnoUtils.java:
最后测试我们的代码:
然后运行代码:
好啦~~~本节还是有点长啊,不过我们还是实现了我们的效果,我们一篇博客写下来,感觉注解太tm爽了,有木有???
但是呢?我们可以看到我们写的代码,其中的判断跟遍历很是很多的,与其这样一个一个找,还不如在activity中定义好都不用找,所以考虑到程序的性能等方面,还是不推荐大家使用注解的。
最后附上项目的github链接:
https://github.com/913453448/AnnotationDemo
上一节呢我们介绍了什么是注解,然后还说了它的使用方法,还遗漏了一点点内容,废话不多说了哈,开撸~~~
上一节说了,定义注解的注解叫:元注解,于是我们认识了一下@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
相关文章推荐
- 夯实java基础,深入理解Android设计思想
- java夯实基础系列:注解
- java基础解析系列(六)---注解原理及使用
- Android中Gson解析json数据使用@SerializedName注解与java对象不匹配的字段
- Java基础知识系列之注解解析器
- Java 基础注解全面解析
- Android中Gson解析json数据使用@SerializedName注解与java对象不匹配的字段
- 框架基础——全面解析Java注解
- android 框架基础之java注解
- Android中Gson解析json数据使用@SerializedName注解与java对象不匹配的字段
- 框架的基础--全面解析java注解
- android基础篇------------java基础(11)(文件解析xml and Json )
- Android中Gson解析json数据使用@SerializedName注解与java对象不匹配的字段
- 【框架基础】:全面解析Java注解(一)
- android基础篇------------java基础(11)(文件解析xml and Json )
- 框架基础——全面解析Java注解
- Java夯实基础(注解一)
- Android中Gson解析json数据使用@SerializedName注解与java对象不匹配的字段
- Android 开源项目源码解析 -->公共技术点之 Java 注解 Annotation(四)
- Android框架常用java基础知识:反射,注解,动态代理