自定义注解,打造自己的框架-下下篇

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用
    该系列源码在https://github.com/huangyuanlove/AndroidAnnotation
    前几篇介绍了@BindView@ClickResponder@LongClickResponder的实现,这一篇介绍一下@IntentValue@UriValue的实现。

前提

我们知道执行Intent.putExtra()方法时,实际上是把数据塞进Bundle中的,并且该方法有不同的重载类型,方便开发这存储不同的数据。当我们从中取数据时,需要调用不同的方法来获取不同类型的数据,这就需要我们在生成辅助代码时,根据注解字段的类型来调用不同的方法。

而Uri中的能传递的参数只有有限的几种类型,最常见的就是String和int

声明注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface IntentValue {

int DEFAULT_TYPE = -1;
int PARCELABLE_OBJECT = 1;
int PARCELABLE_ARRAY_OBJECT = 2;
int PARCELABLE_ARRAYLIST_OBJECT = 3;
int SERIALIZABLE_OBJECT = 4;


String key();
int type() default DEFAULT_TYPE;
}

这里在注解类中声明了几种类型,来配合注解使用,主要是为了在生成辅助代码时简单,因为javax.lang.model.type.TypeKind只声明了有限的几种类型,其余的需要我们自己取判断。

1
2
3
4
5
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface UriValue {
String key();
}

处理注解逻辑

我们还是使用上一篇中的注解处理器,需要添加两个属性

1
2
private Map<TypeElement, List<Element>> intentValueMap = new HashMap<>();
private Map<TypeElement, List<Element>> uriValueMap = new HashMap<>();

getSupportedAnnotationTypes方法中添加支持

1
2
set.add(IntentValue.class.getCanonicalName());
set.add(UriValue.class.getCanonicalName());

process方法中加入我们的注解处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
intentValueMap.clear();
uriValueMap.clear();

Set<? extends Element> intentValueSet = roundEnvironment.getElementsAnnotatedWith(IntentValue.class);
Set<? extends Element> uriValueSet = roundEnvironment.getElementsAnnotatedWith(UriValue.class);

collectIntentValueInfo(intentValueSet);
collectUriValueMapInfo(uriValueSet);

private void collectIntentValueInfo(Set<? extends Element> elements) {
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
List<Element> elementList = intentValueMap.get(typeElement);
if (elementList == null) {
elementList = new ArrayList<>();
intentValueMap.put(typeElement, elementList);
}
elementList.add(element);
}
}


private void collectUriValueMapInfo(Set<? extends Element> elements) {
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
List<Element> elementList = uriValueMap.get(typeElement);
if (elementList == null) {
elementList = new ArrayList<>();
uriValueMap.put(typeElement, elementList);
}
elementList.add(element);
}
}

上面的这些收集信息的工作都差不多,把我们需要的数据存放在map中。

generateCode()方法中调用

1
2
generateIntentValueCode();
generateUriValueCode();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void generateIntentValueCode() {
for (TypeElement typeElement : intentValueMap.keySet()) {
MethodSpec.Builder methodBuilder = generateParseBundleMethodCode(typeElement);

List<Element> elements = intentValueMap.get(typeElement);
for (Element element : elements) {
processorIntentValue(element, methodBuilder);
}
}

}

private void generateUriValueCode() {
for (TypeElement typeElement : uriValueMap.keySet()) {
MethodSpec.Builder methodBuilder = generateParseUriMethodCode(typeElement);

List<Element> elements = uriValueMap.get(typeElement);
for (Element element : elements) {
processorUriValue(element, methodBuilder);
}
}

}

我们先来看一下比较简单的处理Uri参数的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void processorUriValue(Element element, MethodSpec.Builder methodBuilder) {
VariableElement variableElement = (VariableElement) element;

//注解的字段名字
String varName = variableElement.getSimpleName().toString();

UriValue uriValue = variableElement.getAnnotation(UriValue.class);

//获取到注解的key,从Uri中获取对应的值
methodBuilder.addStatement("temp = uri.getQueryParameter($S)", uriValue.key());

//如果是String类型,直接赋值
if (element.asType().toString().equals("java.lang.String")) {
methodBuilder.addStatement("target.$L=temp",varName);
}else {
//其他类型需要转换一下,然后再赋值
switch (element.asType().getKind()){
case BOOLEAN:
methodBuilder.addStatement("target.$L=Boolean.valueOf(temp)",varName);
break;
case INT:
methodBuilder.addStatement("target.$L= Integer.valueOf(temp)",varName);
break;
case DOUBLE:
methodBuilder.addStatement("target.$L= Double.valueOf(temp)",varName);
break;
case FLOAT:
methodBuilder.addStatement("target.$L= Float.valueOf(temp)",varName);
break;

case LONG:
methodBuilder.addStatement("target.$L= Long.valueOf(temp)",varName);
break;

}
}
}

