美文网首页
kotlin MVVM+retrofit2+协程 Reposi

kotlin MVVM+retrofit2+协程 Reposi

作者: 叫我壮士 | 来源:发表于2023-03-05 19:05 被阅读0次
  • Repository层是整个架构数据来源的地方,包括网络和数据库等
    项目模块化呢,又会让每个coder要么维护同一个公共模块定义接口的类,外加Repository类,要么维护多个自己模块,多个Repository类。同一类操作带来代码管理冲突,只有每个人维护各自的接口类最合适。所以,下面就用apt对多个接口的方案实行优化
  1. 创建apt-annotation和apt-repository的kotlin Library
    apt-annotation定义注解,apt-repository实现AbstractProcessor然后自动生成kotlin代码


    自定义注解模块

    apt-annotation中自定义注解

/**
 * 网络请求方法的注解
 */
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
public annotation class Repository(val value : RetrofitLinkType = RetrofitLinkType.RETROFIT_DEFAULT)

RetrofitLinkType只是为了给方法添加一个标识,表明该方法是做什么的,方便日志拦截打印数据出来,文件就不需要打印那么多body内容。打印也是乱码

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

我们一般会封装一个RetrofitManager类来管理retrofit和添加一些请求头、拦截器等,就按照我目前项目来写,RetrofitManager类暴露两个方法
~~这里定死该方法,以便apt生成Repository类获取RetrofitManager,当然也可以自己再定义注解来获取。为了方便就写死吧

//获取RetrofitManager对象
public static RetrofitManager getInstance(RetrofitLinkType type){
        return  new RetrofitManager(type);
    }
//创建网络api接口文件
    public <T> T create(Class<T> service){
        return retrofit.create(service);
    }

enum class RetrofitLinkType {
/**
* 默认请求
*/
RETROFIT_DEFAULT,

/**
 * 文件请求
 */
RETROFIT_FILE,
RETROFIT_DEFAULT2,
RETROFIT_DEFAULT3

}
2.apt-repository实现
创建RepositoryProcessor类继承AbstractProcessor

  • 在main文件夹下创建resources文件夹,再创建META-INF文件夹,再创建service文件夹。添加名为javax.annotation.processing.Processor的文件
    内容写上刚才创建的xxx(包名).RepositoryProcessor
    当然你也可以使用谷歌的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实现
    进入正题:该类是对整个项目注解进行扫描处理的类,涉及到Filer(生成文件所需)Element(每个被注解的元素)
    首先定义一个map集合。key保存每个模块的api接口文件类名,map保存一个创建Repository类的对象ClassCreatorProxy,该对象持有全部添加@Repository注解的api接口方法。
    ClassCreatorProxy实现了生成Repository代码的规则,并最终生成一个(api接口类名+_repository)的单例模式的对象类,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
        //获取项目添加@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 //被一个范围包裹的外层(包裹方法的就只有类了)
            val fullName = classElement.qualifiedName.toString()
            messager!!.printMessage(Diagnostic.Kind.NOTE, fullName)
            //看内存缓存中是否有对应的ClassCreatorProxy类
            var proxy: ClassCreatorProxy? = mProxyMap[fullName]
            if (proxy == null) {
                proxy = mElementUtils?.let { ClassCreatorProxy(it, classElement,retrofitManager!!) }
                if (proxy != null) {
                    mProxyMap[fullName] = proxy
                }
            }
            //向每个ClassCreatorProxy对象添加api中方法。
            proxy?.addMethodName(methodElement)
        }
        //通过遍历mProxyMap,创建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实现
    java是使用的javapoet,而kotlin是使用的kotlinpoet
    gradle添加 implementation 'com.squareup:kotlinpoet:1.12.0'
    当然也可以自己慢慢用字符串拼接。这个过程可能会出人命。
    接下来的思路是生成一个单例类型repository类
    第一步:TypeSpec.companionObjectBuilder()生成伴生对象


    生成属性
    生成方法体

    第二步:FunSpec.builder生成各个方法

kotlinpot在对Any类型或者String类型的处理上会处理成java.lang.Object和java.lang.String。这样就会把最终生成的代码搞成java的类型。导致方法参数类型对应不上。比如


