kotlin MVVM+retrofit2+協(xié)程 Repository層apt優(yōu)化方案

  • Repository層是整個(gè)架構(gòu)數(shù)據(jù)來源的地方,包括網(wǎng)絡(luò)和數(shù)據(jù)庫等
    項(xiàng)目模塊化呢,又會(huì)讓每個(gè)coder要么維護(hù)同一個(gè)公共模塊定義接口的類,外加Repository類,要么維護(hù)多個(gè)自己模塊,多個(gè)Repository類。同一類操作帶來代碼管理沖突,只有每個(gè)人維護(hù)各自的接口類最合適。所以,下面就用apt對(duì)多個(gè)接口的方案實(shí)行優(yōu)化
  1. 創(chuàng)建apt-annotation和apt-repository的kotlin Library
    apt-annotation定義注解,apt-repository實(shí)現(xiàn)AbstractProcessor然后自動(dòng)生成kotlin代碼


    自定義注解模塊

    apt-annotation中自定義注解

/**
 * 網(wǎng)絡(luò)請(qǐng)求方法的注解
 */
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
public annotation class Repository(val value : RetrofitLinkType = RetrofitLinkType.RETROFIT_DEFAULT)

RetrofitLinkType只是為了給方法添加一個(gè)標(biāo)識(shí),表明該方法是做什么的,方便日志攔截打印數(shù)據(jù)出來,文件就不需要打印那么多body內(nèi)容。打印也是亂碼

/**
 * 給RetrofitManager添加注解
 */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class RetrofitManager()

我們一般會(huì)封裝一個(gè)RetrofitManager類來管理retrofit和添加一些請(qǐng)求頭、攔截器等,就按照我目前項(xiàng)目來寫,RetrofitManager類暴露兩個(gè)方法
~~這里定死該方法,以便apt生成Repository類獲取RetrofitManager,當(dāng)然也可以自己再定義注解來獲取。為了方便就寫死吧

//獲取RetrofitManager對(duì)象
public static RetrofitManager getInstance(RetrofitLinkType type){
        return  new RetrofitManager(type);
    }
//創(chuàng)建網(wǎng)絡(luò)api接口文件
    public <T> T create(Class<T> service){
        return retrofit.create(service);
    }

enum class RetrofitLinkType {
/**
* 默認(rèn)請(qǐng)求
*/
RETROFIT_DEFAULT,

/**
 * 文件請(qǐng)求
 */
RETROFIT_FILE,
RETROFIT_DEFAULT2,
RETROFIT_DEFAULT3

}
2.apt-repository實(shí)現(xiàn)
創(chuàng)建RepositoryProcessor類繼承AbstractProcessor

  • 在main文件夾下創(chuàng)建resources文件夾,再創(chuàng)建META-INF文件夾,再創(chuàng)建service文件夾。添加名為javax.annotation.processing.Processor的文件
    內(nèi)容寫上剛才創(chuàng)建的xxx(包名).RepositoryProcessor
    當(dāng)然你也可以使用谷歌的AutoService


  • apt-repository模塊gradle文件配置
