コンパイル時注記Kapt実装ベース版butterKnife

9038 ワード

注釈
注釈を使用すると、追加のメタデータを宣言に関連付けることができます.メタデータは、関連するソース・ツールによってアクセスされ、コンパイルされたクラス・ファイルまたは実行時に、この注釈がどのように構成されているかによって異なります.--《Kotlin in Action》
注記(メタデータとしても使用されます)は、コードに情報を追加するための形式的な方法を提供し、後でこれらのデータを簡単に使用することができます.--《Thinging in Java》
JavaとKotlinで注釈を宣言する方法にはまだ違いがあります.
Java:
public @interface MyAnnotation {
}

public @interface MyAnnotation2{
	String value();
}

Kotlin:
annotation class MyAnnotation

annotation class MyAnnotation2(val value:String)

メタ注記
注釈クラスに適用できる注釈をメタ注釈と呼ぶ.
比較的一般的なメタ注釈は@Target、@Retentionである
@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は、クラス、ファイル、関数、属性など、注釈が適用できる要素のタイプを示しています.必要に応じて複数のオブジェクトを宣言できます.
@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ファイルに格納されるかどうか、および実行時に反射によってアクセスできるかどうかを説明するために使用されます.
注記の分類
値の取り方から言えば、コンパイル時注記と実行時注記の2種類に分けられます.
実行時注記
反射を使用してプログラムの実行時に操作します.現在最も有名なランタイム注釈を使用するオープンソースライブラリはRetrofitです.(実行時の注記は反射を使用するため、必然的に効率に影響します)
コンパイル時の注記
その名の通り、コンパイル時に処理する注釈です.dagger、butterKnife、谷data bindingを含め、コンパイル時注釈が使用されています.その核心はコンパイル時注釈+APT+動的生成バイトコードです.
APTとKAPT
APT(Annotation Processor Tool):注釈プロセッサはjavacでコンパイル時にスキャンおよび処理するための注釈のツールです.特定の注釈のために、独自の注釈プロセッサを登録できます.注釈プロセッサはJavaコードを生成できます.これらの生成したJavaコードは.javaファイルを構成しますが、既存のJavaクラスは変更できません.(既存のクラスにメソッドを追加することはできません).これらの生成されたJavaファイルは、他の一般的な手書きJavaソースコードとともにjavacによってコンパイルされます.
KAPTはAPTと全く同じで,Kotlin下の注釈プロセッサにすぎない.
≪インスタンス|Instance|emdw≫
コンパイル時の注記+APT+動的生成バイトコードを使用してbutterKnifeの最も基本的なfindViewByIdの機能を完成させ、入門学習に適しています.
一、注釈を宣言する
プロジェクトにjava libraryを新規作成し、2つの注釈を宣言します.1つはクラスを注釈し、1つは方法を注釈します.
@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 {
        return setOf(MyClass::class.java.canonicalName)
    }

    override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        
    }

    override fun process(annotations: MutableSet, roundEnv: RoundEnvironment): Boolean {

        return true
    }
}

三、動的にバイトコードを生成する
kotlinpoetを使用してコードを動的に生成する
 override fun process(annotations: MutableSet, 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で、メソッドでは私たちが注釈したtext 123とtext 2の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です.