- 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)化
-
創(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)不上。比如


同理,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ù)和名字

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()。

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)如下大致

各自module的gradle文件依賴commom組件。common的gradle文件添加apt依賴,kotlin必須是kapt
api project(path: ':apt-annotation')
kapt project(path: ':apt-repository')
接口方法添加注解

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



