編譯時註解Kapt 實現基礎版butterKnife

註解

一個註解容許你把額外的元數據關聯到一個聲明上。而後元數據就能夠被相關的源代碼工具訪問,經過編譯好的類文件或是在運行時,取決於這個註解是如何配置的。 --《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、@Retentionbash

@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和KAPT

APT (Annotation Processor Tool):註解處理器是一個在javac中的,用來編譯時掃描和處理的註解的工具。你能夠爲特定的註解,註冊你本身的註解處理器。 註解處理器能夠生成Java代碼,這些生成的Java代碼會組成 .java 文件,但不能修改已經存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件,會同時與其餘普通的手寫Java源代碼一塊兒被javac編譯。

KAPT與APT徹底相同,只是在Kotlin下的註解處理器。

實例

使用編譯時註解+APT+動態生成字節碼完成了一個butterKnife最基礎的findViewById的功能,適合入門學習。

1、聲明註解

在項目中新建一個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)
    }
}
複製代碼

2、獲取註解

建立一個類繼承自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
    }
}
複製代碼

3、動態生成字節碼

使用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的綁定。

4、調用

在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

相關文章
相關標籤/搜索