0%

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

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

  • @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的实现,这一篇介绍一下@RouterModule@RouterPath的实现。

前提

在某些情况下,我们不得不为了某些情形而在代码层面妥协,写出了一坨又一坨的反人类代码。比如接手了一个古老的工程,A模块依赖B模块,但是现在的需求需要在B里面打开A页面,或者调用A的方法来做复杂的计算等等,于是就催生了这个注解。。。

声明注解

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)

public @interface RouterModule {
String host();

String schema() default "App";
}
1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface RouterPath {
String value();
}

处理注解逻辑

这里我们新建一个处理器,专门用来处理这种逻辑,也重新生成另外一个辅助类

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

@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
private Elements elementUtils;

private Map<Element, TypeSpecWrapper> typeSpecWrapperMap = new HashMap<>();

@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(RouterModule.class.getCanonicalName());
set.add(RouterPath.class.getCanonicalName());

return set;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

typeSpecWrapperMap.clear();

Set<? extends Element> routerModuleSet = roundEnv.getElementsAnnotatedWith(RouterModule.class);

for (Element element : routerModuleSet) {
RouterModule routerModule = element.getAnnotation(RouterModule.class);
TypeSpecWrapper typeSpecWrapper = typeSpecWrapperMap.get(element);
if (typeSpecWrapper == null) {
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
ClassName hashMapClassName = ClassName.bestGuess("java.util.HashMap");
ClassName methodClassName = ClassName.bestGuess("java.lang.reflect.Method");
ClassName stringClassName = ClassName.bestGuess("java.lang.String");
ParameterizedTypeName routerMapClassName = ParameterizedTypeName.get(hashMapClassName, stringClassName, methodClassName);

ClassName targetClassName = ClassName.get(packageName, element.getSimpleName().toString());

TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(routerModule.schema() + routerModule.host() + "$$Router")
.addField(routerMapClassName, "routerMap", Modifier.PRIVATE)
.addField(targetClassName, "target");
typeSpecWrapper = new TypeSpecWrapper(typeSpecBuilder, "com.huangyuanlove.router");
typeSpecWrapperMap.put(element, typeSpecWrapper);


MethodSpec.Builder constructorBuilder = typeSpecWrapper.getMethodBuilder(MethodSpec.constructorBuilder().toString());
if (constructorBuilder == null) {
constructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addException(Exception.class);
}
constructorBuilder.addStatement("this.target = new $T()", targetClassName)
.addStatement("this.routerMap = new $T()", routerMapClassName);


List<? extends Element> lists = element.getEnclosedElements();
for (Element element1 : lists) {
ExecutableElement temp = (ExecutableElement) element1;

RouterPath routerPath = temp.getAnnotation(RouterPath.class);
if (routerPath != null) {
constructorBuilder.addStatement("this.routerMap.put($S,target.getClass().getMethod($S,$L))", routerPath.value(), element1.getSimpleName().toString(), paramsClassString(temp));
}

}

typeSpecWrapper.putMethodBuilder(constructorBuilder);

ClassName paramWrapperName = ClassName.bestGuess("com.huangyuanlove.view_inject_api.router.RouterParamWrapper");
ClassName routerDelegateName = ClassName.bestGuess("com.huangyuanlove.view_inject_api.router.RouterDelegate");

MethodSpec.Builder invokeBuilder = MethodSpec.methodBuilder("invoke")
.addModifiers(Modifier.PUBLIC)
.addException(Exception.class)
.addParameter(String.class, "path")
.addParameter(paramWrapperName, "paramWrapper")
.addStatement("$T method = this.routerMap.get($L)", methodClassName, "path")
.beginControlFlow("if(method == null)")
.addStatement(" throw new Exception(\"can not find method which map \" +path)")
.endControlFlow()
.addStatement("return $T.invoke(method,target,paramWrapper)", routerDelegateName)
.returns(Object.class);

typeSpecWrapper.putMethodBuilder(invokeBuilder);

}

}


for (Map.Entry<Element, TypeSpecWrapper> entry : typeSpecWrapperMap.entrySet()) {

entry.getValue().writeTo(processingEnv.getFiler());
}

return true;
}

