PS:人是一種很樂於接受自我暗示的生物,你給了自己消極暗示,那麼你很容易變得頹廢,如果你給了自己積極暗示,那麼你也會變得積極起來。
今天看一下編譯時註解的相關知識,相信手動實踐後你會更容易理解像 Dagger、ARouter、ButterKnife 等這種使用了編譯時註解的框架,也更容易理解其內部源碼實現,內容如下:
- 編譯時和運行時註解
- 註解處理器 APT
- AbstractProcessor
- Element 和 Elements
- 自定義註解處理器
- 使用自定義註解處理器
編譯時和運行時註解#
先了解一下編譯時和運行時的區別:
- 編譯時:指編譯器將源代碼翻譯成機器能夠識別的代碼的過程,Java 中也就是將 Java 源代碼編譯為 JVM 識別的字節碼文件的過程。
- 運行時:指 JVM 分配內存、解釋執行字節碼文件的過程。
元註解 @Retention
決定註解是在編譯時還是在運行時,其可配置策略如下:
public enum RetentionPolicy {
SOURCE, //在編譯時會被丟棄,僅僅在源碼中存在
CLASS, //默認策略,運行時就會被丟棄,僅僅在 class 文件中
RUNTIME //編譯時會將註解信息記錄到class文件,運行時仍然保留,可以通過反射獲取註解信息
}
編譯時註解和運行時註解除了上面的區別之外,實現方式也不一樣,編譯時註解一般是通過註解處理器 (APT) 來實現的,運行時註解一般是通過反射來實現的。
關於註解和反射的可以參考下面兩篇文章:
什麼是 APT#
APT (Annotation Processing Tool) 是 javac 提供的一種可以處理註解的工具,用來在編譯時掃描和處理註解的,簡單來說就是可以通過 APT 獲取到註解及其註解所在位置的信息,可以使用這些信息在編譯器生成代碼。編譯時註解就是通過 APT 來通過註解信息生成代碼來完成某些功能,典型代表有 ButterKnife、Dagger、ARouter 等。
AbstractProcessor#
AbstractProcessor
實現 Processor
是註解處理器的抽象類,要實現註解處理器都需要繼承 AbstractProcessor
進行擴展,主要方法說明如下:
- init:初始化 Processor,可從參數
ProcessingEnvironment
中獲取工具類Elements
、Types
、Filer
以及Messager
等; - getSupportedSourceVersion:返回使用的 Java 版本;
- getSupportedAnnotationTypes:返回要處理的的所有的註解名稱;
- process:獲取所有指定的註解進行處理;
process 方法可能會在運行過程中執行多次,直到沒有其他類產生為止。
Element 和 Elements#
Element
類似於 XML 中的標籤一樣,在 Java 中Element
表示程序元素,如類、成員、方法等,每個 Element
都表示僅表示某種結構,對 Element
對象類的操作,請使用 visitor 或 getKind ( 方法進行判斷再具體處理,Element
的子類如下:
- ExecutableElement
- PackageElement
- Parameterizable
- QualifiedNameable
- TypeElement
- TypeParameterElement
- VariableElement
上述元素結構分別對應下面代碼結構:
// PackageElement
package manu.com.compiler;
// TypeElement
public class ElementSample {
// VariableElement
private int a;
// VariableElement
private Object other;
// ExecutableElement
public ElementSample() {
}
// 方法參數VariableElement
public void setA(int newA) {
}
// TypeParameterElement表示參數化類型,用在泛型參數中
}
Elements
是處理 Element
的工具類,只提供接口,具體有 Java 平台實現。
自定義編譯註解處理器#
下面使用 APT 實現一個註解 @Bind
來替換來模仿 ButterKnife 的註解 @BindView
,案例項目結構如下:
在 api 模塊中定義註解 @Bind
如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
int value();
}
compiler 模塊是 Java 模塊,引入 Google 的 auto-service 用來生成 META-INFO 下的相關文件,javapoet 用於更方便的創建 Java 文件,自定義註解處理器 BindProcessor
如下:
// 用來生成META-INF/services/javax.annotation.processing.Processor文件
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
// 用於創建Java文件
implementation 'com.squareup:javapoet:1.12.1'
/**
* BindProcessor
*/
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
private Elements mElements;
private Filer mFiler;
private Messager mMessager;
// 存儲某個類下面對應的BindModel
private Map<TypeElement, List<BindModel>> mTypeElementMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
print("init");
// 初始化Processor
mElements = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
print("getSupportedSourceVersion");
// 返回使用的Java版本
return SourceVersion.RELEASE_8;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
print("getSupportedAnnotationTypes");
// 返回要處理的的所有的註解名稱
Set<String> set = new HashSet<>();
set.add(Bind.class.getCanonicalName());
set.add(OnClick.class.getCanonicalName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
print("process");
mTypeElementMap.clear();
// 獲取指定Class類型的Element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Bind.class);
// 遍歷將符合條件的Element存儲起來
for (Element element : elements) {
// 獲取Element對應類的全限定類名
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
print("process typeElement name:"+typeElement.getSimpleName());
List<BindModel> modelList = mTypeElementMap.get(typeElement);
if (modelList == null) {
modelList = new ArrayList<>();
}
modelList.add(new BindModel(element));
mTypeElementMap.put(typeElement, modelList);
}
print("process mTypeElementMap size:" + mTypeElementMap.size());
// Java文件生成
mTypeElementMap.forEach((typeElement, bindModels) -> {
print("process bindModels size:" + bindModels.size());
// 獲取包名
String packageName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
// 生成Java文件的文件名
String className = typeElement.getSimpleName().toString();
String newClassName = className + "_ViewBind";
// MethodSpec
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.bestGuess(className), "target");
bindModels.forEach(model -> {
constructorBuilder.addStatement("target.$L=($L)target.findViewById($L)",
model.getViewFieldName(), model.getViewFieldType(), model.getResId());
});
// typeSpec
TypeSpec typeSpec = TypeSpec.classBuilder(newClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(constructorBuilder.build())
.build();
// JavaFile
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.addFileComment("AUTO Create")
.build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
private void print(String message) {
if (mMessager == null) return;
mMessager.printMessage(Diagnostic.Kind.NOTE, message);
}
}
其中 BindModel
是對註解 @Bind
信息的簡單封裝,如下:
/**
* BindModel
*/
public class BindModel {
// 成員變量Element
private VariableElement mViewFieldElement;
// 成員變量類型
private TypeMirror mViewFieldType;
// View的資源Id
private int mResId;
public BindModel(Element element) {
// 校驗Element是否是成員變量
if (element.getKind() != ElementKind.FIELD) {
throw new IllegalArgumentException("element is not FIELD");
}
// 成員變量Element
mViewFieldElement = (VariableElement) element;
// 成員變量類型
mViewFieldType = element.asType();
// 獲取註解的值
Bind bind = mViewFieldElement.getAnnotation(Bind.class);
mResId = bind.value();
}
public int getResId(){
return mResId;
}
public String getViewFieldName(){
return mViewFieldElement.getSimpleName().toString();
}
public TypeMirror getViewFieldType(){
return mViewFieldType;
}
}
在 bind 模塊創建要生成的文件
/**
* 初始化
*/
public class BindKnife {
public static void bind(Activity activity) {
// 獲取activity的全限定類名
String name = activity.getClass().getName();
try {
// 反射創建並注入Activity
Class<?> clazz = Class.forName(name + "_ViewBind");
clazz.getConstructor(activity.getClass()).newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
當然 ButterKnife 應該是把創建的對象緩存起來,不會每次都會去創建新的對象,雖然也是用了反射,但是相較運行時註解來說反射的測試減少了很多,所以性能也較運行時註解更好,這也算是編譯時註解和運行時註解的區別。
使用自定義註解處理器#
使用方式和 ButterKnife 類似,如下:
public class MainActivity extends AppCompatActivity{
@Bind(R.id.tvData)
TextView tvData;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindKnife.bind(this);
tvData.setText("data");
}
}
了解編譯時註解,可能並並不會去立馬造輪子,但是在看其他框架的時候非常有幫助,在解決問題的時候也就多了一個途徑。