kotlin代码是kotlin.String,apt生成的代码是java.lang.String
同理,Any类型的会生成Object类型。这不是我要的那种结果。

使用ExecutableElement元素可以拿到该方法名字以及参数名,参数类型,还有返回值,返回值类型
将其中的java.lang.Object替换成Any,java.lang.String替换成String,由于没发现里面有获取类型和修改的方法,只能转成字符串替换,再用String::class.asTypeName()得到TypeName传递给ParameterSpec.builder以便生成具体的参数和名字


处理参数类型

ExecutableElement在获取到api接口方法的时候。由于协程需要使用了suspend关键字,会把返回值实体包裹在kotlin.coroutines.Continuation中。当成参数传递。

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

就导致了ExecutableElement在getReturnType的时候获取到的是一个object对象。我们反而不能使用getReturnType来生成返回值了,需要在参数中最后一个参数去找到参数类型。把其中的泛型包裹的对象取出来传递给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)
    }
/////////----------------------------------
    /**
     * 生成伴生对象
     */
    private fun generateCompanion():TypeSpec{
        return TypeSpec.companionObjectBuilder()
            .addProperty(generateProperty())
            .addProperty(generateProperty2())
            .addFunction(generateFunction("getInstance"))
            .build()
    }

    /**
     * 生成伴生对象中属性mApiService
     */
    private fun generateProperty():PropertySpec{
        return PropertySpec.builder("mApiService",  apiService.copy(nullable = true))//可空参数
            // 初始化值
            .initializer("null")
            // 修饰符
            .addModifiers(KModifier.PRIVATE)
            .mutable()//var
            // 注释
            .build()
    }
    /**
     * 生成伴生对象中属性instance
     */
    private fun generateProperty2():PropertySpec{
        return PropertySpec.builder("instance",thisClassName)
            .initializer("%T()",thisClassName)//默认值是new class对象
            // 修饰符
            .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()
    }
    /**
     * 最后调用创建Java代码   javapoet
     * @return
     */
    fun generateJavaCode2(): TypeSpec? {
        val cb=FunSpec.constructorBuilder()
            .addModifiers(KModifier.PRIVATE)
            .build()
        return mClassName?.let {
            TypeSpec //class名称设置
                .classBuilder(it) //类为public
                .addFunction(cb)//私有构造函数
                .addType(generateCompanion())
                .addModifiers(KModifier.PUBLIC)
                .addFunctions(generateMethods2())
                .build()
        }
    }

    /**
     * 生成调用的方法
     */
    @OptIn(DelicateKotlinPoetApi::class)
    private fun generateMethods2(): Iterable<FunSpec> {
        val hashMap= mutableListOf<FunSpec>()
        executableElements.forEach {
            //获取到方法里面的参数
            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协程是将返回值包裹成kotlin.coroutines.Continuation<T>当做参数传递的
                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{
                    ///这里敲重点。java.lang.String 只有转为kotlin.String
                    if (asType.toString()=="java.lang.String"){
                        asType = kotlin.String::class.asTypeName()
                    }
                    val b = ParameterSpec.builder(param.simpleName.toString(), asType)//将参数构建出来
                        .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.如何使用
工程结构如下大致


QQ图片20230306184151.png

各自module的gradle文件依赖commom组件。common的gradle文件添加apt依赖,kotlin必须是kapt

    api project(path: ':apt-annotation')
    kapt project(path: ':apt-repository')
接口方法添加注解 RetrofitManager添加自定义注解即可
4.最终apt会帮助我们自动生成kotlin协程方法

我们只需要在viewModel中调用方法

   fun dsad(ctx:Activity){
        viewModelScope.launch{
            //只需要各自module的接口类名(比如ApiBasic)加上 _repository就可以一行代码直接网络请求了
            val result1 =ApiBasic_repository.getInstance().test2("tiyu_new","15","pcFeed","pd")
        }
    }

这样每个负责自己模块的小伙伴就可以负责各自的接口类而不用影响到其他人,也不用单独去写接口实现类了

OVER

相关文章

网友评论

      本文标题:kotlin MVVM+retrofit2+协程 Reposi

      本文链接:https://www.haomeiwen.com/subject/efgkldtx.html