Android -- 自定义注解学习笔记

参考文章
http://blog.csdn.net/johnny901114/article/details/52662376
http://blog.csdn.net/johnny901114/article/details/52664112
http://blog.csdn.net/johnny901114/article/details/52672188
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html

这个demo没有在项目中涉及,只是用来理解java的注解及使用,并不是一个完整的框架。通过这个demo,能掌握注解的相关知识,并且提高了自己的逼格,O__O “…主要是提高了逼格。


一、

在项目中用到了mvp,封装了一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EditorActivity extends BindingActivity<ActivityEditorBinding, EditorPresenter> implements EditorContract.View{

@Override
protected EditorPresenter createPresenter() {
return new EditorPresenter(this);
}

@Override
protected int createLayoutId() {
return R.layout.activity_editor;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public void showMessage(String message) {
GlobalToast.show(message);
}
}

上面创建了一个简单的activity, 再createPresenter方法中创建了Presenter在createLayoutId方法中传入了布局的id。每个activity都要有这两个方法,写起来还挺繁琐的。有没有更好的方法呢?

要是像butterknife和dragger一样通过注解注入该多好

二、

一个叫刀一个剑的,这个demo的名字就叫fork把,和butterknife一样,都和吃有关系

先看一下完成之后的activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ForkLayoutId(R.layout.activity_main)
@ForkPresenter(MainPresenter.class)
public class MainActivity extends ForkActivity<ActivityMainBinding, MainPresenter> implements MainContract.View {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Fork.bind(this);

binding.rvText.setText("haha");
mvpPresenter.run();
}

@Override
public void showMessage(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}

费了大半天的劲,少了俩方法,呵呵

三、

说了一堆废话,记录一下实现吧!

1、首先再android studio 中创建一个java library(一定要是java 项目,不然android项目可找不到项目需要的包)
module 名字就叫 fork-annotations 吧,这里准备主要放用到的注解

首先,创建今天的第一个注解 名字叫ForkLayoutId

1
2
3
4
5
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ForkLayoutId {
int value();
}

注解跟普通的java接口的定义很像,但接口是给程序员看的,而注解是给计算机看的,所以这里的interface前面加上了一个@

@Retention

是用来标记这个注解的生命周期,有以下几种

  • RetentionPolicy.SOURCE : 注解只保留在源文件中
  • RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
  • RetentionPolicy.RUNTIME : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

@Target

是用来标记注解所修饰的属性

  • ElementType.TYPE:说明该注解只能被声明在一个类前。
  • ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
  • ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
  • ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
  • ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
  • ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
  • ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
  • ElementType.PACKAGE:说明该注解只能声明在一个包名前。

int value();

这个就是接口的参数了

再定义另一个接口,不, 注解!

1
2
3
4
5
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ForkPresenter {
Class value();
}

2、
两个注解定义完之后,接下来就是最重要的类

```
1
在程序编译时,就通过 ```     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)

这个方法,处理我们的注解

