参考文章
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,封装了一下
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
@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
@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();
这个就是接口的参数了
再定义另一个接口,不, 注解!
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ForkPresenter {
Class value();
}
2、
两个注解定义完之后,接下来就是最重要的类 AbstractProcessor
在程序编译时,就通过 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
这个方法,处理我们的注解
处理的过程看似复杂,其实很简单。获得我们需要的值,动态生成java代码,生成代码也是一个库javapoet,就直接贴代码吧,更直观些。(javapoet的用法这里就不说了)
@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环境
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
3、这些都写完了,还要加上一个配置文件
再与java 同级,添加 resources / META_INF / services / javax.annotation.processing.Processor 这样一个文件
填写里面的内容
org.fork.annotation.ForkProcessor
这是完整的目录结构
ForkProcessor这个文件会报错,但是没什么影响。可能是android studio支持不够好吧,再IntelliJ中不会报错
通过javapoet,编译之后会生成如下代码,当然生成什么都是自己控制的。下面就详细的说一下生成的这个类
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的名字,也就是上面的MainActivity
加上final 防止复写方法。getLayoutId没什么说的,getPresenter的强转有些蛋疼,一会儿再说。
provider 接口提供了两个方法
public interface Provider {
Object getPresenter(Object obj);
int getLayoutId();
}
费了九牛二虎之力,通过注解,拿到了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);
}
}
}
通过加载器,创建了Provider的一个实例。这样我们就可以得到layout id 和presenter了
为了实现封装,我创建了一个ForkActivity
public class ForkActivity<B extends ViewDataBinding, P> 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