Android中的自定义注解

Posted IT界的吉祥物

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android中的自定义注解相关的知识,希望对你有一定的参考价值。

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/51779695
本文出自:【顾林海的博客】

个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!
在这里插入图片描述

前言

目前注解的使用频率还是挺高,像第三方butterknife、数据库Activeandroid等等,通过注解,我们的开发效率得到了明显提高。因此理解注解并熟练使用注解是非常重要的,下面分为两部分,第一部分是注解的介绍,资料来源于网上;第二部分是两个小例子,利用注解+反射分别完成网络请求的封装和数据库操作案例。

什么是注解

注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响.

元注解

ava内置的注解有Override, Deprecated, SuppressWarnings等.
现在查看Override注解的源码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

发现Override注解上面有两个注解, 这就是元注解. 元注解就是用来定义注解的注解.其作用就是定义注解的作用范围, 使用在什么元素上等等, 下面来详细介绍.

元注解共有四种@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:

在请求网络数据时,会提供接口地址、请求数据等等一些参数,接下来展示的时如何利用反射和注解来封装我们的请求部分:

package demo.src.demo.request.host;

public enum Host {

	Host_1("https://L1"), Host_2("https://T2"), Host_3("https://R3");

	private String host;

	Host(String host) {
		this.host = host;
	}

	public String getHost() {
		return host;
	}
}

Host是我们的请求端口。

package demo.src.demo.request.params;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RequestParamsKey {
	String key();
}

package demo.src.demo.request.params;

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

import demo.src.demo.request.host.Host;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RequestParamsUrl {
	//接口地址
	String url();

	//端口
	Host host() default Host.Host_1;

	//缓存
	boolean isCache() default false;
}

RequestParamsKey 注解用于请求的键值对,RequestParamsUrl 注解用于请求的地址、端口以及一些配置参数。

package demo.src.demo.request;

import java.lang.reflect.Field;

import demo.src.demo.request.params.RequestParamsKey;
import demo.src.demo.request.params.RequestParamsUrl;

/**
 * 拼接请求参数
 * 
 * @author glh
 * 
 */
public class RequestParam {

	private RequestParam() {
	}

	/**
	 * 获取request参数
	 * @param _clazz
	 * @param _object
	 * @return
	 */
	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();
	}

}

RequestParam 类用于解析我们自定义的注解并获取所需的参数。

接下来看如何定义我们的请求参数request:

package demo.src.demo.reqBody;

import demo.src.demo.request.host.Host;
import demo.src.demo.request.params.RequestParamsKey;
import demo.src.demo.request.params.RequestParamsUrl;

@RequestParamsUrl(url = "getLocation.php", isCache = true, host = Host.Host_2)
public class LocationReq {
	@RequestParamsKey(key = "lat_key")
	public String lat;
	@RequestParamsKey(key = "lan_key")
	public String lan;
}

package demo.src.demo;

import demo.src.demo.reqBody.LocationReq;
import demo.src.demo.request.RequestParam;

public class demo {

	public LocationReq mLocation;

	public static void main(String[] args) throws ClassNotFoundException {
		demo demo = new demo();
		demo.cofig();
		demo.getRequest();
	}

	/**
	 * 设置请求参数
	 */
	private void cofig() {
		mLocation = new LocationReq();
		mLocation.lan = "123.09";
		mLocation.lat = "232.34";
		
	}
	
	/**
	 * 获取请求路径
	 */
	private void getRequest(){
		System.out.println(RequestParam.getParam(mLocation.getClass(),mLocation));
	}

	
}

输出结果:

请求端口:https://T2
请求地址:getLocation.php
是否缓存:true
请求路径:https://T2getLocation.php?lat_key=232.34&lan_key=123.09
https://T2getLocation.php?lat_key=232.34&lan_key=123.09

栗子2:

下面是通过注解和反射定义数据库的创建等操作:

package com.example.dbannotion.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * 数据库
 * @author glh
 *
 */