处理的过程看似复杂,其实很简单。获得我们需要的值,动态生成java代码,生成代码也是一个库javapoet,就直接贴代码吧,更直观些。(javapoet的用法这里就不说了)

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
@SupportedAnnotationTypes({"org.fork.annotation.ForkLayoutId", "org.fork.annotation.ForkPresenter"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SuppressWarnings("All")
public class ForkProcessor extends AbstractProcessor {
private String packageName;
private String activityName;
private TypeMirror activityClass;

private int layoutId;
private String presenterName;

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() > 0) {
parseBindViews(annotations, roundEnv);
javaPoet();
}
return true;
}

private void javaPoet() {
MethodSpec getPresenter = MethodSpec.methodBuilder("getPresenter")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.returns(Object.class)
.addParameter(TypeName.OBJECT, "activity")
.addStatement("return new " + presenterName + "((" + activityName + ")activity)")
.build();

MethodSpec getLayoutId = MethodSpec.methodBuilder("getLayoutId")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.returns(int.class)
.addStatement("return " + layoutId)
.build();

TypeSpec clazz = TypeSpec.classBuilder(activityName + "$$Provider")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(Provider.class)
.addMethod(getPresenter)
.addMethod(getLayoutId)
.build();

JavaFile.Builder builder = JavaFile
.builder(packageName, clazz);
JavaFile javaFile = builder.build();

try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}

private void parseBindViews(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(ForkLayoutId.class)) {
if (element.getKind() == ElementKind.CLASS) {
layoutId = element.getAnnotation(ForkLayoutId.class).value();
activityName = element.getSimpleName().toString();
activityClass = element.asType();
packageName = element.toString().replace("." + activityName, "");
}
}

for (Element element : roundEnv.getElementsAnnotatedWith(ForkPresenter.class)) {
if (element.getKind() == ElementKind.CLASS) {
try {
presenterName = element.getAnnotation(ForkPresenter.class).value().getSimpleName().toString();
} catch (MirroredTypeException mte) {
presenterName = mte.getTypeMirror().toString().replace(packageName + ".", "");
}
}
}
}
}
  • @SupportedAnnotationTypes 用来指定这里会处理的注解
  • @SupportedSourceVersion(SourceVersion.RELEASE_7) 指定java版本,网上贴子说,这个写成注解兼容性更好,当然,class开头的两句,也可以使用java代码来声明
  • 注意,再这个java库的build.gradle中,我们还要配置一遍java环境
    1
    2
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"

3、这些都写完了,还要加上一个配置文件
再与java 同级,添加 resources / META_INF / services / javax.annotation.processing.Processor 这样一个文件

填写里面的内容

1
org.fork.annotation.ForkProcessor

这是完整的目录结构

Paste_Image.png

ForkProcessor这个文件会报错,但是没什么影响。可能是android studio支持不够好吧,再IntelliJ中不会报错

通过javapoet,编译之后会生成如下代码,当然生成什么都是自己控制的。下面就详细的说一下生成的这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.demo.tiny;

import java.lang.Object;
import org.fork.annotation.Provider;

public final class MainActivity$$Provider implements Provider {
public final Object getPresenter(Object activity) {
return new MainPersenter((MainActivity)activity);
}

public final int getLayoutId() {
return 2130968603;
}
}

我们再activity使用注解,将pressenter的字节码文件和layout的id传入。

我们通过注解能拿到这个activity的名字,也就是上面的

```
1
2
3
加上final 防止复写方法。getLayoutId没什么说的,getPresenter的强转有些蛋疼,一会儿再说。

provider 接口提供了两个方法

public interface Provider {
Object getPresenter(Object obj);
int getLayoutId();
}

1
2
3

费了九牛二虎之力,通过注解,拿到了layout的id并创建了presenter。
下面,我们就要使用他们了。 fork类上场。为了将这个demo封装起来,作为一个三方框架,我新建了一个android library

public final class Fork {

public static void bind(ForkActivity activity) {
    Provider provider = null;
    try {
        try {
            provider = (Provider) Class.forName(activity.getClass().getName() + "$$Provider").newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    if (provider != null) {
        activity.binding = DataBindingUtil.setContentView(activity, provider.getLayoutId());
        activity.mvpPresenter = provider.getPresenter(activity);
    }

}

}

1
2
3
4

通过加载器,创建了Provider的一个实例。这样我们就可以得到layout id 和presenter了

为了实现封装,我创建了一个ForkActivity

public class ForkActivity extends Activity {
protected B binding;
protected P mvpPresenter;
}
`
这里就是为什么要进行强转了,因为想把binding和mvpPresenter这两个属性封装起来,放进父类。但我们自动生成的代码的报名却和ForkActivity 不在同一个包下。总不能把两个属性全公有吧。

此外,还有一个坑,Provider 并不是我们自己生成的,所以不可能知道Activity的名字,这里也就只有写Object 了。会涉及几处的强转。

再Fork.java中传递的是MainActivity,再注解创建presenter是,我们知道这是MainActivity,所以将其强转创建一个presenter,但是mvpPresenter又被抽取再ForkActivity中,我们并不知道实际的activity是Main,所以又强转成ForkActivity,并赋值mvpPresenter。我们再MainActivity中使用mvpPresenter,通过泛型,声明了他的类型是MainActivity

项目地址

https://github.com/LavenderStream/fork