需求
之前封裝了BaseActivity方法, 里面有兩個抽象方法
protected abstract @LayoutRes int getContentLayoutId();
/**
* 創(chuàng)建MVP中的Presenter
*
* @return Presenter
*/
protected abstract T createPresenter();
然后在子Activity中實現(xiàn)這兩個方法
@Override
protected int getContentLayoutId() {
return R.layout.activity_answering_detail;
}
@Override
protected AnsweringDetailContract.Presenter createPresenter() {
return new AnsweringDetailPresenter();
}
即可完成布局文件的設置和Presenter的創(chuàng)建和關聯(lián)
現(xiàn)在的目標就是通過注解, 干掉getContentLayoutId和createPresenter這兩個方法.只在子類Activity上添加注解即可.
方案1 注解+反射
1. 創(chuàng)建注解類
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityBinding {
int layout();
Class presenter() default Void.class;
}
注意,這里的Retention必須是RUNTIME, 否則,反射無法獲取到
2.反射
可以分成兩步:
①調(diào)用Activity#setContentView方法設置布局
②通過反射創(chuàng)建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")
}
}
結(jié)果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
準備
一般而言, 我們需要創(chuàng)建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方法給用戶調(diào). 該lib一般是通過反射關聯(lián)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是合適的.
現(xiàn)在, 創(chuàng)建兩個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
①創(chuàng)建MvpHelperProcessor, 繼承AbstractProcessor
②配置AbstractProcessor
有兩種方式
方式一: main下創(chuàng)建\resources\META-INF\services\javax.annotation.processing.Processor
注意,這里的文件夾和文件名都是固定的, 不可更改.
javax.annotation.processing.Processor文件的內(nèi)容是
com.houtrry.lib_compiler.MvpHelperProcessor
也就是①里創(chuàng)建的文件的路徑
方式二: 使用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文件下下看到自動創(chuàng)建的javax.annotation.processing.Processor文件.如下圖:
③ 實現(xiàn)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<String> 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;
}
}
④根據(jù)注解生成.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();
// 根據(jù)舊Java類名創(chuàng)建新的Java文件
String className = typeElement.getQualifiedName().toString().substring(packageName.length() + 1);
String newClassName = className + "$$$Binding";
//生成構(gòu)造方法
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 反射關聯(lián)
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();
}
}
}
簡單來說, 就是通過反射執(zhí)行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文件
發(fā)現(xiàn)跟gradle版本有關
5.0之后,需要加上
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'