public class SqliteHelper extends SQLiteOpenHelper{
	
	private String createTable;
	private String tableName;
	
  public SqliteHelper(Context context, String name, CursorFactory factory, int version,String createTable,String tableName) {
  	super(context, name, factory, version);
  	this.createTable=createTable;
  	this.tableName=tableName;

  }
  /**
   * 创建表
   */
  @Override
  public void onCreate(SQLiteDatabase db) {
  	if(createTable!=""){
  		db.execSQL(createTable);
  	}
     
  }
  /**
   * 更新表
   */
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      db.execSQL( "DROP TABLE IF EXISTS " + tableName );
      onCreate(db);
  }
  /**
   * 更新列
   * @param db
   * @param oldColumn
   * @param newColumn
   * @param typeColumn
   */
  public void updateColumn(SQLiteDatabase db, String oldColumn, String newColumn, String typeColumn){
      try{
          db.execSQL( "ALTER TABLE " +
          		tableName + " CHANGE " +
                  oldColumn + " "+ newColumn +
                  " " + typeColumn
          );
      } catch(Exception e){
          e.printStackTrace();
      }
  }
}

SqliteHelper 用于创建、更新数据库操作。

package com.example.dbannotion.db.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumDB {

	String column();
	
}

package com.example.dbannotion.db.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableDB {
	String table();//表名
	String dbName() default "demoDb.db";//数据库名称
	int version() default 1;//版本号
}

ColumDB 注解用于列数据(键值对),TableDB 注解用于表和数据库的配置参数。

package com.example.dbannotion.db;

import java.lang.reflect.Field;
import java.util.ArrayList;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.example.dbannotion.db.annotation.ColumDB;
import com.example.dbannotion.db.annotation.TableDB;

public abstract class AbstractDB {

	public static final String TAG = "AbstractDB";

	private SQLiteDatabase db;
	private SqliteHelper dbHelper;
	// 版本
	private int version;
	// 数据库名称
	private String dbName;
	// 表名
	private String tableName;
	// 建表语句
	private StringBuilder createBuilder;

	// 插入的数据
	private ContentValues contentValues = new ContentValues();

	// 是否存在数据
	private boolean isColums;

	private ArrayList<String> clumsList = new ArrayList<String>();

	/**
	 * 打开数据库
	 * 
	 * @param _context
	 * @param _clazz
	 * @param _object
	 * @return
	 */
	public AbstractDB open(Context _context, Class<?> _clazz, Object _object) {
		if (paramsDB(_clazz, _object)) {
			dbHelper = new SqliteHelper(_context, dbName, null, version, createBuilder.toString(), tableName);
			db = dbHelper.getWritableDatabase();
			Log.e(TAG, "-------------数据库打开成功!----------");

		} else {
			Log.e(TAG, "-------------数据库打开失败!----------");
		}
		return this;
	}

	/**
	 * 打开数据库时是否进行插入操作
	 * 
	 * @param isInsert
	 * @return
	 */
	public AbstractDB isInsert(boolean isInsert) {
		if (isInsert && isColums) {
			insert();
		}
		return this;
	}

	/**
	 * 创建并检查数据库
	 * 
	 * @param _object
	 * @param _clazz
	 * @return false:失败 true:成功
	 */
	public boolean paramsDB(Class<?> _clazz, Object _object) {
		isColums = false;
		contentValues.clear();
		clumsList.clear();
		createBuilder = new StringBuilder();
		Class<?> clazz = _clazz;
		Field[] fields = clazz.getDeclaredFields();
		createBuilder.append("CREATE TABLE IF NOT EXISTS ");
		/*
		 * 表名
		 */
		TableDB tDb = _clazz.getAnnotation(TableDB.class);
		if (tDb != null) {
			dbName = tDb.dbName();
			tableName = tDb.table();
			version = tDb.version();
			createBuilder.append(tableName);
			createBuilder.append("(");
		} else {
			return false;
		}

		/*
		 * 列
		 */
		for (Field field : fields) {
			ColumDB requestParamsKey = field.getAnnotation(ColumDB.class);
			if (requestParamsKey != null) {
				String key = requestParamsKey.column();
				String value;
				try {
					value = (String) field.get(_object);

					clumsList.add(key);
					if (!value.isEmpty()) {
						contentValues.put(key, value);
					}
					createBuilder.append(key);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				}
				createBuilder.append(" varchar,");
				isColums = true;
			}
		}
		if (!isColums) {
			return false;
		}
		createBuilder.deleteCharAt(createBuilder.length() - 1);
		createBuilder.append(")");
		return true;
	}

	public AbstractDB insert() {
		if (contentValues != null) {
			db.insert(tableName, null, contentValues);
			Log.e(TAG, "-------------数据添加成功!----------");
		} else {
			Log.e(TAG, "-------------数据添加失败!----------");
		}
		return this;
	}

	/**
	 * 获取数据
	 * 
	 * @return
	 */
	public String getDate() {
		StringBuilder dateBuilder = new StringBuilder();
		SQLiteDatabase db = dbHelper.getReadableDatabase();
		Cursor cursor = db.rawQuery("select * from " + tableName, null);
		while (cursor.moveToNext()) {
			if (clumsList != null) {
				for (int i = 0, length = clumsList.size(); i < length; i++) {
					String name = cursor.getString(cursor.getColumnIndex(clumsList.get(i)));
					dateBuilder.append(clumsList.get(i));
					dateBuilder.append("=");
					dateBuilder.append(name);
					dateBuilder.append(",");
				}
				dateBuilder.append("\\n");
			}
		}
		cursor.close();
		if (dateBuilder.length() > 1) {
			dateBuilder.deleteCharAt(dateBuilder.length() - 1);
			return dateBuilder.toString();
		} else {
			Log.e(TAG, "-------------无数据解析!----------");
			return "";
		}
	}

	public void Close() {
		db.close();
		dbHelper.close();
	}

}

AbstractDB 是一个抽象类,用于数据库的操作。

以下是建表操作:

package com.example.dbannotion.table;

import com.example.dbannotion.db.AbstractDB;
import com.example.dbannotion.db.annotation.ColumDB;
import com.example.dbannotion.db.annotation.TableDB;

@TableDB(table = "student",dbName="demo.db",version=1)
public class Student extends AbstractDB {
	@ColumDB(column = "name")
	public String name;
	@ColumDB(column = "age")
	public String age;
	@ColumDB(column = "sex")
	public String sex;
}

创建了一个demo.db数据库,表面为student,版本号1,name,age,sex列。

package com.example.dbannotion;

import com.example.dbannotion.table.Student;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

	private Student student;

	public TextView tv_show;
	public Button btn_get;

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

	private void initView() {
		tv_show = (TextView) findViewById(R.id.tv_show);
		btn_get = (Button) findViewById(R.id.btn_get);
	}

	private void initEvent() {
		btn_get.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				new Handler().post(new Runnable() {

					@Override
					public void run() {
						tv_show.setText(student.getDate());
					}
				});
			}
		});
	}

	private void initDate() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					student = new Student();
					student.age = i + "";
					student.name = "name-" + i;
					if (i % 2 == 0) {
						student.sex = "男";
					} else {
						student.sex = "女";
					}
					student.open(MainActivity.this, student.getClass(), student).isInsert(true);
				}
			}
		}).start();
	}

}

运行结果:

这里写图片描述

以上栗子只是起了一个抛砖迎玉的作用,大家可以定义出适合当前业务场景注解框架。

以上是关于Android中的自定义注解的主要内容,如果未能解决你的问题,请参考以下文章

Java中的自定义注解

从自定义适配器获取片段中的 UI 元素 ID

片段内的自定义列表不起作用

具有获取 json 值的片段中的自定义适配器

片段中的自定义列表适配器

片段中的自定义列表视图。未找到布局