plugins {
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm'
    id 'kotlin'
    id 'kotlin-kapt'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {

    implementation project(path: ':apt-annotation')
//    annotationProcessor "com.google.auto.service:auto-service:1.0.1"
//    implementation "com.google.auto.service:auto-service:1.0.1"
    implementation 'com.squareup:kotlinpoet:1.12.0'
}

apt-repository模塊中build.gradle文件JavaVersion.VERSION_1_7需要換成JavaVersion.VERSION_1_8

  • RepositoryProcessor實(shí)現(xiàn)
    進(jìn)入正題:該類是對(duì)整個(gè)項(xiàng)目注解進(jìn)行掃描處理的類,涉及到Filer(生成文件所需)Element(每個(gè)被注解的元素)
    首先定義一個(gè)map集合。key保存每個(gè)模塊的api接口文件類名,map保存一個(gè)創(chuàng)建Repository類的對(duì)象ClassCreatorProxy,該對(duì)象持有全部添加@Repository注解的api接口方法。
    ClassCreatorProxy實(shí)現(xiàn)了生成Repository代碼的規(guī)則,并最終生成一個(gè)(api接口類名+_repository)的單例模式的對(duì)象類,processor代碼如下
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class RepositoryProcessor : AbstractProcessor() {
    //日志控制
    private var messager: Messager? = null
    //生成文件
    private var filer: Filer? = null
    private var mElementUtils: Elements? = null
    private val mProxyMap: HashMap<String, ClassCreatorProxy> = HashMap()
 
    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        filer = processingEnv?.filer
        messager = processingEnv!!.messager
        mElementUtils = processingEnv.elementUtils
        messager!!.printMessage(Diagnostic.Kind.NOTE, "APT-------------------初始化")
    }
    /**
     * 定義支持的注解類型,只需要掃描我們自定義的就夠了
     */
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        val set= mutableSetOf<String>()
        set.add(Repository::class.java.canonicalName)
        set.add(RetrofitManager::class.java.canonicalName)
        return set
    }

    override fun process(p0: MutableSet<out TypeElement>, roundEnvironment: RoundEnvironment?): Boolean {
        if (p0.isEmpty()) return  false
        //獲取項(xiàng)目添加@RetrofitManager唯一的retrofitManager類,需要該類暴露getInstance和service方法,見上面的描述
        var retrofitManager:String?=null
        val annotatedTypes1: Set<Element?> =
            roundEnvironment?.getElementsAnnotatedWith(RetrofitManager::class.java)!! //類注解
        for (element in annotatedTypes1) {
            val typeElement = element as TypeElement
            retrofitManager = typeElement.qualifiedName.toString()//全路徑
        }
        if(retrofitManager==null)return false
        //獲取api接口類上添加@Repository的方法注解
        val annotatedTypes: Set<Element?> =
            roundEnvironment.getElementsAnnotatedWith(Repository::class.java)!! //類注解
        //遍歷元素
        for (element in annotatedTypes) {
            val methodElement = element as ExecutableElement
            //獲取全類名
            val classElement = methodElement.enclosingElement as TypeElement //被一個(gè)范圍包裹的外層(包裹方法的就只有類了)
            val fullName = classElement.qualifiedName.toString()
            messager!!.printMessage(Diagnostic.Kind.NOTE, fullName)
            //看內(nèi)存緩存中是否有對(duì)應(yīng)的ClassCreatorProxy類
            var proxy: ClassCreatorProxy? = mProxyMap[fullName]
            if (proxy == null) {
                proxy = mElementUtils?.let { ClassCreatorProxy(it, classElement,retrofitManager!!) }
                if (proxy != null) {
                    mProxyMap[fullName] = proxy
                }
            }
            //向每個(gè)ClassCreatorProxy對(duì)象添加api中方法。
            proxy?.addMethodName(methodElement)
        }
        //通過遍歷mProxyMap,創(chuàng)建kotlin文件
        for (key in mProxyMap.keys) {
            val proxyInfo = mProxyMap[key]

            val classFile: FileSpec =
                FileSpec.builder(proxyInfo!!.getPackageName(), proxyInfo.getClassName())
                    .addType(proxyInfo.generateJavaCode2()!!)
                    .build()
            try {
                //生成kotlin文件
                filer?.let { classFile.writeTo(it) }
            } catch (e: IOException) {
                messager?.printMessage(
                    Diagnostic.Kind.NOTE,
                    " --> create " + proxyInfo.getProxyClassFullName() + "error"
                )
            }
        }


        return true
    }
}
  • ClassCreatorProxy實(shí)現(xiàn)
    java是使用的javapoet,而kotlin是使用的kotlinpoet
    gradle添加 implementation 'com.squareup:kotlinpoet:1.12.0'
    當(dāng)然也可以自己慢慢用字符串拼接。這個(gè)過程可能會(huì)出人命。
    接下來的思路是生成一個(gè)單例類型repository類
    第一步:TypeSpec.companionObjectBuilder()生成伴生對(duì)象


    生成屬性

    生成方法體

    第二步:FunSpec.builder生成各個(gè)方法

kotlinpot在對(duì)Any類型或者String類型的處理上會(huì)處理成java.lang.Object和java.lang.String。這樣就會(huì)把最終生成的代碼搞成java的類型。導(dǎo)致方法參數(shù)類型對(duì)應(yīng)不上。比如


kotlin代碼是kotlin.String,apt生成的代碼是java.lang.String

同理,Any類型的會(huì)生成Object類型。這不是我要的那種結(jié)果。

