一個註解容許你把額外的元數據關聯到一個聲明上。而後元數據就能夠被相關的源代碼工具訪問,經過編譯好的類文件或是在運行時,取決於這個註解是如何配置的。 --《Kotlin in Action》java
註解(也被成爲元數據)爲咱們在代碼中添加信息提供了一種形式化的方法,使咱們能夠在稍後某個時刻很是方便地使用這些數據。 --《Thinging in Java》git
在Java和Kotlin中聲明註解的方式仍是有些差別:github
Java:
public @interface MyAnnotation {
}
public @interface MyAnnotation2{
String value();
}
Kotlin:
annotation class MyAnnotation
annotation class MyAnnotation2(val value:String)
複製代碼
能夠應用到註解類上的註解被稱爲元註解。express
比較常見的元註解有@Target、@Retention
bash
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
複製代碼
public enum class AnnotationTarget {
/** Class, interface or object, annotation class is also included */
CLASS,
/** Annotation class only */
ANNOTATION_CLASS,
/** Generic type parameter (unsupported yet) */
TYPE_PARAMETER,
/** Property */
PROPERTY,
/** Field, including property's backing field */ FIELD, /** Local variable */ LOCAL_VARIABLE, /** Value parameter of a function or a constructor */ VALUE_PARAMETER, /** Constructor only (primary or secondary) */ CONSTRUCTOR, /** Function (constructors are not included) */ FUNCTION, /** Property getter only */ PROPERTY_GETTER, /** Property setter only */ PROPERTY_SETTER, /** Type usage */ TYPE, /** Any expression */ EXPRESSION, /** File */ FILE, /** Type alias */ @SinceKotlin("1.1") TYPEALIAS } 複製代碼
Target代表你的註解能夠被應用的元素類型,包括類、文件、函數、屬性等,若是須要你能夠聲明多個對象。ide
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)
複製代碼
public enum class AnnotationRetention {
/** Annotation isn't stored in binary output */ SOURCE, /** Annotation is stored in binary output, but invisible for reflection */ BINARY, /** Annotation is stored in binary output and visible for reflection (default retention) */ RUNTIME } 複製代碼
Retention被用來講明你聲明的註解是否會被存儲到.class文件,以及在運行時是否能夠經過反射來訪問它。函數
從取值的方式來講能夠分爲兩類:編譯時註解和運行時註解。工具
使用反射在程序運行時操做。目前最著名的使用運行時註解的開源庫就是Retrofit。(因爲運行時註解使用了反射,必然會影響到效率)學習
顧名思義,就是編譯時去處理的註解。dagger,butterKnife,包括谷data binding,都用到了編譯時註解。其核心就是編譯時註解+APT+動態生成字節碼。ui
APT (Annotation Processor Tool):註解處理器是一個在javac中的,用來編譯時掃描和處理的註解的工具。你能夠爲特定的註解,註冊你本身的註解處理器。 註解處理器能夠生成Java代碼,這些生成的Java代碼會組成 .java 文件,但不能修改已經存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件,會同時與其餘普通的手寫Java源代碼一塊兒被javac編譯。
KAPT與APT徹底相同,只是在Kotlin下的註解處理器。
使用編譯時註解+APT+動態生成字節碼完成了一個butterKnife最基礎的findViewById的功能,適合入門學習。
在項目中新建一個java library,聲明兩個註解,一個用來註解類,一個用來註解方法。
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class MyClass
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.SOURCE)
annotation class findView(val value: Int = -1)
複製代碼
使用註解
@MyClass
class MainActivity : Activity() {
@findView(R.id.text1)
var text123: TextView? = null
@findView(R.id.text2)
var text2: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
複製代碼
建立一個類繼承自AbstractProcessor
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class MyProcessor : AbstractProcessor() {
override fun getSupportedAnnotationTypes(): Set<String> {
return setOf(MyClass::class.java.canonicalName)
}
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
}
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
return true
}
}
複製代碼
使用kotlinpoet動態生成代碼
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
mLogger.info("processor start")
//獲取全部用@MyClass註解的類
val elements = roundEnv.getElementsAnnotatedWith(MyClass::class.java)
elements.forEach {
val typeElement = it as TypeElement
val members = elementUtils!!.getAllMembers(typeElement)
//建立一個bingdView的方法,參數爲activity,並使用JvmStatic註解
val bindFunBuilder = FunSpec.builder("bindView").addParameter("activity", typeElement.asClassName()).addAnnotation(JvmStatic::class.java)
members.forEach {
//獲取全部@findview註解的屬性
val find: findView? = it.getAnnotation(findView::class.java)
if (find != null) {
mLogger.info("find annotation " + it.simpleName)
//方法中添加findviewById
bindFunBuilder.addStatement("activity.${it.simpleName} = activity.findViewById(${find.value})")
}
}
val bindFun = bindFunBuilder.build()
//生成一個由@MyClass註解的類的名稱加_bindView後綴的類,其中有一個靜態方法bindView
val file = FileSpec.builder(getPackageName(typeElement), it.simpleName.toString()+"_bindView")
.addType(TypeSpec.classBuilder(it.simpleName.toString()+"_bindView")
.addType(TypeSpec.companionObjectBuilder()
.addFunction(bindFun)
.build())
.build())
.build()
file.writeFile()
}
mLogger.info("end")
return true
}
複製代碼
編譯代碼,本例就會在build下生成一個MainActivity_bindView的類,其中有一個靜態方法bindview,傳入的參數是activity,方法中是咱們註解的text123和text2的findviewById。只要在Activity啓動時調用這個靜態方法就能夠實現View的綁定。
在MainActivity中調用靜態方法就能夠綁定View,可是因爲這個類是編譯時生成的,在MainActivity中其實並不知道有這個類存在,沒法直接調用。這個時候就要使用反射了。咱們在生成類的時候使用「類名」+「_bindView」的方式,知道了靜態方法的類名就可使用反射執行方法了。
class MyKapt {
companion object {
fun bindView(target: Any) {
val classs = target.javaClass
val claName = classs.name + "_bindView"
val clazz = Class.forName(claName)
val bindMethod = clazz.getMethod("bindView", target::class.java)
val ob = clazz.newInstance()
bindMethod.invoke(ob, target)
}
}
}
複製代碼
MainActivity中:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MyKapt.bindView(this)
}
複製代碼
搞定!這是我寫的簡單Demo 項目Demo