基于同样的方式,我们来处理一下IntentValue,因为Bundle中可以存储的数据类型较多,方法比较长,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

private void processorIntentValue(Element element, MethodSpec.Builder methodBuilder) {
VariableElement variableElement = (VariableElement) element;

//注解字段的名字
String varName = variableElement.getSimpleName().toString();
//注解字段的类型名
String varType = variableElement.asType().toString();


IntentValue intentValue = variableElement.getAnnotation(IntentValue.class);


methodBuilder.beginControlFlow("if(bundle.containsKey($S))", intentValue.key());

if (intentValue.type() == IntentValue.DEFAULT_TYPE) {

//基本数据类型可以直接判断,不需要类型名
switch (element.asType().getKind()) {
case BOOLEAN:
methodBuilder.addStatement("target.$L = bundle.getBoolean($S)", varName, intentValue.key());
break;


case SHORT:
methodBuilder.addStatement("target.$L = bundle.getShort($S)", varName, intentValue.key());
break;

case BYTE:
methodBuilder.addStatement("target.$L = bundle.getByte($S)", varName, intentValue.key());
break;

case INT:
methodBuilder.addStatement("target.$L = bundle.getInt($S)", varName, intentValue.key());
break;

case CHAR:
methodBuilder.addStatement("target.$L = bundle.getChar($S)", varName, intentValue.key());
break;

case LONG:
methodBuilder.addStatement("target.$L = bundle.getLong($S)", varName, intentValue.key());
break;

case FLOAT:
methodBuilder.addStatement("target.$L = bundle.getFloat($S)", varName, intentValue.key());
break;

case DOUBLE:
methodBuilder.addStatement("target.$L = bundle.getDouble($S)", varName, intentValue.key());
break;

//ARRAY类型分好多中,需要类型的名字具体判断
case ARRAY:
switch (element.asType().toString()) {
case "byte[]":
methodBuilder.addStatement("target.$L = bundle.getByteArray($S)", varName, intentValue.key());
break;
case "short[]":

methodBuilder.addStatement("target.$L = bundle.getShortArray($S)", varName, intentValue.key());
break;
case "boolean[]":
methodBuilder.addStatement("target.$L = bundle.getBooleanArray($S)", varName, intentValue.key());
break;
case "int[]":
methodBuilder.addStatement("target.$L = bundle.getIntArray($S)", varName, intentValue.key());
break;
case "long[]":
methodBuilder.addStatement("target.$L = bundle.getLongArray($S)", varName, intentValue.key());
break;
case "char[]":
methodBuilder.addStatement("target.$L = bundle.getCharArray($S)", varName, intentValue.key());
break;
case "java.lang.CharSequence[]":
methodBuilder.addStatement("target.$L = bundle.getCharSequenceArray($S)", varName, intentValue.key());
break;
case "float[]":
methodBuilder.addStatement("target.$L = bundle.getFloatArray($S)", varName, intentValue.key());
break;
case "double[]":
methodBuilder.addStatement("target.$L = bundle.getDoubleArray($S)", varName, intentValue.key());
break;
case "java.lang.String[]":
methodBuilder.addStatement("target.$L = bundle.getStringArray($S)", varName, intentValue.key());
break;
default:
methodBuilder.addStatement("target.$L = ($L) bundle.getParcelableArray($S)", varName, varType, intentValue.key());
break;

}
break;

//其余的会被当做`DECLARED`类型
case DECLARED:
switch (element.asType().toString()) {
case "java.util.ArrayList<java.lang.Integer>":
methodBuilder.addStatement("target.$L = bundle.getIntegerArrayList($S)", varName, intentValue.key());
break;
case "java.lang.CharSequence":

methodBuilder.addStatement("target.$L = bundle.getCharSequence($S)", varName, intentValue.key());
break;
case "java.util.ArrayList<java.lang.CharSequence>":
methodBuilder.addStatement("target.$L = bundle.getCharSequenceArrayList($S)", varName, intentValue.key());
break;
case "java.lang.String":
methodBuilder.addStatement("target.$L = bundle.getString($S)", varName, intentValue.key());
break;
case "java.util.ArrayList<java.lang.String>":
methodBuilder.addStatement("target.$L = bundle.getStringArrayList($S)", varName, intentValue.key());
break;
default:

break;
}
break;
}
} else {
//这里的就是方便通过类型或者类型名来判断的,通过我们自己定义的类型来做判断
switch (intentValue.type()) {
case IntentValue.SERIALIZABLE_OBJECT:
methodBuilder.addStatement("target.$L = ($L) bundle.getSerializable($S)", varName, varType, intentValue.key());
break;
case IntentValue.PARCELABLE_OBJECT:
methodBuilder.addStatement("target.$L = bundle.getParcelable($S)", varName, intentValue.key());
break;
case IntentValue.PARCELABLE_ARRAY_OBJECT:

methodBuilder.addStatement(" android.os.Parcelable[] temp = bundle.getParcelableArray($S)", intentValue.key());
methodBuilder.beginControlFlow("if(temp!=null && temp.length>0)");
methodBuilder.addStatement("target.$L = new $L[temp.length]", varName, varType.substring(0, varType.length() - 2));
methodBuilder.beginControlFlow("for(int i = 0 ; i < temp.length;i++)");
methodBuilder.addStatement("target.$L[i] = ($L) temp[i]", varName, varType.substring(0, varType.length() - 2));
methodBuilder.endControlFlow();
methodBuilder.endControlFlow();


break;
case IntentValue.PARCELABLE_ARRAYLIST_OBJECT:
methodBuilder.addStatement("target.$L = bundle.getParcelableArrayList($S)", varName, intentValue.key());

break;
}
}
methodBuilder.endControlFlow();

}