使用ExecutableElement元素可以拿到該方法名字以及參數(shù)名,參數(shù)類型,還有返回值,返回值類型
將其中的java.lang.Object替換成Any,java.lang.String替換成String,由于沒發(fā)現(xiàn)里面有獲取類型和修改的方法,只能轉(zhuǎn)成字符串替換,再用String::class.asTypeName()得到TypeName傳遞給ParameterSpec.builder以便生成具體的參數(shù)和名字


處理參數(shù)類型

ExecutableElement在獲取到api接口方法的時(shí)候。由于協(xié)程需要使用了suspend關(guān)鍵字,會(huì)把返回值實(shí)體包裹在kotlin.coroutines.Continuation中。當(dāng)成參數(shù)傳遞。

kotlin.coroutines.Continuation<in java.lang.String>

就導(dǎo)致了ExecutableElement在getReturnType的時(shí)候獲取到的是一個(gè)object對(duì)象。我們反而不能使用getReturnType來生成返回值了,需要在參數(shù)中最后一個(gè)參數(shù)去找到參數(shù)類型。把其中的泛型包裹的對(duì)象取出來傳遞給FunSpec.builder.returns()。

由于kotlinPoet不是很熟練。所以不清楚具體方法。只能用老辦法字符串替代。
最后用TypeVariableName接收字符串傳遞給FunSpec.builder.returns()

ClassCreatorProxy類完整代碼如下

class ClassCreatorProxy(elementUtils: Elements, classElement: TypeElement?, private val managerPath:String) {
    private var mClassName: String? = null//
    private var mPackageName: String? = null
    private var mTypeElement: TypeElement? = null
    private var apiService:ClassName//retrofit 接口類
    private var thisClassName:ClassName//需要生成的單例類
    /**
     * 方法集合
     */
    val executableElements: MutableList<ExecutableElement> = mutableListOf()

    init {
        mTypeElement = classElement
        val packageElement = elementUtils.getPackageOf(mTypeElement)
        val packageName = packageElement.qualifiedName.toString()
        val className = mTypeElement!!.simpleName.toString() //只獲取類名
        mPackageName = packageName
        mClassName = className + "_repository"
        apiService = ClassName(getPackageName(), mTypeElement!!.simpleName.toString())
        thisClassName=ClassName(getPackageName(),getClassName())
    }

    /**
     * 添加方法到executableElements緩存
     * @param element
     */
    fun addMethodName(element: ExecutableElement) {
        executableElements.add(element)
    }
/////////----------------------------------
    /**
     * 生成伴生對(duì)象
     */
    private fun generateCompanion():TypeSpec{
        return TypeSpec.companionObjectBuilder()
            .addProperty(generateProperty())
            .addProperty(generateProperty2())
            .addFunction(generateFunction("getInstance"))
            .build()
    }

    /**
     * 生成伴生對(duì)象中屬性mApiService
     */
    private fun generateProperty():PropertySpec{
        return PropertySpec.builder("mApiService",  apiService.copy(nullable = true))//可空參數(shù)
            // 初始化值
            .initializer("null")
            // 修飾符
            .addModifiers(KModifier.PRIVATE)
            .mutable()//var
            // 注釋
            .build()
    }
    /**
     * 生成伴生對(duì)象中屬性instance
     */
    private fun generateProperty2():PropertySpec{
        return PropertySpec.builder("instance",thisClassName)
            .initializer("%T()",thisClassName)//默認(rèn)值是new class對(duì)象
            // 修飾符
            .addModifiers(KModifier.PRIVATE)
            .build()
    }
    /**
     * 生成getInstance方法體
     */
    private fun generateFunction(funName:String):FunSpec{
        val params=ParameterSpec.builder("type", RetrofitLinkType::class)
            .defaultValue("${RetrofitLinkType::class.java.`package`.name}.RetrofitLinkType.%L", RetrofitLinkType.RETROFIT_DEFAULT)
            .build()
        return FunSpec.builder(funName)
            .addParameter(params)
            .returns(thisClassName)
            .addStatement("mApiService= %L.getInstance(type).create(%L)",managerPath,apiService.simpleName+"::class.java")
            .addStatement("return instance")
            .build()
    }
    /**
     * 最后調(diào)用創(chuàng)建Java代碼   javapoet
     * @return
     */
    fun generateJavaCode2(): TypeSpec? {
        val cb=FunSpec.constructorBuilder()
            .addModifiers(KModifier.PRIVATE)
            .build()
        return mClassName?.let {
            TypeSpec //class名稱設(shè)置
                .classBuilder(it) //類為public
                .addFunction(cb)//私有構(gòu)造函數(shù)
                .addType(generateCompanion())
                .addModifiers(KModifier.PUBLIC)
                .addFunctions(generateMethods2())
                .build()
        }
    }

