摘要:使用來綁定該,主要是用來實例化自動生成的類。該部分下篇文章將提及我們自己定義的綁定注解庫已經(jīng)完成了,接下來我們將實現(xiàn)它的代碼自動生成部分。也是注解庫代碼自動生成的核心部分。該方法的作用就是獲取到有我們自定義注解的。
上一期我們已經(jīng)把butterknife-annotations中的注解變量都已經(jīng)定義好了,分別為BindView、OnClick與Keep。
如果你是第一次進入本系列文章,強烈推薦跳到文章末尾查看上篇文章,要不然你可能會有點云里霧里。
如果在代碼中引用的話,它將與開源庫ButterKnife的操作類似。
class MainActivity : AppCompatActivity() { @BindView(R.id.public_service, R.string.public_service) lateinit var sName: TextView @BindView(R.id.personal_wx, R.string.personal_wx) lateinit var sPhone: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Butterknife.bind(this) } @OnClick(R.id.public_service) fun nameClick(view: View) { Toast.makeText(this, getString(R.string.public_service_click_toast), Toast.LENGTH_LONG).show() } @OnClick(R.id.personal_wx) fun phoneClick(view: View) { Toast.makeText(this, getString(R.string.personal_wx_click_toast), Toast.LENGTH_LONG).show() } }
使用@BindView來綁定我的View,使用@OnClick來綁定View的點擊事件。使用Butterknife.bind來綁定該Class,主要是用來實例化自動生成的類。(該部分下篇文章將提及)
我們自己定義的綁定注解庫已經(jīng)完成了1/3,接下來我們將實現(xiàn)它的代碼自動生成部分。這時就到了上期提到的第二個Module:butterknife-compiler。
NameUtils是一些常量的管理工具類。
final class NameUtils { static String getAutoGeneratorTypeName(String typeName) { return typeName + ConstantUtils.BINDING_BUTTERKNIFE_SUFFIX; } static class Package{ static final String ANDROID_VIEW = "android.view"; } static class Class { static final String CLASS_VIEW = "View"; static final String CLASS_ON_CLICK_LISTENER = "OnClickListener"; } static class Method{ static final String BIND_VIEW = "bindView"; static final String SET_ON_CLICK_LISTENER = "setOnClickListener"; static final String ON_CLICK = "onClick"; } static class Variable{ static final String ANDROID_ACTIVITY = "activity"; } }
NameUitls包含了自動生成的類名稱,包名,方法名,變量名。總之就是為了代碼更健全,方便管理。
第二個類Processor是今天的重中之重。也是注解庫代碼自動生成的核心部分。由于注解的自動生成代碼都是在注解進程中進行,所以這里它繼承于AbstractProcessor,其中主要有三個方法需要實現(xiàn)。
init:初始化必要的數(shù)據(jù)
getSupportedAnnotationTypes:所支持的注解
process:解析注解,編寫自動生成代碼
init從簡單到容易,先是init方法,我們直接看代碼
@Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mFiler = processingEnv.getFiler(); mMessager = processingEnv.getMessager(); mElementUtils = processingEnv.getElementUtils(); }
方法參數(shù)processingEnv為我們提供注解處理所需的環(huán)境狀態(tài)。我們通過getFiler()、getMessager()與getElementUthis()方法,分別獲取創(chuàng)建源代碼的Filer、消息發(fā)送器Messager(主要用于向外界發(fā)送錯誤信息)與解析注解元素所需的通用方法。
例如:當(dāng)我們已經(jīng)構(gòu)建好了需要自動生成的類,這時我們就可以使用Filter來將代碼寫入到j(luò)ava文件中,如遇錯誤使用Messager將錯誤信息發(fā)送出去。
//寫入java文 try { JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler) } catch (IOException e) { mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement); }
代碼中的JavaFile與typeBuilder都是JavaPoet中的類。JavaPote主要提供Java API來幫助生成.java資源文件。
getSupportedAnnotationTypes@Override public SetgetSupportedAnnotationTypes() { return new TreeSet<>(Arrays.asList( BindView.class.getCanonicalName(), OnClick.class.getCanonicalName(), Keep.class.getCanonicalName()) ); }
看方法名就知道了,包含所支持的注解,將其通過set集合來返回。這里將我們上一期自定義的注解添加到set集合中即可。
process到了本篇文章的核心,process用來生成與注解相匹配的方法代碼。通過解析Class中定義的注解,生成與注解相關(guān)聯(lián)的類。
@Override public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) { .... .... return true; }
提供了兩個參數(shù):annotations與roundEnv,分別代表需要處理的注解,這里就代表我們自定義的注解;注解處理器所需的環(huán)境,幫助進行解析注解。
在開始解析注解之前,我們應(yīng)該先過濾我們所不需要的注解。回頭看getSupportedAnnotationTypes方法,我們只支持BindView、OnClick與Keep這三個注解。為了解析出相匹配的注解,我們將這個邏輯多帶帶抽離出來,交由getTypeElementsByAnnotationType來管理。
private SetgetTypeElementsByAnnotationType(Set extends TypeElement> annotations, Set extends Element> elements) { Set result = new HashSet<>(); //遍歷包含的 package class method for (Element element : elements) { //匹配 class or interface if (element instanceof TypeElement) { boolean found = false; //遍歷class中包含的 filed method constructors for (Element subElement : element.getEnclosedElements()) { //遍歷element中包含的注釋 for (AnnotationMirror annotationMirror : subElement.getAnnotationMirrors()) { for (TypeElement annotation : annotations) { //匹配注釋 if (annotationMirror.getAnnotationType().asElement().equals(annotation)) { result.add((TypeElement) element); found = true; break; } } if (found) break; } if (found) break; } } } return result; }
首先理解Element是什么?Element代表程序中的包名、類、方法,這也是注解所支持的作用類型。然后再回到代碼部分,已經(jīng)給出詳細代碼注釋。
該方法的作用就是獲取到有我們自定義注解的class。這里介紹兩個主要的方法
getEnclosedElements():獲元素中的閉包的注解元素,在我們的實例中元素為MainActivity(TypeElement,Type代表Class),而閉包的注解元素則為sName、sPhone、nameClick、phoneClick與onCreate。在這里簡單的理解就是獲取有注解的字段名、方法名
getAnnotationMirrors():獲取上述閉包元素的所有注解。這里分別為sName與sPhone上的@BindeView、nameClick與phoneClick上的@OnClick、onCreate上的@Override。
所以通過該方法最終返回的就是MainActivity,它將被轉(zhuǎn)化為TypeElement類型返回,然后將由processing來處理。
我們再回到process方法中。通過getTypeElementsByAnnotationType()方法我們已經(jīng)獲取到了我們使用了自定義注解的TypeElement(MainActivity)。
//獲取與annotation相匹配的TypeElement,即有注釋聲明的class Setelements = getTypeElementsByAnnotationType(annotations, roundEnv.getRootElements());
下面我們再獲取構(gòu)建類所需的相關(guān)信息。
//包名 String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString(); //類名 String typeName = typeElement.getSimpleName().toString(); //全稱類名 ClassName className = ClassName.get(packageName, typeName); //自動生成類全稱名 ClassName autoGenerationClassName = ClassName.get(packageName, NameUtils.getAutoGeneratorTypeName(typeName)); //構(gòu)建自動生成的類 TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName) .addModifiers(Modifier.PUBLIC) .addAnnotation(Keep.class);
注釋已經(jīng)劃分清楚了,可以分為四個步驟
獲取對應(yīng)typeElement的包名(這里獲取的是com.idisfkj.androidapianalysis)
獲取typeElement的SimpleName(這里為MainActivity字符串)
根據(jù)上述獲取的包名與SimpleName來構(gòu)建一個ClassName,為了后續(xù)聲明方法的參數(shù)類型(這里為MainActivity類,注意是MainActivity類型)
構(gòu)建需要自動生成的ClassName,這里使用NameUtils.getAutoGeneratorTypeName進行了統(tǒng)一命名(這里自動生成的類名為MainActivity$Binding,都以原始類名后面加$Binding)
所有信息準(zhǔn)備完畢后,然后開始定義自動生成的類。這里通過使用TypeSpec.Builder來構(gòu)建。它是JavaPoet中的類。
JavaPoet由于直接使用JavaFileObject生成.java資源文件是非常麻煩的,所以推薦使用JavaPoet。JavaPoet是一個開源庫,主要用來幫助方便快捷的生成.java的資源文件。想要全面了解的可以查看Github鏈接。為了幫助快速讀懂該文章,這里對其中幾個主要方法進行介紹。當(dāng)然在使用前還需在butterknife-compiler中的builder.gradle添加依賴:
dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation project(":butterknife-annotations") implementation "com.squareup:javapoet:1.11.1" }
同時也將上一期我們自定義的注解Module引入。
TypeSpec.Builder: 定義一個類
addModifiers: 定義private、public與protected類型
addAnnotation: 對Element元素添加注解。例如:@Keep
TypeSpec.Builder -> addMethod: 添加方法
MethodSpec -> addParameter: 為方法添加參數(shù)類型與參數(shù)名
MethodSpec -> addStatement: 在方法中添加代碼塊。而其中的一些動態(tài)類型會使用占位符替代。例如:addStatement("$N($N)", "bindView", "activity"),它將會生成bindView(activity)。占位符:$N -> name, $T -> type(ClassName), $L -> literals
有了上面的理解我們再來看下面的生成代碼:
//構(gòu)建自動生成的類 TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName) .addModifiers(Modifier.PUBLIC) .addAnnotation(Keep.class); //添加構(gòu)造方法 typeBuilder.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY) .addStatement("$N($N)", NameUtils.Method.BIND_VIEW, NameUtils.Variable.ANDROID_ACTIVITY) .addStatement("$N($N)", NameUtils.Method.SET_ON_CLICK_LISTENER, NameUtils.Variable.ANDROID_ACTIVITY) .build());
首先通過TypeSpec.Builder構(gòu)建一個類,類名為autoGenerationClassName(MainActivity$Binding),類的訪問級別為public,由于為了防止混淆使用了我們自定義的@Keep注解。
然后再來添加類的構(gòu)造方法,使用addMethod、addModifiers、addParameter與addStatement分別構(gòu)建構(gòu)造方法名、方法訪問級別、方法參數(shù)與方法中執(zhí)行的代碼塊。所以上面的代碼最終將會自動生成如下代碼:
@Keep public class MainActivity$Binding { public MainActivity$Binding(MainActivity activity) { bindView(activity); setOnClickListener(activity); } }
在自動生成類的構(gòu)造方法中調(diào)用了我們想要的bindView與setOnClickListener方法。所以接下來我們要實現(xiàn)的就是這兩個方法的構(gòu)建。
bindView//添加bindView成員方法 MethodSpec.Builder bindViewBuilder = MethodSpec.methodBuilder(NameUtils.Method.BIND_VIEW) .addModifiers(Modifier.PRIVATE) .returns(TypeName.VOID) .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY); //添加方法內(nèi)容 for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) { BindView bindView = variableElement.getAnnotation(BindView.class); if (bindView != null) { bindViewBuilder.addStatement("$N.$N=($T)$N.findViewById($L)", NameUtils.Variable.ANDROID_ACTIVITY, variableElement.getSimpleName(), variableElement, NameUtils.Variable.ANDROID_ACTIVITY, bindView.value()[0] ).addStatement("$N.$N.setText($N.getString($L))", NameUtils.Variable.ANDROID_ACTIVITY, variableElement.getSimpleName(), NameUtils.Variable.ANDROID_ACTIVITY, bindView.value()[1]); } } typeBuilder.addMethod(bindViewBuilder.build());
使用MethodSpec.Builder來創(chuàng)建bindView方法,其它的都與構(gòu)造方法類似。使用returns為方法返回void類型。然后再遍歷MainActivity中的注解,找到與我們定義的BindView相匹配的字段。最后分別向bindView方法中添加findViewById與setText代碼塊,同時將定義的方法添加到typeBuilder中。所以執(zhí)行完上面代碼后在MainActivity$Binding中展示如下:
private void bindView(MainActivity activity) { activity.sName=(TextView)activity.findViewById(2131165265); activity.sName.setText(activity.getString(2131427362)); activity.sPhone=(TextView)activity.findViewById(2131165262); activity.sPhone.setText(activity.getString(2131427360)); }
實現(xiàn)了我們最初的View的綁定與TextView的默認值設(shè)置。
setOnClickListener//添加setOnClickListener成員方法 MethodSpec.Builder setOnClickListenerBuilder = MethodSpec.methodBuilder(NameUtils.Method.SET_ON_CLICK_LISTENER) .addModifiers(Modifier.PRIVATE) .returns(TypeName.VOID) .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY, Modifier.FINAL); //添加方法內(nèi)容 ClassName viewClassName = ClassName.get(NameUtils.Package.ANDROID_VIEW, NameUtils.Class.CLASS_VIEW); ClassName onClickListenerClassName = ClassName.get(NameUtils.Package.ANDROID_VIEW, NameUtils.Class.CLASS_VIEW, NameUtils.Class.CLASS_ON_CLICK_LISTENER); for (ExecutableElement executableElement : ElementFilter.methodsIn(typeElement.getEnclosedElements())) { OnClick onClick = executableElement.getAnnotation(OnClick.class); if (onClick != null) { //構(gòu)建匿名class TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("") .addSuperinterface(onClickListenerClassName) .addMethod(MethodSpec.methodBuilder(NameUtils.Method.ON_CLICK) .addModifiers(Modifier.PUBLIC) .addParameter(viewClassName, NameUtils.Class.CLASS_VIEW) .returns(TypeName.VOID) .addStatement("$N.$N($N)", NameUtils.Variable.ANDROID_ACTIVITY, executableElement.getSimpleName(), NameUtils.Class.CLASS_VIEW) .build()) .build(); setOnClickListenerBuilder.addStatement("$N.findViewById($L).setOnClickListener($L)", NameUtils.Variable.ANDROID_ACTIVITY, onClick.value(), typeSpec); } } typeBuilder.addMethod(setOnClickListenerBuilder.build());
與bindView方法不同的是,由于使用到了匿名類OnClickListener與類View,所以我們這里也要定義他們的ClassName,然后使用TypeSpec來生成匿名類。生成之后再添加到setOnClickListener方法中。最后再將setOnClickListener方法添加到MainActivity$Binding中。所以最終展示如下:
private void setOnClickListener(final MainActivity activity) { activity.findViewById(2131165265).setOnClickListener(new View.OnClickListener() { public void onClick(View View) { activity.nameClick(View); } }); activity.findViewById(2131165262).setOnClickListener(new View.OnClickListener() { public void onClick(View View) { activity.phoneClick(View); } }); }
我們的MainActivity$Binding類就已經(jīng)定義完成,最后再寫入到j(luò)ava文件中
//寫入java文件 try { JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler); } catch (IOException e) { mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement); }services
在butterknife-compiler中,我們還需創(chuàng)建一個特定的目錄:
butterknife-compiler/src/main/resources/META-INF/services
在services目錄中,我們還需創(chuàng)建一個文件:javax.annotation.processing.Processor,該文件是用來告訴編譯器,當(dāng)它在編譯代碼的過程中正處于注解處理中時,會告訴注解處理器來自動生成哪些類。
所以我們在文件中將添加我們自定義的Processor路徑
com.idisfkj.butterknife.compiler.Processor
這樣注解器就會調(diào)用該指定的Processor。到這里整個butterknife-compiler就完成了,現(xiàn)在我們可以Make Project一下工程,完成之后就可以全局搜索到MainActivity$Binding文件了。或者在如下路徑中查看:
/app/build/generated/source/kapt/debug/com/idisfkj/androidapianalysis/MainActivity$Binding.java
文章中的代碼都可以在Github中獲取到。使用時請將分支切換到feat_annotation_processing
相關(guān)文章自定義Android注解Part1:注解變量
自定義Android注解Part3:綁定
關(guān)注公眾號:怪談時間到了
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71549.html
摘要:上一節(jié)我們已經(jīng)將自動生成注解代碼部分介紹完畢,今天這篇文章是自定義注解系列的最后一篇文章。該部分是對我們前面定義的注解變量與自動生成的代碼進行綁定,即調(diào)用我們自動生成的代碼。 showImg(https://segmentfault.com/img/bVbdEsh?w=1200&h=720); 上一節(jié)我們已經(jīng)將自動生成注解代碼部分介紹完畢,今天這篇文章是自定義Android注解系列的最...
摘要:下面我們會自己實現(xiàn)與注解,實現(xiàn)中的對應(yīng)注解功能。帶大家一起來聲明注解變量。知道了它的作用范圍之后,我們在自定義注解時就要盡量較小注解的作用范圍,提高項目的編譯與運行速度。它們代表自定義的注解能夠作用的對象。總結(jié)庫中的自定義注解就完成了。 showImg(https://segmentfault.com/img/bVbc08a?w=740&h=416); 對于Android注解,或多或少...
閱讀 2565·2021-10-11 10:58
閱讀 1148·2021-09-29 09:34
閱讀 1486·2021-09-26 09:46
閱讀 3830·2021-09-22 15:31
閱讀 730·2019-08-30 15:54
閱讀 1458·2019-08-30 13:20
閱讀 1251·2019-08-30 13:13
閱讀 1486·2019-08-26 13:52