给使用者提供调用方法

还是在api模块中的ViewInjector类中添加如下方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
static final Map<Class<?>, Method> BUNDLES = new LinkedHashMap<>();

public static void parseBundle(Activity activity) {
try {
Method method = findParseBundleMethodForClass(activity.getClass(),activity.getClass());
method.invoke(null,activity,activity.getIntent()==null?null:activity.getIntent().getExtras());
}catch (Exception e){
e.printStackTrace();
}
}

public static void parseBundle(Fragment fragment) {
try {
Method method =findParseBundleMethodForClass(fragment.getClass(),fragment.getClass());
method .invoke(null,fragment,fragment.getArguments());

}catch (Exception e){
e.printStackTrace();
}
}

private static Method findParseBundleMethodForClass(Class<?> cls,Class clazz) {
Method parseBundle = BUNDLES.get(cls);
if (parseBundle == null) {
try {
Class<?> bindingClass = Class.forName(cls.getName() + "$ViewInjector");
parseBundle= bindingClass.getDeclaredMethod("parseBundle",clazz, Bundle.class);
BUNDLES.put(cls, parseBundle);
} catch (Exception e) {
e.printStackTrace();
}
}
return parseBundle;
}


static final Map<Class<?>, Method> URIS = new LinkedHashMap<>();

public static void parseUri(Activity activity) {
try {
Method method = findParseUriMethodForClass(activity.getClass(),activity.getClass());
method.invoke(null,activity,activity.getIntent()==null?null:activity.getIntent().getData());
}catch (Exception e){
e.printStackTrace();
}
}


private static Method findParseUriMethodForClass(Class<?> cls,Class clazz) {
Method parseUri = URIS.get(cls);
if (parseUri == null) {
try {
Class<?> bindingClass = Class.forName(cls.getName() + "$ViewInjector");
parseUri= bindingClass.getDeclaredMethod("parseUri",clazz, Uri.class);
URIS.put(cls, parseUri);
} catch (Exception e) {
e.printStackTrace();
}
}
return parseUri;
}

使用

只需要在对应的类中调用ViewInjector.parseBundle(this);ViewInjector.parseUri(this);方法就好了

其实应该把UriValue当成练手让大家自己写的。。。。


以上


自定义注解,打造自己的框架-下下篇
https://blog.huangyuanlove.com/2019/12/09/自定义注解,打造自己的框架-下下篇/
作者
HuangYuan_xuan
发布于
2019年12月9日
许可协议
BY HUANG兄