需求
之前封装了BaseActivity方法, 里面有两个抽象方法
protected abstract @LayoutRes int getContentLayoutId();
/**
* 创建MVP中的Presenter
*
* @return Presenter
*/
protected abstract T createPresenter();
然后在子Activity中实现这两个方法
@Override
protected int getContentLayoutId() {
return R.layout.activity_answering_detail;
}
@Override
protected AnsweringDetailContract.Presenter createPresenter() {
return new AnsweringDetailPresenter();
}
即可完成布局文件的设置和Presenter的创建和关联
现在的目标就是通过注解, 干掉getContentLayoutId和createPresenter这两个方法.只在子类Activity上添加注解即可.
方案1 注解+反射
1. 创建注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityBinding {
int layout();
Class presenter() default Void.class;
}
注意,这里的Retention必须是RUNTIME, 否则,反射无法获取到
2.反射
可以分成两步:
①调用Activity#setContentView方法设置布局
②通过反射创建Presenter对象,然后赋值给mPresenter
public class MvpReflectHelper {
public static void bind(Activity activity) {
if (activity == null) {
return;
}
ActivityBindingReflect annotation = activity.getClass().getAnnotation(ActivityBindingReflect.class);
activity.setContentView(annotation.layout());
Class presenter = annotation.presenter();
try {
Class cls = Class.forName("com.houtrry.annotationprocessorsamples.BaseActivity");
Field presenterField = cls.getDeclaredField("mPresenter");
Object instance = presenter.newInstance();
presenterField.setAccessible(true);
presenterField.set(activity, instance);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
3.使用
open class BaseActivity: AppCompatActivity() {
var mPresenter:Any? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MvpReflectHelper.bind(this)
initData()
initEvent()
}
open fun initData() {
}
open fun initEvent() {
}
}
@ActivityBindingReflect(layout = R.layout.activity_main, presenter = MainPresenter::class)
class MainActivity : BaseActivity() {
override fun initData() {
super.initData()
println("===>>>$mPresenter")
}
}
结果UI正常展示, mPresenter不为空, 说明运行正常.
2020-05-07 19:22:53.776 16064-16064/com.houtrry.annotationprocessorsamples I/System.out: ===>>>com.houtrry.annotationprocessorsamples.MainPresenter@3ce781f
众所周知, 反射影响性能( why?), 所以, 我们有了下面的方案.
方案2 注解+AbstractProcessor
准备
一般而言, 我们需要创建3个lib,
- lib-annotations: 专门用来存放各个注解. 可以是java/Android lib,一般是java lib.
- lib-complier: 用来声明自定义AbstractProcessor, 获取注解生成.java文件的lib. 只能是java lib, Android lib中没有AbstractProcessor. 该lib需要依赖lib-annotations.
- lib-mvphelper: 提供类和方法给用户使用的lib. 比如butterKnife中提供ButterKnife.java和ButterKnife#bind方法给用户调. 该lib一般是通过反射关联lib-complier生成的类. 可以是java/Android lib,一般是Android lib.
简单说明一下, 为什么得是分成3个lib:
①AbstractProcessor在Android中没有, 况且, 编译生成.java的这部分逻辑是在编译期, 不需要将AbstractProcessor相关的代码逻辑打包进apk文件中, 所以, 我们首先可以想到, 把AbstractProcessor这部分逻辑给抽取出来, 生成lib-complier
②注解应该放到哪里? 注解在lib-complier和主项目中需要使用, 那是否考虑放到lib-complier中? 在①中我们说到AbstractProcessor的逻辑我们我们不期望打包进apk, 所以, 把注解放进lib-complier是不合适的. 那放到lib-mvphelper合适吗? 放到lib-mvphelper的问题是, 我们lib-complier需要用到注解, 但是又不需要反射bind这些, 所以, 综上考虑, 拆成3个lib是合适的.
现在, 创建两个Java Module(lib-annotations 和 lib-complier), 一个Android Module(lib-mvphelper)
lib-complier依赖lib-annotations,
lib-mvphelper依赖lib-annotations(建议用api, 而不是implementation, 这样的话, app依赖lib-mvphelper的时候可以不再依赖lib-annotations)
app依赖lib-mvphelper和lib-complier
implementation project(path: ':lib-mvphelper')
annotationProcessor project(path: ':lib-compiler')
注意
①lib-compiler用的是annotationProcessor
②lib-mvphelper中依赖lib-annotations用的是api. 如果用的是implementation,那app 下的build.gradle这里还需要添加implementation project(path: ':lib-annotations')
1. lib-annotations
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ActivityBinding {
int layout();
Class presenter();
}
注意,这里的Retention用CLASS即可.
2. lib-compiler
①创建MvpHelperProcessor, 继承AbstractProcessor
②配置AbstractProcessor
有两种方式
方式一: main下创建\resources\META-INF\services\javax.annotation.processing.Processor
注意,这里的文件夹和文件名都是固定的, 不可更改.
javax.annotation.processing.Processor文件的内容是
com.houtrry.lib_compiler.MvpHelperProcessor
也就是①里创建的文件的路径
方式二: 使用auto-service
lib-compiler的build.gradle中引入auto-service
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
然后在添加注解@AutoService(Processor.class)在MvpHelperProcessor上(也就是我们继承了AbstractProcessor的类)
@AutoService(Processor.class)
public class MvpHelperProcessor extends AbstractProcessor {
//...省略
}
编译成功后, 可以在 lib-compiler的build文件下下看到自动创建的javax.annotation.processing.Processor文件.如下图:
③ 实现MvpHelperProcessor
@AutoService(Processor.class)
public class MvpHelperProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
/**
* 初始化
* 在该初始化方法中, 我们可以获取mFiler/mElementUtils
* mFiler/mElementUtils是后面生成java文件要用的
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
/**
* 此Processor支持的java版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* @return 支持的注解类型
*/
@Override
public Set getSupportedAnnotationTypes() {
return Collections.singleton(ActivityBinding.class.getCanonicalName());
}
/**
* 核心方法
* 获取注解信息, 生成.java的具体逻辑
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("================process start===========================");
return false;
}
}
④根据注解生成.java文件
这里, 我们可以先写下我们期望生成的文件
比如, 简单点, 生成如下文件
public final class JavaActivity$$$Binding {
public JavaActivity$$$Binding(JavaActivity target) {
target.setContentView(R.layout.activity_annotation_processor);
target.mPresenter=new MainPresenter();
}
}
这里, 生成java文件我们使用javapoet
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("================process start===========================");
try {
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ActivityBinding.class);
for (Element element : elements) {
if (element.getKind() != ElementKind.CLASS) {
continue;
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "===element: " + element);
createMvpHelperClass((TypeElement)element);
}
} catch (Exception e) {
e.printStackTrace();
mMessager.printMessage(Diagnostic.Kind.ERROR, "===e: " + e);
}
System.out.println("================process end===========================");
return false;
}
private void createMvpHelperClass(TypeElement typeElement) {
ActivityBinding annotation = typeElement.getAnnotation(ActivityBinding.class);
if (annotation == null) {
return;
}
int layoutId = annotation.layout();
// 获取包名
String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
// 根据旧Java类名创建新的Java文件
String className = typeElement.getQualifiedName().toString().substring(packageName.length() + 1);
String newClassName = className + "$$$Binding";
//生成构造方法
ClassName presenterClassName = getClsName(typeElement);
if (presenterClassName == null) {
return;
}
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.bestGuess(className), "target")
.addStatement("target.setContentView($L)", layoutId)
.addStatement("target.mPresenter=new $T()", presenterClassName);
//生成类信息
TypeSpec typeBuilder = TypeSpec.classBuilder(newClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodBuilder.build())
.build();
//输出.java文件
JavaFile javaFile = JavaFile.builder(packageName, typeBuilder)
.addFileComment("Generated code from Mvp Helper. Do not modify!")
.build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
private ClassName getClsName(TypeElement typeElement) {
ActivityBinding annotation = typeElement.getAnnotation(ActivityBinding.class);
if (annotation == null) {
return null;
}
ClassName className = null;
try {
Class presenter = annotation.presenter();
className = ClassName.get(presenter);
} catch (MirroredTypeException e) {
e.printStackTrace();
//捕捉MirroredTypeException异常
//在该异常中, 通过异常获取TypeMirror
//通过TypeMirror获取TypeName
TypeMirror typeMirror = e.getTypeMirror();
if (typeMirror != null) {
TypeName typeName = ClassName.get(typeMirror);
if (typeName instanceof ClassName) {
className = (ClassName) typeName;
}
}
}
return className;
}
javapoet可以参考JavaPoet的基本使用以及javapoet的javapoet github
process方法中的Element可以参考
make project成功后, 我们就可以在项目中看到生成的.java文件了
3. lib-mvphelper 反射关联
public class MvpHelper {
public static void bind(Activity activity) {
try {
Class cls = Class.forName(activity.getComponentName() + "$$$Binding");
Constructor declaredConstructor = cls.getDeclaredConstructor(activity.getClass());
System.out.println("bind=>declaredConstructor: "+declaredConstructor);
Object o = declaredConstructor.newInstance(activity);
System.out.println("bind=>o: "+o);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
简单来说, 就是通过反射执行new JavaActivity$$$Binding(activity)方法.
public class MvpHelper {
public static void bind(@NonNull Activity activity) {
try {
Class cls = Class.forName(activity.getClass().getCanonicalName() + "$$$Binding");
Constructor declaredConstructor = cls.getDeclaredConstructor(activity.getClass());
System.out.println("bind=>declaredConstructor: "+declaredConstructor);
Object o = declaredConstructor.newInstance(activity);
System.out.println("bind=>o: "+o);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
至于为啥还是反射, 不是说反射影响性能的吗? 因为这里只需要反射一次, 而上面的方案1至少一次, 一般都是很多次.
遇到的问题
1.
警告: 来自注释处理程序 'org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.8'
分析: 如果不重写getSupportedSourceVersion, 则其父类方法AbstractProcessor#getSupportedSourceVersion方法中默认返回SourceVersion.RELEASE_6.
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion var1 = (SupportedSourceVersion)this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion var2 = null;
if (var1 == null) {
var2 = SourceVersion.RELEASE_6;
if (this.isInitialized()) {
this.processingEnv.getMessager().printMessage(Kind.WARNING, "No SupportedSourceVersion annotation found on " + this.getClass().getName() + ", returning " + var2 + ".");
}
} else {
var2 = var1.value();
}
return var2;
}
解决办法: 重写getSupportedSourceVersion,并返回SourceVersion.latestSupported
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
2.
javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror com.het.soildetector.mvp.presenter.AnsweringListPresenter
at com.sun.tools.javac.model.AnnotationProxyMaker$MirroredTypeExceptionProxy.generateException(AnnotationProxyMaker.java:308)
at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:84)
at com.sun.proxy.$Proxy152.presenter(Unknown Source)
at com.het.lib_mvphelper_compiler.MvpHelperProcessor.getClsName(MvpHelperProcessor.java:143)
at com.het.lib_mvphelper_compiler.MvpHelperProcessor.createMvpHelperClass(MvpHelperProcessor.java:113)
at com.het.lib_mvphelper_compiler.MvpHelperProcessor.process(MvpHelperProcessor.java:74)
at org.gradle.api.internal.tasks.compile.processing.DelegatingProcessor.process(DelegatingProcessor.java:62)
at org.gradle.api.internal.tasks.compile.processing.NonIncrementalProcessor.process(NonIncrementalProcessor.java:45)
at org.gradle.api.internal.tasks.compile.processing.DelegatingProcessor.process(DelegatingProcessor.java:62)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor.access$401(TimeTrackingProcessor.java:37)
//...省略
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:193)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:129)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
at java.lang.Thread.run(Thread.java:748)
分析: 通过注解获取注解中的Class值时抛出MirroredTypeException异常.
参考 # Java 6 annotation processing — getting a class from an annotation
和Getting Class values from Annotations in an AnnotationProcessor
解决方法如下
private ClassName getClsName(TypeElement typeElement) {
ActivityBinding annotation = typeElement.getAnnotation(ActivityBinding.class);
if (annotation == null) {
return null;
}
ClassName className = null;
try {
Class presenter = annotation.presenter();
className = ClassName.get(presenter);
} catch (MirroredTypeException e) {
e.printStackTrace();
//捕捉MirroredTypeException异常
//在该异常中, 通过异常获取TypeMirror
//通过TypeMirror获取TypeName
TypeMirror typeMirror = e.getTypeMirror();
if (typeMirror != null) {
TypeName typeName = ClassName.get(typeMirror);
if (typeName instanceof ClassName) {
className = (ClassName) typeName;
}
}
}
return className;
}
3. 使用auto-service没有生成META-INF文件
发现跟gradle版本有关
5.0之后,需要加上
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'