您的位置:首页 > 移动开发 > Android开发

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

2017-03-21 23:21 525 查看
我们继续我们上一节内容 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: