JavaPoet 是 Square 公司推出的開源 Java代碼生成框架,提供接口生成 Java 源文件。
它的項目主頁及源碼:https://github.com/square/javapoet
這個框架功能非常有用,我們可以很方便的使用它根據註解、數據庫模式、協議格式等來對應生成代碼。通過這種自動化生成代碼的方式,可以讓我們用更加簡潔優雅的方式要替代繁瑣冗雜的重複工作。
例如業內的一些開源庫 Butterknife,GreenDao,Dagger 2 等等,也都有運用 JavaPoet 結合註解來實現編譯時動態生成代碼。
以下是 JavaPoet 裏面的一些關鍵類:
JavaFile : 用於構造輸出包含一個頂級類的Java文件
TypeSpec : 生成類,接口,或者枚舉
MethodSpec : 生成構造函數或方法
FieldSpec : 生成成員變量或字段
ParameterSpec : 用來創建參數
AnnotationSpec : 用來創建註解
下面參考 Butterknife 源碼,實現綁定 View 和設置監聽的功能:
1,首先是 註解類:
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
int value();
}
2,繼承 AbstractProcessor ,實現 Processor :
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 代碼 輸出到 哪
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 拿到 每個類,要生成的 代碼塊;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndParseTargets(env);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對應的 類文件;
ViewBindHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
private Map<TypeElement, List<CodeBlock.Builder>> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
// 遍歷帶 對應註解的 元素,具體來看實際也就是 某個View對象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
ViewBindHelper.parseBindView(element, builderMap);
}
// 遍歷帶 對應註解的 元素,具體來看實際也就是 某個方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
ViewBindHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
下面來看幾個關鍵性代碼,ViewBindHelper 類的 parseBindView 方法:
public static void parseBindView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
// 獲取最裏面的節點, 具體實際可能就是 某個Activity對象
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 這個view是哪個類 Class
String typeMirror = element.asType().toString();
// 註解的值,具體實際可能就是 R.id.xxx
int annotationValue = element.getAnnotation(BindView.class).value();
// 這個view對象 名稱
String name = element.getSimpleName().toString();
//創建代碼塊
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", name); //$L是佔位符,會把後面的 name 參數拼接到 $L 所在的地方
builder.add("($L)source.findViewById($L)", typeMirror, annotationValue);
List<CodeBlock.Builder> codeList = codeBuilderMap.get(enclosingElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(enclosingElement, codeList);
}
codeList.add(builder);
}
看到這裏你就知道根據 BindView 註解怎麼生成 findViewById 的代碼了。OnClick註解也是類似。
最後看怎麼生成Java文件,ViewBindHelper 類的 writeBindView 方法:
public static void writeBindView(TypeElement enclosingElement, List<CodeBlock.Builder> codeList, Filer filer) {
// enclosingElement ,暗指 某個Activity.
// 先拿到 Activity 所在包名
String packageName = enclosingElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));
// 再拿到 Activity 類名
String className = enclosingElement.getSimpleName().toString();
// 再拿到 Activity 是 哪個類
TypeName type = TypeName.get(enclosingElement.asType());//此元素定義的類型
if (type instanceof ParameterizedTypeName) {
type = ((ParameterizedTypeName) type).rawType;
}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
// 創建構造方法 如果 Activity是 MainActivity,則會有 生成如下構造方法
// public MainActivity_ViewBinding(final MainActivity target, final View source) {
// target.btn1 = (android.widget.Button)source.findViewById(2131165217);
// source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
// }
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法裏面 ,代碼是什麼
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 創建類 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
// 將文件寫出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
而主工程代碼,看 MainActivity 裏面怎麼使用的:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
public Button btn1;
@BindView(R.id.btn2)
public Button btn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyAnnotationUtils.bind(this);
HelloWorld.main(null);
btn1.setText("測試成功1");
btn2.setText("測試成功2");
}
@OnClick({R.id.btn1, R.id.btn2})
public void onBtn1Click(View v) {
Toast.makeText(this, "測試" + ((Button)v).getText().toString(), Toast.LENGTH_SHORT).show();
}
}
通過上面,整個項目編譯完成後,在 app\build\generated\source\apt\debug\com\mill\apt 目錄下面有一個自動生成的類:
package com.mill.apt;
import android.view.View;
public class MainActivity_ViewBinding {
public MainActivity_ViewBinding(final MainActivity target, final View source) {
target.btn1 = (android.widget.Button)source.findViewById(2131165217);
target.btn2 = (android.widget.Button)source.findViewById(2131165218);
source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
source.findViewById(2131165218).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
}
}
而我們關心的這個類裏面的方法是怎麼調用呢?那就要看 MyAnnotationUtils類的 bind 方法:
public class MyAnnotationUtils {
public static void bind(Activity activity) {
//獲取activity的decorView(根view)
View view = activity.getWindow().getDecorView();
bind(activity, view);
}
private static void bind(Object obj, View view) {
String qualifiedName = obj.getClass().getName();
//找到該activity對應的Bind類的名字
String generateClass = qualifiedName + "_ViewBinding";
//然後調用Bind類的構造方法,從而完成activity裏view的初始化
try {
Class.forName(generateClass).getConstructor(obj.getClass(), View.class).newInstance(obj, view);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
實際就是通過反射,調用動態生成類的構造方法。
這裏提幾個需要注意的點:
1,HelloProcessor 類是必須在 Java Module裏面的,不然找不到 javax.annotation.processing.AbstractProcessor;
2,自Android Gradle 插件 2.2 版本開始,官方提供了名爲 AnnotationProcessor 的功能來完全代替 Android-Apt;
3,建議使用 com.google.auto.service:auto-service 庫,註冊註解處理器;
導入庫之後,類似 加上 @AutoService(Processor.class) ,就會自動生成對應的
\META-INF\services javax.annotation.processing.Processor:
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
//......
}
最後附上demo地址:https://github.com/miLLlulei/AnnotationProcessor
歡迎大家star~