「我已放棄成爲珠三角頭髮最濃密的程序員夢想...」java
在android開發中,比較經常使用到的第三方庫中,有很多用到了 註解處理器(Annotation Processor)。 比較常見的就有 Butterknife,Dagger2,DBFlow 等。android
Java中存在很多關於註解的Api, 好比@Override
用於覆蓋父類方法,@Deprecated
表示已捨棄的類或方法屬性等,android中又多了一些註解的擴展,如@NonNull
, @StringRes
, @IntRes
等。git
使用代碼自動生成,一是爲了提升編碼的效率,二是避免在運行期大量使用反射,經過在編譯期利用反射生成輔助類和方法以供運行時使用。程序員
註解處理器的處理步驟主要有如下:github
Butterknife的註解處理器的工做方式以下:api
Butterknife.bind(..)
方法。當你點擊Android Studio的Build
按鈕時,Butterknife先是按照上述步驟生成了對應的輔助類和方法。在代碼執行到bind(..)
方法時,Butterknife就去調用以前生成的輔助類方法,完成對被註解元素的賦值操做。bash
瞭解了基本的知識點後,咱們應該嘗試去使用這些技巧。 接下來是實踐時間,咱們來開發一個簡單的例子,利用註解處理器來自動產生隨機數字和隨機字符串。markdown
在lib_annotations中添加兩個註解:RandomString
, RandomInt
,分別用於生成隨機數字和隨機字符串:app
@Retention(CLASS) @Target(value = FIELD) public @interface RandomString { } 複製代碼
@Retention(CLASS) @Target(value = FIELD) public @interface RandomInt { int minValue() default 0; int maxValue() default 65535; } 複製代碼
public enum ElementType { TYPE, //類 FIELD, //屬性 METHOD, //方法 PARAMETER, //參數 CONSTRUCTOR, //構造函數 LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE; private ElementType() { } } 複製代碼
public enum RetentionPolicy { SOURCE, //被編譯器所忽略 CLASS, //被編譯器保留至類文件,但不會保留至運行時 RUNTIME //保留至類文件,且保留至運行時,能在運行時反射該註解修飾的對象 } 複製代碼
真正處理註解並生成代碼的操做都在這裏。 在寫代碼以前咱們須要先導入兩個重要的庫,以及咱們的註解模塊:dom
compile 'com.google.auto.service:auto-service:1.0-rc4' compile 'com.squareup:javapoet:1.9.0' implementation project(':lib_annotations') 複製代碼
新建類RandomProcessor.java
:
@AutoService(Processor.class) public class RandomProcessor extends AbstractProcessor{ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); } @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); } @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } } 複製代碼
META-INF/services
下生成javax.annotation.processing.Processor
文件,文件的內容爲com.rhythm7.lib_compiler.RandomProcessor
複製代碼
也就是說,你所聲明的註解處理器都會在被寫入這個配置文件中。 這樣子,當外部程序裝載這個模塊的時候,就能經過該模塊的jar包下的META-INF/services下找到具體的註解處理器的實現類名,並加載實例化,完成模塊的注入。 註解處理器須要實現AbstractProcessor
接口,並實現對應的方法
init() 可選 在該方法中能夠獲取到processingEnvironment
對象,藉由該對象能夠獲取到生成代碼的文件對象, debug輸出對象,以及一些相關工具類
getSupportedSourceVersion() 返回所支持的java版本,通常返回當前所支持的最新java版本便可
getSupportedAnnotationTypes() 你所須要處理的全部註解,該方法的返回值會被process()
方法所接收
process() 必須實現 掃描全部被註解的元素,並做處理,最後生成文件。該方法的返回值爲boolean類型,若返回true,則表明本次處理的註解已經都被處理,不但願下一個註解處理器繼續處理,不然下一個註解處理器會繼續處理。
較詳細代碼以下:
private static final List<Class<? extends Annotation>> RANDOM_TYPES = Arrays.asList(RandomInt.class, RandomString.class); private Messager messager; private Types typesUtil; private Elements elementsUtil; private Filer filer; private TypeonProcess()per.init(processingEnv); messager = processingEnv.getMessager(); typesUtil = processingEnv.getTypeUtils(); elementsUtil = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : RANDOM_TYPES) { annotations.add(annotation.getCanonicalName()); } return annotations; } 複製代碼
在process()
方法中執行如下操做:
for (Element element : roundEnv.getElementsAnnotatedWith(RandomInt.class)) { //AnnotatedRandomInt是對被RandomInt註解的Elment的簡單封裝 AnnotatedRandomInt randomElement = new AnnotatedRandomInt(element); messager.printMessage(Diagnostic.Kind.NOTE, randomElement.toString()); //判斷被註解的類型是否符合要求 if (!element.asType().getKind().equals(TypeKind.INT)) { messager.printMessage(Diagnostic.Kind.ERROR, randomElement.getSimpleClassName().toString() + "#" + randomElement.getElementName().toString() + " is not in valid type int"); } //按被註解元素所在類的完整類名爲key將被註解元素存儲進Map中,後面會根據key生成類文件 String qualifier = randomElement.getQualifiedClassName().toString(); if (annotatedElementMap.get(qualifier) == null) { annotatedElementMap.put(qualifier, new ArrayList<AnnotatedRandomElement>()); } annotatedElementMap.get(qualifier).add(randomElement); } 複製代碼
將以前以註解所在類爲key的map遍歷,並以key值爲分組生成類文件。
for (Map.Entry<String, List<AnnotatedRandomElement>> entry : annotatedElementMap.entrySet()) { MethodSpec constructor = createConstructor(entry.getValue()); TypeSpec binder = createClass(getClassName(entry.getKey()), constructor); JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build(); javaFile.writeTo(filer); } 複製代碼
生成類、構造函數、代碼段以及文件都是利用到了javapoet
依賴庫。固然你也能夠選擇拼接字符串和本身用文件IO寫入,可是用javapoet
要更方便得多。
private MethodSpec createConstructor(List<AnnotatedRandomElement> randomElements) { AnnotatedRandomElement firstElement = randomElements.get(0); MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), "target"); for (int i = 0; i < randomElements.size(); i++) { addStatement(builder, randomElements.get(i)); } return builder.build(); } private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) { builder.addStatement(String.format( "target.%1$s = %2$s", randomElement.getElementName().toString(), randomElement.getRandomValue()) ); } private TypeSpec createClass(String className, MethodSpec constructor) { return TypeSpec.classBuilder(className + "_Random") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(constructor) .build(); } private String getPackage(String qualifier) { return qualifier.substring(0, qualifier.lastIndexOf(".")); } private String getClassName(String qualifier) { return qualifier.substring(qualifier.lastIndexOf(".") + 1); } 複製代碼
經過以上幾行代碼,建立了類文件。在類的構造函數中添加參數(target)
, 併爲每個被註解元素添加語句"target.%1$s = %2$s"
,最後經過javaFile.writeTo(filer)
完成文件寫入。
在lib_api中新建一個類:RandomUtil.java
,添加註入方法:
public static void inject(Object object) { Class bindingClass = Class.forName(object.getClass().getCanonicalName() + "_Random"); Constructor constructor = bindingClass.getConstructor(object.getClass()); constructor.newInstance(object); } 複製代碼
這裏利用反射找到了以「Object類名_Random」命名的生成類,並調用它的構造方法。而在咱們以前的註解處理器中,咱們已在生成類的構造方法中實現了屬性的賦值操做。
在app module中依賴剛纔建立的庫:
implementation project(':lib_annotations') implementation project(':lib_api') annotationProcessor project(':lib_compiler') 複製代碼
在Activity中的使用
public class MainActivity extends AppCompatActivity { @RandomInt(minValue = 10, maxValue = 1000) int mRandomInt; @RandomString String mRandomString; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RandomUtil.inject(this); Log.i("RandomInt ==> ", mRandomInt + ""); Log.i("RandomString ==> ", mRandomString); } } 複製代碼
編譯,運行,查看結果:
RandomInt ==>: 700
RandomString ==>: HhRayFyTtt
複製代碼
被註解的元素成功被自動賦值,說明注入成功。
註解處理器的debug跟普通的代碼debug有點不一樣:
在當前工程路徑下輸入命令
gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac 複製代碼
並在Edit Configurations
中新添加一個遠程配置(remote),名字隨意,端口爲5005。 而後點擊debug按鈕,就能夠鏈接上遠程調試器進行Annotation的調試了。