我们继续我们上一节内容 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链接: