前言
Android注解在之前都没用过,只知道最简单的 @override,最新心血来潮想学习下,找了一些资料,将学到的知识做下记录。
一、注解的含义
Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
著名的butterknife框架,在早期的时候,使用的应该是运行时注解,所以,在效率性能方面会有影响。但是后期,JakeWharton修改为编译期注解,这样对程序的运行就不存在性能问题了。
二、元注解:修饰注解的注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
以上是@Override的注解源码,它本身是一个注解,但是它的定义上还有 @Target 和 @Retention 在修饰它, 像这两个用来 专门修饰注解的注解 就叫做元注解。
其中,@Target用于注解对象的作用范围,一对多的关系,可指明多个适用对象(value 是特殊的key值,调用时可直接设置对应的值,其他参数或有多个参数时需写上参数“名称 = 值")
@Target(ElementType.TYPE, ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface XTest {
- // String name() default "";//默认值
long value() default 1000;
- // int id() ;//调用时候,需要写上参数“名称=值”
}
// 调用
- //@XTest(id = 1, name = "111", value = 100)
@XTest(100)
public class ReflectDemoActivity extends AppCompatActivity {
......
}
参数 | 作用范围 |
---|---|
ElementType.TYPE | 类、接口、枚举、注解类型 |
ElementType.FIELD | 类成员属性(构造方法、方法、成员变量) |
ElementType.METHOD | 方法 |
ElementType.PARAMETER | 参数 |
ElementType.CONSTRUCTOR | 方法构造参数 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
ElementType.PACKAGE | 包 |
ElementType.TYPE_PARAMETER | 类型参数 |
ElementType.TYPE_USE | 类型使用说明 |
@Retention用于注解保留的生命周期,其中SOURCE < CLASS < RUNTIME(可以通过 ASM 插件查看对应状态)
参数 | 作用周期 |
---|---|
RetentionPolicy.SOURCE | 只在源码中有效,编译时抛弃,如@Override |
RetentionPolicy.CLASS | 编译期注解, 编译时由编译器保留,但 Java 虚拟机(JVM)会忽略 |
RetentionPolicy.RUNTIME | 运行期注解,由 JVM 保留,因此运行时环境可以使用它 |
@IntDef 用于IDE语法检查(源码注解)
若出现需要对入参进行类型限定,如限定为资源ID、布局ID等类型参数,将参数类型直接给定int即可。
然而,我们可以利用Android为我们提供的语法检查注解,来辅助进行更为直接的参数类型检查与提示,如图片资源ID用@DrawableRes,亦可自定义语法检查注解。
package com.example.practicedemo.javademo.inject;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import com.example.practicedemo.R;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class ReflectDemo {
// 耗内存
enum WeekDay{
SUNDAY, MONDAY
}
private static final int SUNDAY = 0;
private static final int MONDAY = 1;
@WekDay private static int mCurrentIntDay;
private static WeekDay mCurrentDay;
@IntDef({SUNDAY, MONDAY})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
@interface WekDay{}
public static void main(String[] args) {
//setDrawable(1);
setDrawable(R.drawable.ic_launcher_background);
setCurrentDay(WeekDay.SUNDAY);
// setCurrentIntDay(1);
setCurrentIntDay(SUNDAY);
}
public static void setCurrentDay(WeekDay currentDay) {
mCurrentDay = currentDay;
}
public static void setCurrentIntDay(@WekDay int currentIntDay) {
mCurrentIntDay = currentIntDay;
}
public static void setDrawable(@DrawableRes int id) {}
}
三、注解的应用场景
根据注解的保留级别不同,对注解的使用自然存在不同场景,以下是场景案例:
级别–》技术 | 说明 |
---|---|
源码—APT(+ IDE语法检查) | 在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。 |
字节码—字节码增强 | 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。 |
运行时—反射 | 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。 |
源码------注解处理器
- main目录下创建一个Java库模块,命名为compiler
- 此模块中创建一个类继承AbstractProcessor——MyProcessor
- 注解处理器的注册流程: main目录下新建目录resources–》META-INF.services
–》再创建文件javax.annotation.processing.Processor,文件中写入注解程序的全类名即可(com.example.compiler.MyProcessor)
package com.example.compiler;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
// 指定处理注解(可不指定)
@SupportedAnnotationTypes("com.example.practicedemo.javademo.inject.XTest")
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "==============================");
return false;
}
}
四、自定义注解
4.1 自定义编写规则
- 注解类型的声明,使用**@interface** 和元注解进行描述。
- 注解的元素是以 接口方法 的形式体现的(但它真的是元素),方法声明不能包含任何参数或throws,只能用public或默认(default)访问权修饰。
- 方法的返回类型必须是基本数据类型、String、Class、枚举、注解或这些类型的数组,方法可以有默认值(如果没有默认值,必须在属性中设置;Class默认值不能为null)
- 如果自定义注解只有一个参数,参数的名字如果是自定义那么,在调用时候,需要写上参数“名称=值”
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface XTest {
String name() default "";//默认值
long value() default 1000;
int id() ;//调用时候,需要写上参数“名称=值”
}
- 如果参数有且只有一个,且参数名字是value,则,调用时候,可不写参数名称
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBindView {
@IdRes int value() ;
}
//调用时:
@MyBindView(R.id.tv_test)
public TextView tv_test;
4.2 自定义运行时注解
自定义注解(4.1规则中最后一项) + 注解处理器(会用到java反射机制)------类似于傻瓜式ButterKnife。
注释处理器:
public class MyButterKnife {
public static void bindView(Activity activity){
Class<? extends Activity> clazz = activity.getClass();
// getDeclaredFields------只能获得自己的成员
// getFields------获得自己+父类的成员(不包括private)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("名称: " + field.getName());
// 判断此类是否被MyBindView注解申明
if (field.isAnnotationPresent(MyBindView.class)) {
MyBindView annotation = field.getAnnotation(MyBindView.class);
if (annotation != null) {
try {
field.setAccessible(true);//设置访问权限,允许操作private属性
field.set(activity, activity.findViewById(annotation.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}else {
}
}
}
}
自定义注释调用:
@MyBindView(R.id.tv_test)
public TextView tv_test;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo_reflect);
MyButterKnife.bindView(this);
Test();
}
private void Test() {
tv_test.setText("自定义注释成功~~~~");
}
4.3 通过自定义注解与反射实现页面跳转的参数注入
在这之前需要先梳理一下关于反射的相关知识,参考了反射知识博客
package com.example.practicedemo.javademo.inject.jumptask;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.practicedemo.R;
import com.example.practicedemo.javademo.inject.InjectUtils;
import java.util.ArrayList;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
ArrayList<UserParcelable> userParcelableList = new ArrayList<>();
userParcelableList.add(new UserParcelable("parceleable1"));
Intent intent = new Intent(this, SecondActivity.class)
.putExtra("name", "namevalue")
.putExtra("attr","attr")
.putExtra("array", new int[]{1, 2, 3, 4, 5, 6})
.putExtra("userParcelable", new UserParcelable("userParcelable"))
.putExtra("userParcelables", new UserParcelable[]{new UserParcelable("userParcelables")})
.putExtra("users",new UserSerializable[]{new UserSerializable("sericlizable")})
.putExtra("strs",new String[]{"1","2"})
.putParcelableArrayListExtra("userParcelableList", userParcelableList);
startActivity(intent);
}
}
package com.example.practicedemo.javademo.inject.jumptask;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.practicedemo.javademo.inject.InjectUtils;
import java.util.Arrays;
import java.util.List;
public class SecondActivity extends AppCompatActivity {
@Autowired
String name;
@Autowired("attr")
String attr;
@Autowired
int[] array;
@Autowired
UserParcelable userParcelable;
@Autowired
UserParcelable[] userParcelables;
@Autowired
List<UserParcelable> userParcelableList;
@Autowired("users")
UserSerializable[] userSerializables;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.injectAutowired(this);
System.out.println(toString());
}
@Override
public String toString() {
return "SecondActivity{" +
"name='" + name + '\'' +
", attr='" + attr + '\'' +
", array=" + Arrays.toString(array) +
", userParcelable=" + userParcelable +
", userParcelables=" + Arrays.toString(userParcelables) +
", userParcelableList=" + userParcelableList +
", userSerializables=" + Arrays.toString(userSerializables) +
'}';
}
}
package com.example.practicedemo.javademo.inject;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.view.View;
import com.example.practicedemo.javademo.inject.jumptask.Autowired;
import com.example.practicedemo.javademo.inject.jumptask.InjectView;
import java.lang.reflect.Field;
import java.util.Arrays;
public class InjectUtils {
public static void injectAutowired(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//获得数据
Intent intent = activity.getIntent();
Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
//获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
// 判断属性是否被Autowired注解声明
if (field.isAnnotationPresent(Autowired.class)) {
Autowired autowired = field.getAnnotation(Autowired.class);
//获得key
String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();
if (extras.containsKey(key)) {
Object obj = extras.get(key);
// Parcelable数组类型不能直接设置,其他的都可以.
//获得数组单个元素类型
Class<?> componentType = field.getType().getComponentType();
//当前属性是数组并且是 Parcelable(子类)数组
if (field.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)) {
Object[] objs = (Object[]) obj;
//创建对应类型的数组并由objs拷贝
Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
obj = objects;
}
field.setAccessible(true);
try {
// 反射时第一个参数是在哪个对象上设置属性或调用方法
field.set(activity, obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
package com.example.practicedemo.javademo.inject.jumptask;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 此处注解作用是标识 作用在成员属性 作用在运行时--RUNTIME
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
五、代理模式
三个对象:
抽象接口
代理对象------Agent
真实对象------Impl
5.1 静态代理
/**
* 代理抽象角色: 定义了服务的接口
*/
public interface Teach {
void teach();
}
public class Person1 implements Teach {
@Override
public void teach() {
System.out.println("Person1 teach");
}
}
public class Agent implements Teach {
private final Teach teach;
public Agent(Teach teach) {
this.teach = teach;
}
//....前置处理
public void before() {
System.out.println("Agent before");
}
//....后置处理
public void after() {
System.out.println("Agent after");
}
@Override
public void teach() {
before();
teach.teach();
after();
}
}
public class Impl {
public static void main(String[] args) {
//静态代理
Person1 person1 = new Person1();
Agent agent = new Agent(person1);
agent.teach();
}
}
每个代理类只能为一个接口服务,若Person新增功能则需再添加Agent,会使得程序中有很多代理类,因此就有了动态代理,一个代理类实现全部代理功能
5.2 动态代理
public interface Study {
void study();
}
public class Person2 implements Teach, Study {
@Override
public void teach() {
System.out.println("Person2 teach");
}
@Override
public void study() {
System.out.println("Person2 study");
}
}
Person2 person2 = new Person2();
// 动态代理
// InvocationHandler的作用------回调
Object o = Proxy.newProxyInstance(Impl.class.getClassLoader(),
new Class[]{Teach.class, Study.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke");
return method.invoke(person2, args);
}
});
Teach teach = (Teach)o;
teach.teach();
六、Retrofit简易版实现——注解+反射+动态代理的结合
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
String value();
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
String value() default "";
}
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {
String value();
}
public interface CustomWeatherApi {
@POST("/v3/weather/weatherInfo")
Call postWeather(@Field("city") String city, @Field("key") String key);
@GET("/v3/weather/weatherInfo")
Call getWeather(@Query("city") String city, @Query("key") String key);
}
package com.example.practicedemo.javademo.inject.customretrofit;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
public class CustomRetrofit {
final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
final Call.Factory callFactory;
final HttpUrl baseUrl;
CustomRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
}
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//解析这个method 上所有的注解信息
ServiceMethod serviceMethod = loadServiceMethod(method);
//args:
return serviceMethod.invoke(args);
}
});
}
// 请求多次从缓存中处理
private ServiceMethod loadServiceMethod(Method method) {
//先不上锁,避免synchronized的性能损失
ServiceMethod result = serviceMethodCache.get(method);
if (result != null) return result;
//多线程下,避免重复解析,
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
/**
* 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。
*/
public static final class Builder {
private HttpUrl baseUrl;
//Okhttp->OkhttClient
private okhttp3.Call.Factory callFactory; //null
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = factory;
return this;
}
public Builder baseUrl(String baseUrl) {
this.baseUrl = HttpUrl.get(baseUrl);
return this;
}
public CustomRetrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
return new CustomRetrofit(callFactory, baseUrl);
}
}
}
// 解析方法中的所有注解信息:记录请求类型/参数/请求地址
public class ServiceMethod {
private final Call.Factory callFactory;
private final String relativeUrl;
private final boolean hasBody;
private final ParameterHandler[] parameterHandler;
private FormBody.Builder formBuild;
HttpUrl baseUrl;
String httpMethod;
HttpUrl.Builder urlBuilder;
public ServiceMethod(Builder builder) {
baseUrl = builder.customRetrofit.baseUrl;
callFactory = builder.customRetrofit.callFactory;
httpMethod = builder.httpMethod;
relativeUrl = builder.relativeUrl;
hasBody = builder.hasBody;
parameterHandler = builder.parameterHandler;
//如果是有请求体,创建一个okhttp的请求体对象
if (hasBody) {
formBuild = new FormBody.Builder();
}
}
public Object invoke(Object[] args) {
/**
* 1 处理请求的地址与参数
*/
for (int i = 0; i < parameterHandler.length; i++) {
ParameterHandler handlers = parameterHandler[i];
//handler内本来就记录了key,现在给到对应的value
handlers.apply(this, args[i].toString());
}
//获取最终请求地址
HttpUrl url;
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
url = urlBuilder.build();
//请求体
FormBody formBody = null;
if (formBuild != null) {
formBody = formBuild.build();
}
Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();
return callFactory.newCall(request);
}
// get请求, 把 k-v 拼到url里面
public void addQueryParameter(String key, String value) {
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
urlBuilder.addQueryParameter(key, value);
}
//Post 把k-v 放到 请求体中
public void addFiledParameter(String key, String value) {
formBuild.add(key, value);
}
public static class Builder {
private final CustomRetrofit customRetrofit;
private final Annotation[] methodAnnotations;
private final Annotation[][] parameterAnnotations;
ParameterHandler[] parameterHandler;
private String httpMethod;
private String relativeUrl;
private boolean hasBody;
public Builder(CustomRetrofit customRetrofit, Method method) {
this.customRetrofit = customRetrofit;
//获取方法上的所有的注解
methodAnnotations = method.getAnnotations();
//获得方法参数的所有的注解 (一个参数可以有多个注解,一个方法又会有多个参数)
parameterAnnotations = method.getParameterAnnotations();
}
public ServiceMethod build() {
/**
* 1 解析方法上的注解, 只处理POST与GET
*/
for (Annotation methodAnnotation : methodAnnotations) {
if (methodAnnotation instanceof POST) {
//记录当前请求方式
this.httpMethod = "POST";
//记录请求url的path
this.relativeUrl = ((POST) methodAnnotation).value();
// 是否有请求体
this.hasBody = true;
} else if (methodAnnotation instanceof GET) {
this.httpMethod = "GET";
this.relativeUrl = ((GET) methodAnnotation).value();
this.hasBody = false;
}
}
/**
* 2 解析方法参数的注解
*/
int length = parameterAnnotations.length;
parameterHandler = new ParameterHandler[length];
for (int i = 0; i < length; i++) {
// 一个参数上的所有的注解
Annotation[] annotations = parameterAnnotations[i];
// 处理参数上的每一个注解
for (Annotation annotation : annotations) {
//可以加一个判断:如果httpMethod是get请求,现在又解析到Filed注解,可以提示使用者使用Query注解
if (annotation instanceof Field) {
//得到注解上的value: 请求参数的key
String value = ((Field) annotation).value();
parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
} else if (annotation instanceof Query) {
String value = ((Query) annotation).value();
parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);
}
}
}
return new ServiceMethod(this);
}
}
}
// 记录请求参数的key
public abstract class ParameterHandler {
abstract void apply(ServiceMethod serviceMethod, String value);
static class QueryParameterHandler extends ParameterHandler {
String key;
public QueryParameterHandler(String key) {
this.key = key;
}
//serviceMethod: 回调
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addQueryParameter(key,value);
}
}
static class FiledParameterHandler extends ParameterHandler {
String key;
public FiledParameterHandler(String key) {
this.key = key;
}
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addFiledParameter(key,value);
}
}
}