    /**
     * 生成調(diào)用的方法
     */
    @OptIn(DelicateKotlinPoetApi::class)
    private fun generateMethods2(): Iterable<FunSpec> {
        val hashMap= mutableListOf<FunSpec>()
        executableElements.forEach {
            //獲取到方法里面的參數(shù)
            val params= mutableListOf<ParameterSpec>()
            val parameters = it.parameters
            val returnParams=StringBuilder()

            var returnName =TypeVariableName("Any")
            parameters.forEachIndexed { index, param->
                var asType = param.asType().asTypeName()
                //kotlin協(xié)程是將返回值包裹成kotlin.coroutines.Continuation<T>當(dāng)做參數(shù)傳遞的
                if (asType.toString().contains("kotlin.coroutines.Continuation")){
                    var asTypeString=asType.toString()
                    if(asTypeString.contains("java.lang.Object")){
                        asTypeString = asType.toString().replace("java.lang.Object", "Any")
                    }else if(asTypeString.contains("java.lang.String")){
                        asTypeString = asType.toString().replace("java.lang.String", "String")
                    }
                    asTypeString = asTypeString.removeSurrounding("kotlin.coroutines.Continuation<in ", ">")
                    asTypeString = asTypeString.removeSurrounding("kotlin.coroutines.Continuation<", ">")
                    returnName = TypeVariableName(asTypeString)
                }else{
                    ///這里敲重點(diǎn)。java.lang.String 只有轉(zhuǎn)為kotlin.String
                    if (asType.toString()=="java.lang.String"){
                        asType = kotlin.String::class.asTypeName()
                    }
                    val b = ParameterSpec.builder(param.simpleName.toString(), asType)//將參數(shù)構(gòu)建出來
                        .build()
                    params.add(b)
                    if(index==0){
                        returnParams.append(param.simpleName.toString())
                    }else{
                        returnParams.append(",".plus(param.simpleName.toString()))
                    }
                }
            }
            //獲取到返回類型
            var asTypeName = it.returnType.asTypeName()

            val build = FunSpec.builder(it.simpleName.toString())
                .addParameters(params)
                .addModifiers(KModifier.PUBLIC,KModifier.SUSPEND)
                .returns(returnName)
                .addStatement(
                    if (asTypeName.toString()=="kotlin.Unit")
                        "mApiService!!.${it.simpleName}(%L)"
                    else
                        "return mApiService!!.${it.simpleName}(%L)"
                    ,returnParams.toString())
                .build()
            hashMap.add(build)
        }
        return hashMap
    }
    fun getPackageName(): String {
        return mPackageName!!
    }
    fun getProxyClassFullName(): String {
        return "$mPackageName.$mClassName"
    }
    fun getClassName(): String {
        return "$mClassName"
    }

3.如何使用
工程結(jié)構(gòu)如下大致


QQ圖片20230306184151.png

各自module的gradle文件依賴commom組件。common的gradle文件添加apt依賴,kotlin必須是kapt

    api project(path: ':apt-annotation')
    kapt project(path: ':apt-repository')

接口方法添加注解

RetrofitManager添加自定義注解即可

4.最終apt會(huì)幫助我們自動(dòng)生成kotlin協(xié)程方法

我們只需要在viewModel中調(diào)用方法

   fun dsad(ctx:Activity){
        viewModelScope.launch{
            //只需要各自module的接口類名(比如ApiBasic)加上 _repository就可以一行代碼直接網(wǎng)絡(luò)請(qǐng)求了
            val result1 =ApiBasic_repository.getInstance().test2("tiyu_new","15","pcFeed","pd")
        }
    }

這樣每個(gè)負(fù)責(zé)自己模塊的小伙伴就可以負(fù)責(zé)各自的接口類而不用影響到其他人,也不用單獨(dú)去寫接口實(shí)現(xiàn)類了

OVER

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容