private String paramsClassString(ExecutableElement temp) {

if (temp == null) {
return null;
}

List<? extends VariableElement> parameters = temp.getParameters();

if (parameters == null || parameters.size() == 0) {
return null;
}

String[] result = new String[parameters.size()];

StringBuilder sb = new StringBuilder();

for (int i = 0; i < parameters.size(); i++) {
result[i] = parameters.get(i).asType().toString() + ".class";
sb.append(parameters.get(i).asType().toString() + ".class");
sb.append(",");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
}

主要就是收集注解信息,生成注解类,在调用对应注解路径时,调用api模块中对应的代理类RouterDelegate,最终由使用者调用Router中的RouterBuilder来调用对应路径注解的方法。

给使用者提供调用方法

api模块中,新建RouterParamWrapper作为我们注解参数的包装类

1
2
3
4
5
6
7
8
9
10
11
public class RouterParamWrapper {

private Object []paramArray;

public RouterParamWrapper(Object [] paramArray) {
this.paramArray = paramArray;
}
public Object[] getParamArray() {
return paramArray;
}
}

api模块中,新建RouterDelegate作为我们注解的代理类,实际上也就是一个中间层

1
2
3
4
5
6
public class RouterDelegate {

public static Object invoke(Method method, Object target, RouterParamWrapper paramWrapper) throws Exception{
return method.invoke(target,paramWrapper.getParamArray());
}
}

然后提供给调用者一个调用方法

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
public class Router {

public interface InvokeResultListener<T>{
void onError(Exception e);
void onSuccess( T result);
}

private static final String PACKAGE_NAME = "com.huangyuanlove.router";

public static RouterBuilder to(String path){
return new RouterBuilder(path);
}

public static class RouterBuilder{
private String path;
private Object[] paramArray;

private RouterBuilder(String path) {
this.path = path;
}

public RouterBuilder addParam(Object ... paramArray){
this. paramArray = paramArray;
return this;

}

public void done(){
done(null);
}

public void done(InvokeResultListener listener){

try {
Uri uri = Uri.parse(path);
String schema = uri.getScheme();
String host = uri.getHost();
String path = uri.getPath();

Class routerInject = Class.forName(PACKAGE_NAME +"." + schema + host +"$$Router");
Constructor constructor = routerInject.getConstructor();
constructor.setAccessible(true);
RouterParamWrapper paramWrapper = new RouterParamWrapper(paramArray);

Method invokeMethod = routerInject.getMethod("invoke",String.class,RouterParamWrapper.class);
invokeMethod.setAccessible(true);
Object result = invokeMethod.invoke(constructor.newInstance(),path,paramWrapper);
if(listener!=null){
listener.onSuccess(result);
}
}catch (Exception e){

if(listener!=null){
listener.onError(e);
}
}
}
}
}

这样使用者调用Router.to(String path).addParam(Object ... param).done就可以调用了。

使用

我们可以在一个模块中提供一个功能类,用来暴露出提供给其他模块使用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RouterModule(schema = "App",host = "main")
public class MainProvider {

@RouterPath(value = "/toMain")
public void startMain(Activity context, int id){
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("id",id);
context.startActivity(intent);
}

@RouterPath(value = "/toMainWithResult")
public void startMain(Activity context, String title,int requestCode){
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("title",title);
context.startActivity(intent);
}

@RouterPath(value = "/getInt")
public int getIntValue(String s){
return s.length();
}
}

然后我们就可以在其他模块中使用Router进行任意调用了,比如

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
@ClickResponder(idStr = "toAppMainActivity")
public void toAppMainActivity(View v){

Router.to("App://main/toMain").addParam(this,123).done(new Router.InvokeResultListener() {
@Override
public void onError(Exception e) {
Toast.makeText(EXT_MainActivity.this,e.toString(),Toast.LENGTH_SHORT).show();

}

@Override
public void onSuccess(Object o) {

}
});

}

@ClickResponder(idStr = "invoke_main_method")
public void invokeMainMethod(View v){

Router.to("App://main/getInt").addParam("12345678").done(new Router.InvokeResultListener<Integer>() {
@Override
public void onError(Exception e) {
Toast.makeText(EXT_MainActivity.this,e.toString(),Toast.LENGTH_SHORT).show();
}

@Override
public void onSuccess(Integer result) {
Toast.makeText(EXT_MainActivity.this,result+"",Toast.LENGTH_SHORT).show();

}
});

}

其实我也不想搞出来这种东西,但毕竟是时间紧、任务重,先这么用着吧。

结语

终于把注解这一块写完了,也算是对自己写注解的一个总结吧。


以上