1,概述
平时使用的一些框架如dagger2、butterknife等,通过注解动态生成代码的技术,就是apt,本文将写一个简单demo,来演示apt基本使用;
2,实例
(1)创建java-libray
在android-studio4.0版本,创建一个java-libray module;
创建两个,分别命名为apt-annotation、apt-processor
再创建一个android module,取名apt-library
项目结构如下,
(2)在apt-annotation下创建注解,这儿选择class,即编译时;
@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.FIELD})
public @interface DemoFindViewById {
int value();
}
(3)在apt-processor模块导入依赖,
dependencies {
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation project(':apt-annotation')
}
(4)创建DemoProcessor类,在process中写入即将生成的java代码
@AutoService(Processor.class)
public class DemoProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
(5)完整DemoProcessor如下,
//此注解,表示会执行此类
@AutoService(Processor.class)
public class DemoProcessor extends AbstractProcessor {
private ProcessingEnvironment mProcessingEnvironment;
private Map<String, CreateClassProxy> mCache = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.mProcessingEnvironment = processingEnv;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
// 写入支持编译的注解
Set<String> supportTypes = new HashSet<>();
supportTypes.add(DemoFindViewById.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
// 兼容到JDK最新版本
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//动态生成java代码
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE, "processing...");
//获取所有@Demo的Element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(DemoFindViewById.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
String className = typeElement.getQualifiedName().toString();
CreateClassProxy proxy = mCache.get(className);
if (proxy == null) {
proxy = new CreateClassProxy(mProcessingEnvironment.getElementUtils(), typeElement);
mCache.put(className, proxy);
}
DemoFindViewById findViewById = variableElement.getAnnotation(DemoFindViewById.class);
int id = findViewById.value();
proxy.putElement(id,variableElement);
}
//创建java文件
for (CreateClassProxy proxy : mCache.values()) {
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"create code :" + proxy.getClassName());
try {
JavaFileObject javaFileObject = super.processingEnv.getFiler().createSourceFile(proxy.getClassName(), proxy.getTypeElement());
Writer writer = javaFileObject.openWriter();
writer.write(proxy.generateJavaCode().toString());
writer.flush();
writer.close();
} catch (IOException e) {
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"fail code :" + proxy.getClassName());
}
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"finish code :" + proxy.getClassName());
}
return true;
}
}
(6)生成代码proxy类,
CreateClassProxy,采用JavaPoet辅助生成代码;
public class CreateClassProxy {
private String mClassName;
private String mPackageName;
private TypeElement mTypeElement;
private final Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public CreateClassProxy(Elements elementUtils, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mClassName = className + "_DemoBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
public String getClassName() {
return mClassName;
}
public String getPackageName() {
return mPackageName;
}
public TypeElement getTypeElement() {
return mTypeElement;
}
public TypeSpec generateJavaCode(){
TypeSpec typeSpec = TypeSpec.classBuilder(mClassName)
.addMethod(generateJavaMethod())
.build();
StringBuilder builder = new StringBuilder();
// 不知为何,生成代码少package行,导致找不到,这儿暂时这么写
return builder.append("package ")
.append(mPackageName)
.append(";\n")
.append(typeSpec.toString);
}
public MethodSpec generateJavaMethod(){
//类名
ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder builder = MethodSpec.methodBuilder("inject")//方法名
.addModifiers(Modifier.PUBLIC)//限定符
.returns(void.class)//返回值
.addParameter(className, "className");//参数类型和参数名
//迭代,加入代码行
for (Integer id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
//代码块
builder.addCode("className." + name + " = " + "(" + type + ")(((android.app.Activity)className).findViewById( " + id + "));");
}
return builder.build();
}
}
(7)在apt-library写工具类,即通过反射调用已经生成的代码,
public class AptDemoInjector {
public static void inject(Activity activity){
try {
Class<?> target = Class.forName(activity.getClass().getName() + "_DemoBinding");
Method inject = target.getMethod("inject", activity.getClass());
inject.invoke(target.newInstance(),activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
(8)在app模块写入测试代码,
public class AptDemoActivity extends AppCompatActivity {
private static final String TAG = "AptDemoActivity";
@DemoFindViewById(R.id.demo_id)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_apt_demo);
// 注入,自动生成代码
AptDemoInjector.inject(this);
Log.d(TAG, "inject: TextView ->" + textView);
}
}
(9)点击rebuild,自动生成代码如下
import android.widget.TextView;
import com.zjw.demoapp.apt.AptDemoActivity;
class AptDemoActivity_DemoBinding {
AptDemoActivity_DemoBinding() {
}
public void inject(AptDemoActivity className) {
className.textView = (TextView)className.findViewById(2131230864);
}
}
运行app,打印日志,看到已经成功注入
2021-12-27 09:51:35.106 25978-25978/com.zjw.framework_self D/MainActivity: inject: TextView ->com.google.android.material.textview.MaterialTextView{81f68cb V.ED..... ......ID 0,0-0,0 #7f08008e app:id/demo_id}