前情回顧
上一篇文章我們主要講的gradle的統(tǒng)一管理,組件之間的通信方案,以及如何使用全局map進(jìn)行頁(yè)面跳轉(zhuǎn)。每個(gè)頁(yè)面都需要在application中進(jìn)行注冊(cè),這樣肯定是不行的,根據(jù)面向?qū)ο蟮乃枷耄?我們先對(duì)其進(jìn)行一層封裝,做到在編譯器自動(dòng)進(jìn)行注冊(cè)。
封裝全局Map
首先我們將通信方案作為一個(gè)組件,創(chuàng)建一個(gè)irouter_api的module,由base去依賴它,然后把RouteBean實(shí)體類挪進(jìn)來(lái),為了方便以后擴(kuò)展,我們?cè)赗outeBean中增加幾個(gè)屬性
class RouteBean {
/**
* 為了方便擴(kuò)展,這里做一個(gè)標(biāo)識(shí)
*/
enum class TypeEnum {
ACTIVITY,
FRAGMENT
}
// 組名: order | goods ...
var group: String? = null
// 路徑: order/order_list
var path: String? = null
// 類: OrderActivity.class
var clazz: Class<*>? = null
// 標(biāo)識(shí)是Activity, Fragment,或是其他
var typeEnum: TypeEnum? = null
}
組名,路徑和類不變, 增加了一個(gè)enum的標(biāo)識(shí),用來(lái)標(biāo)記是個(gè)Fragment,還是一個(gè)Activity,這時(shí)候項(xiàng)目結(jié)構(gòu)應(yīng)該是這樣的
不妨再考慮一下,只使用一個(gè)全局map的話,隨著項(xiàng)目越來(lái)越大,activity越來(lái)越多,查找肯定會(huì)帶來(lái)一定的效率問(wèn)題,如果我們一個(gè)模塊使用一個(gè)map呢?這樣可以在很大程度上解決這種性能影響。怎么實(shí)現(xiàn)呢?我們先畫(huà)畫(huà)圖,理清思路
首先是一個(gè)全局的IRouterGroup, 里面有一個(gè)全局map,key就是module的group, value用于存放每個(gè)模塊的路徑,我們把value進(jìn)一步封裝成IRouterPath,IRouterPath通過(guò)組名存放組下面的路徑。思路都理清了,下面進(jìn)入擼碼階段。
基于面向接口編程,我們定義一個(gè)path接口,用于存放某一個(gè)模塊中的所有的路徑:
interface IRouterPath {
/**
* Map :
* key -》》》》 gourp
* value -》》》》 path, class, 等信息
*/
fun getPath(): Map<String, RouteBean>
}
再定義一個(gè)group接口,用于存放某個(gè)group的所有的path,因?yàn)閏lass是要實(shí)現(xiàn)IRouterPath接口的,所以使用out限定符:
interface IRouterGroup {
/**
* Map
* key ->>>>> group
*
* value ->>>>> path集合
*/
fun getGroupMap(map: Map<String, Class<out IRouterPath>>)
}
下面我們基于order模塊進(jìn)行實(shí)現(xiàn),比如我們訂單模塊有一個(gè)訂單列表和一個(gè)訂單詳情,這時(shí)候path的實(shí)現(xiàn)應(yīng)該是這樣的:
class OrderRouterPath : IRouterPath {
override fun getPath(): Map<String, RouteBean> {
val map = mutableMapOf<String, RouteBean>()
// 訂單列表
map["component_order/list"] = RouteBean().apply {
group = "component_order"
path = "component_order/list"
clazz = OrderActivity::class.java
typeEnum = RouteBean.TypeEnum.ACTIVITY
}
// 訂單詳情
map["component_order/detail"] = RouteBean().apply {
group = "component_order"
path = "component_order/detail"
clazz = OrderDetailActivity::class.java
typeEnum = RouteBean.TypeEnum.ACTIVITY
}
return map
}
}
組用于包裝path的實(shí)現(xiàn):
class OrderRouterGroup : IRouterGroup {
override fun getGroupMap(): MutableMap<String, Class<out IRouterPath>> {
val map = mutableMapOf<String, Class<out IRouterPath>>()
map["component_order"] = OrderRouterPath::class.java
return map
}
}
這樣處理之后,就可以從其他組件跳轉(zhuǎn)了,比如從goods模塊跳轉(zhuǎn)到訂單列表,之前的邏輯就應(yīng)該改成這樣:
find<TextView>(R.id.tvGoods).setOnClickListener {
// 找到組map
val groupClazz = Class.forName("com.kangf.art.order.router.OrderRouterGroup")
val groupInstance = groupClazz.newInstance() as IRouterGroup
// 通過(guò)組找到路徑的map
val pathInstance = groupInstance.getGroupMap()["component_order"]!!.newInstance() as IRouterPath
// 通過(guò)路徑的map找到組對(duì)應(yīng)的routeBean
val routeBean = pathInstance.getPath()["component_order/list"]
// 找到對(duì)應(yīng)的class進(jìn)行跳轉(zhuǎn)
val clazz = routeBean!!.clazz
startActivity(Intent(this, clazz))
}
我們來(lái)看一下運(yùn)行效果

WTF? 我只要一個(gè)跳轉(zhuǎn)頁(yè)面,你給我搞這么一堆邏輯?還好意思叫封裝?各位請(qǐng)收好手中的菜葉子臭雞蛋,我們下一步才是真正的開(kāi)始,好戲還在后頭呢!
注解處理器
上面的做法顯然 是不行的,在頁(yè)面增多的情況下,我們將會(huì)做更多的重復(fù)性的工作,那何不將這些工作完全交給編譯器來(lái)解決呢?這時(shí)候就用到了APT技術(shù)。 APT可以在編譯時(shí)檢查所有的注解,而我們正好可以借助它生成代碼,來(lái)替我們完成這些重復(fù)的工作。
APT是什么?
APT(Annotation Processing Tool) 是一種處理注釋的工具,它對(duì)源代碼文件進(jìn)行檢測(cè)找出其中的Annotation,根據(jù)注解自動(dòng)生成代碼,如果想要自定義的注解處理器能夠正常運(yùn)行,必須要通過(guò)APT工具來(lái)進(jìn)行處理。 也可以樣理解,只有通過(guò)聲明APT工具后,程序在編譯期間自定義注解解釋器才能執(zhí)行。 通俗理解:根據(jù)規(guī)則,幫我們生成代碼、生成類文件。
注解
有注解處理器,就不得不提到注解,對(duì)注解不了解的,可以先看一下這篇文章:
IOC依賴注入(一)— 手寫(xiě)B(tài)utterKnife框架
講的是運(yùn)行時(shí)注解,而我們現(xiàn)在用到的,是編譯時(shí)注解。
注解處理器的API
首先我們創(chuàng)建一個(gè)java/kotlin的module作為注解處理器,新建一個(gè)類繼承javax.annotation.processing.AbstractProcessor
class IRouterProcessor : AbstractProcessor() {
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
return true
}
}
我們需要了解注解處理器用到的API,這對(duì)大部分人來(lái)說(shuō)可能是陌生的,不過(guò)這很簡(jiǎn)單,大家用過(guò)一次之后都會(huì)對(duì)其有所了解。
Element
可以看到process方法中有一個(gè)MutableSet,這個(gè)就是所有注解的元素的集合,它的泛型是TypeElement的下限類型。
對(duì)于java源文件來(lái)說(shuō),它其實(shí)是一種結(jié)構(gòu)體語(yǔ)言,這些結(jié)構(gòu)組成就是一個(gè)個(gè)的Element組成的,在注解處理器中,Element是一個(gè)非常重要的元素。而我們注解的每一個(gè)元素,其實(shí)就是被包裝成了一個(gè)個(gè)的Element放進(jìn)了MutableSet集合中。Element有以下幾個(gè)實(shí)現(xiàn)類,代表了不同的元素:
PackageElement 表示一個(gè)包程序元素。提供對(duì)有關(guān)包及其成員的信息的訪問(wèn)
ExecutableElement 表示某個(gè)類或接口的方法、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗?
TypeElement 表示一個(gè)類或接口程序元素。提供對(duì)有關(guān)類型及其成員的信息的訪問(wèn)
VariableElement 表示一個(gè)字段、enum 常量、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)
Element節(jié)點(diǎn)中的API
getEnclosedElements() 返回該元素直接包含的子元素
getEnclosingElement() 返回包含該element的父element,與上一個(gè)方法相反
getKind() 返回element的類型,判斷是哪種element
getModifiers() 獲取修飾關(guān)鍵字,入public static final等關(guān)鍵字
getSimpleName() 獲取名字,不帶包名
getQualifiedName() 獲取全名,如果是類的話,包含完整的包名路徑
getParameters() 獲取方法的參數(shù)元素,每個(gè)元素是一個(gè)VariableElement
getReturnType() 獲取方法元素的返回值
getConstantValue() 如果屬性變量被final修飾,則可以使用該方法獲取它的值
Element中有以上幾個(gè)方法,我們一會(huì)將會(huì)頻繁的用到。
kotlinpoet
javapoet是square推出的開(kāi)源java代碼生成框架,提供Java Api生成.java源文件 這個(gè)框架功能非常實(shí)用,也是我們習(xí)慣的Java面向?qū)ο驩OP語(yǔ)法 可以很方便的使用它根據(jù)注解生成對(duì)應(yīng)代碼通過(guò)這種自動(dòng)化生成代碼的方式, 可以讓我們用更加簡(jiǎn)潔優(yōu)雅的方式要替代繁瑣冗雜的重復(fù)工作。kotlinpoet顧名思義,是針對(duì)kotlin的一套框架,我們今天要用到的就是kotlinpoet,它可以幫助我們生成kotlin文件。
kotlinpoet API
在kotlinpoet中,每一個(gè)節(jié)點(diǎn)都對(duì)應(yīng)一個(gè)Spec
類對(duì)象 說(shuō)明
MethodSpec 代表一個(gè)構(gòu)造函數(shù)或方法聲明
TypeSpec 代表一個(gè)類,接口,或者枚舉聲明
FieldSpec 代表一個(gè)成員變量,一個(gè)字段聲明
JavaFile 包含一個(gè)頂級(jí)類的Java文件
ParameterSpec 用來(lái)創(chuàng)建參數(shù)
AnnotationSpec 用來(lái)創(chuàng)建注解
ClassName 用來(lái)包裝一個(gè)類
TypeName 類型,如在添加返回值類型是使用 TypeName.VOID
通配符:
%S 字符串,如:%S, ”hello”
%T 類、接口,如:%T, MainActivity
比如要生成以下代碼:
class Greeter(val name: String) {
fun greet() {
println("""Hello, $name""")
}
}
fun main(vararg args: String) {
Greeter(args[0]).greet()
}
那么在注解處理器中,就要這樣實(shí)現(xiàn):
// 創(chuàng)建一個(gè)類類型
val greeterClass = ClassName("", "Greeter")
// 創(chuàng)建名為HelloWorld的文件
val file = FileSpec.builder("", "HelloWorld")
// 文件中添加一個(gè)Greeter類
.addType(TypeSpec.classBuilder("Greeter")
// 類中的構(gòu)造方法中,增加一個(gè)name屬性
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build())
// 類中增加一個(gè)方法
.addFunction(FunSpec.builder("greet")
// 方法中的語(yǔ)句,%P通配符代表了字符串模板
.addStatement("println(%P)", "Hello, \$name")
.build())
.build())
// 在HelloWorld的文件中增加一個(gè)main方法
.addFunction(FunSpec.builder("main")
// main方法中增加一個(gè)args的可變參數(shù)
.addParameter("args", String::class, VARARG)
// main方法中調(diào)用Greeter類中的greet方法
.addStatement("%T(args[0]).greet()", greeterClass)
.build())
.build()
// 將文件寫(xiě)入輸出流。
file.writeTo(System.out)
關(guān)于kotlin的詳細(xì)使用,可以看它的官方文檔。
擼碼
好了,以上就是關(guān)鍵的API了,下面正式進(jìn)入擼碼階段:
首先創(chuàng)建注解module,用于存放注解,同時(shí)我們需要將RouteBean對(duì)象移動(dòng)到注解module中,因?yàn)樽⒔馓幚砥骱蛂outer_api模塊需要這個(gè)實(shí)體類,同時(shí)再次對(duì)RouteBean進(jìn)行擴(kuò)展:
class RouteBean {
/**
* 為了方便擴(kuò)展,這里做一個(gè)標(biāo)識(shí)
*/
enum class TypeEnum {
ACTIVITY,
FRAGMENT
}
// 組名: order | goods ...
var group: String? = null
// 路徑: order/order_list
var path: String? = null
// 類: OrderActivity.class
var clazz: Class<*>? = null
// 標(biāo)識(shí)是Activity, Fragment,或是其他
var typeEnum: TypeEnum? = null
// 類節(jié)點(diǎn)信息
var element: Element? = null
}
我們?cè)黾右粋€(gè)類節(jié)點(diǎn)信息,方便以后使用。 下面定義一個(gè)編譯時(shí)注解,將作用在Activity或Fragment中:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class IRouter(val path: String)
這個(gè)注解需要出入path。萬(wàn)事俱備,接下來(lái)就輪到注解處理器了。
首先在注解處理器模塊的gradle中引入AutoService,用于幫我們生成MATE-INF.services下的文件,需要這個(gè)文件系統(tǒng)才能幫我們識(shí)別是一個(gè)注解處理器
plugins {
id 'java-library'
id 'kotlin'
id 'kotlin-kapt'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//noinspection AnnotationProcessorOnCompilePath
compileOnly "com.google.auto.service:auto-service:1.0-rc7"
kapt "com.google.auto.service:auto-service:1.0-rc7"
implementation project(path: ':network_annotation')
implementation 'com.squareup:kotlinpoet:1.7.2'
}
工欲善其事必先利其器,編譯完成之后,完善一下我們的注解處理器 :
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions(ProcessorConfig.OPTIONS)
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_NAME)
class IRouterProcessor : AbstractProcessor() {
// 打印日志工具類
private lateinit var mMessage: Messager
// 文件操作類,我們將通過(guò)此類生成kotlin文件
private lateinit var mFiler: Filer
// 類型工具類,處理Element的類型
private lateinit var mTypeTools: Types
private lateinit var mElementUtils: Elements
// gradle傳進(jìn)來(lái)的模塊名
private var mModuleName: String? = null
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
if (processingEnv == null) return
mMessage = processingEnv.messager
mFiler = processingEnv.filer
mElementUtils = processingEnv.elementUtils
mTypeTools = processingEnv.typeUtils
mModuleName = processingEnv.getOptions().get(ProcessorConfig.OPTIONS);
mMessage.printMessage(Diagnostic.Kind.NOTE, "processor 初始化完成.....${mModuleName}")
}
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
return true
}
}
在每個(gè)業(yè)務(wù)模塊中引入我們的注解處理器:
apply plugin: 'kotlin-kapt'
dependencies {
// ....
kapt project(":irouter_processor")
}
注解處理器上面的每個(gè)注解又有什么含義呢?
@AutoService
用于幫我們生成META-INF.services文件,有了這個(gè)文件才能識(shí)別出來(lái)注解處理器,那么這個(gè)文件在哪呢:
我們可以看到,這個(gè)文件名是Processor的全類名,文件內(nèi)容是注解處理器的全類名。
@SupportedAnnotationTypes
支持的注解,這個(gè)注解內(nèi)部傳入的是IRouter注解的全類名,表示我們要處理哪個(gè)注解。
@SupportedSourceVersion
支持的java版本,這里 傳入1.8即可
@SupportedOptions
gradle工程的配置,gradle中如果需要?jiǎng)討B(tài)的傳入某個(gè)變量,我們?cè)谶@里可以接收,比如我們需要傳入模塊名moduleName, 那么這個(gè)注解參數(shù)就傳入moduleName,然后在每個(gè)模塊里面?zhèn)魅雲(yún)?shù):
kapt {
arguments {
arg("moduleName", project.getName())
}
}
這樣可以動(dòng)態(tài)的將模塊名傳遞到注解處理器中,需要注意的是,每個(gè)模塊都會(huì)執(zhí)行一次注解處理器。
編譯 一下可以看到,打印了我們的模塊名,這個(gè)將會(huì)作為組名應(yīng)用到工程中。最后別忘了在Activity中 應(yīng)用我們的注解,否則process方法將不會(huì)執(zhí)行。
@IRouter("order/list")
class OrderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order)
}
}
@IRouter("order/detail")
class OrderDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order_detail)
}
}
這時(shí)我們可以在process方法中打印一下:
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
if (annotations.isNullOrEmpty() || roundEnv == null) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "沒(méi)有地方使用注解")
return false
}
// 獲取所有的被注解的節(jié)點(diǎn)
val elements = roundEnv.getElementsAnnotatedWith(IRouter::class.java)
elements.forEach {
mMessage.printMessage(Diagnostic.Kind.NOTE, "類名:${it}")
}
return true
}
可以看到以下打印,說(shuō)明我們已經(jīng)配置成功啦。
注: 類名:com.kangf.art.order.OrderActivity
注: 類名:com.kangf.art.order.OrderDetailActivity
生成path
下面開(kāi)始生成path,因?yàn)槊恳粋€(gè)模塊都生成一個(gè)pathMap,map中有多個(gè)path,所以我們定義一個(gè)map進(jìn)行分類:
private var mModuleName: String? = null
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
if (annotations.isNullOrEmpty() || roundEnv == null) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "沒(méi)有地方使用注解")
return false
}
// 獲取所有的被注解的節(jié)點(diǎn)
val elements = roundEnv.getElementsAnnotatedWith(IRouter::class.java)
// 獲取activity的類型,轉(zhuǎn)換成TypeMirror,用于判斷
val activityType = mElementUtils.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE).asType()
// 獲取fragment的類型,轉(zhuǎn)換成TypeMirror,用于判斷
val fragmentType = mElementUtils.getTypeElement(ProcessorConfig.FRAGMENT_PACKAGE).asType()
elements.forEach {
val className = it.simpleName.toString()
mMessage.printMessage(Diagnostic.Kind.NOTE, "類名:${className}")
// 獲取注解的path變量
val iRouter = it.getAnnotation(IRouter::class.java)
val path = iRouter.path
// 嚴(yán)謹(jǐn)性,進(jìn)行判空
if (path.isEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "${className}中path不能為空")
}
// 嚴(yán)謹(jǐn)性,進(jìn)行判空
if(mModuleName.isNullOrEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE,
"""
|請(qǐng)?jiān)趃radle中進(jìn)行配置
|kapt {
| arguments {
| arg("moduleName", project.getName())
| }
|}
""".trimMargin())
}
// 生成RouteBean
val routeBean = RouteBean().apply {
this.group = mModuleName
this.path = iRouter.path
this.element = it
}
when {
mTypeTools.isSubtype(it.asType(), activityType) -> {
// 如果被注解的類型是Activity
routeBean.typeEnum = RouteBean.TypeEnum.ACTIVITY
}
mTypeTools.isSubtype(it.asType(), fragmentType) -> {
// 如果被注解的類型是Fragment
routeBean.typeEnum = RouteBean.TypeEnum.FRAGMENT
}
else -> {
// 否則報(bào)錯(cuò)
mMessage.printMessage(
Diagnostic.Kind.ERROR,
"@IRouter注解目前僅限用于Activity和Fragment類之上"
)
}
}
// 在mPathMap集合中塞數(shù)據(jù)
val routeBeanList = mPathMap[routeBean.group]
if (routeBeanList.isNullOrEmpty()) {
val list = mutableListOf<RouteBean>()
list.add(routeBean)
mPathMap[routeBean.group!!] = list
} else {
routeBeanList.add(routeBean)
}
}
// 打印map
mMessage.printMessage(Diagnostic.Kind.NOTE, "$mPathMap")
return true
}
上面代碼很簡(jiǎn)單,就是為了在map中塞數(shù)據(jù),如果還有不明白的,我們看一下map的打印結(jié)果:
map component_goods--- > [com.kangf.router.annotation.bean.RouteBean@4039151c]
map component_order--- > [com.kangf.router.annotation.bean.RouteBean@6c3ea76c, com.kangf.router.annotation.bean.RouteBean@c2ce562]
可以看到,goods模塊中有一個(gè)注解,order模塊中有兩個(gè)注解。OK,接下來(lái)我們要根據(jù)map生成path文件,這個(gè)path應(yīng)該是什么樣子呢?我們?cè)侔焉厦娴拇a拿過(guò)來(lái)參考:
class OrderRouterPath : IRouterPath {
override fun getPath(): Map<String, RouteBean> {
val map = mutableMapOf<String, RouteBean>()
// 訂單列表
map["component_order/list"] = RouteBean().apply {
group = "component_order"
path = "component_order/list"
clazz = OrderActivity::class.java
typeEnum = RouteBean.TypeEnum.ACTIVITY
}
// 訂單詳情
map["component_order/detail"] = RouteBean().apply {
group = "component_order"
path = "component_order/detail"
clazz = OrderDetailActivity::class.java
typeEnum = RouteBean.TypeEnum.ACTIVITY
}
return map
}
}
好,那么我們就動(dòng)態(tài)的生成這類文件,根據(jù)kotlinpoet官網(wǎng)學(xué)到的,首先創(chuàng)建方法,再創(chuàng)建類,再把方法加入到類中,大致上就是這么一個(gè)流程:
private fun generatePathFile() {
// class OrderRouterPath : IRouterPath {
//
// override fun getPath(): Map<String, RouteBean> {
// val map = mutableMapOf<String, RouteBean>()
// // 訂單詳情
// map["component_order/detail"] = RouteBean().apply {
// group = "component_order"
// path = "component_order/detail"
// clazz = OrderDetailActivity::class.java
// typeEnum = RouteBean.TypeEnum.ACTIVITY
// }
//
// return map
// }
// }
// --------------------------- 方法創(chuàng)建開(kāi)始 --------------------------- //
// 獲取 某個(gè)模塊的List<RouteBean>
val routeList = mPathMap[mModuleName]
if (routeList.isNullOrEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "${mModuleName}中沒(méi)有地方使用注解")
return
}
// 方法返回類型,泛型為String,RouteBean
val returnType = Map::class.java.asClassName().parameterizedBy(
String::class.java.asTypeName().javaToKotlinType(),
RouteBean::class.asTypeName().javaToKotlinType()
).javaToKotlinType()
// 創(chuàng)建方法,方法名為 getPath
val funcSpecBuilder = FunSpec.builder(ProcessorConfig.PATH_METHOD_NAME)
// override關(guān)鍵字
.addModifiers(KModifier.OVERRIDE)
// 返回map
.returns(returnType)
.addStatement(
"val %N = mutableMapOf<%T, %T>()",
ProcessorConfig.PATH_VAR_MAP,
String::class.java.asTypeName().javaToKotlinType(),
RouteBean::class.java
)
// 添加語(yǔ)句
routeList.forEach {
funcSpecBuilder.addStatement(
"""
|%N[%S] = %T().apply {
| group = %S
| path = %S
| clazz = %T::class.java
| typeEnum = %T.%L
|}
|
""".trimMargin(),
ProcessorConfig.PATH_VAR_MAP,
it.path ?: "",
RouteBean::class.java,
it.group ?: "",
it.path ?: "",
it.element!!.asType().asTypeName(),
RouteBean.TypeEnum::class.java,
it.typeEnum!!
)
}
funcSpecBuilder.addStatement("return %N", ProcessorConfig.PATH_VAR_MAP)
// --------------------------- 方法創(chuàng)建完成 --------------------------- //
// --------------------------- 類創(chuàng)建開(kāi)始 --------------------------- //
val superInter = ClassName("com.kangf.router.api", "IRouterPath")
val fileName = "RouterPath_${mModuleName}"
val typeSpec = TypeSpec.classBuilder(fileName)
// 類中添加方法
.addFunction(funcSpecBuilder.build())
// 實(shí)現(xiàn)IRouterPath
.addSuperinterface(superInter)
.build()
// 創(chuàng)建文件
FileSpec.builder(mGeneratePackage, fileName)
.addType(typeSpec)
.build()
// 寫(xiě)入文件
.writeTo(mFiler)
// --------------------------- 類創(chuàng)建結(jié)束 --------------------------- //
mGrop
}
上面的代碼看著很多,其實(shí)很簡(jiǎn)單,每個(gè)方法看名字都能大概知道什么意思,每一行我都有注釋,有興趣的可以自己玩玩。最后生成出來(lái)的文件是這樣的:
package com.kangf.route.generate
import com.kangf.router.`annotation`.bean.RouteBean
import com.kangf.router.api.IRouterPath
import kotlin.String
import kotlin.collections.Map
public class RouterPath_component_goods : IRouterPath {
public override fun getPath(): Map<String, RouteBean> {
val pathMap = mutableMapOf<String, RouteBean>()
pathMap["goods/list"] = RouteBean().apply {
group = "component_goods"
path = "goods/list"
clazz = RouteBean::class.java
typeEnum = RouteBean.TypeEnum.ACTIVITY
}
return pathMap
}
}
生成group
這樣路徑對(duì)應(yīng)的封裝就完成了,下面還有g(shù)roup封裝,就相對(duì)簡(jiǎn)單多了,還是一樣的道理,先看看我們上面封裝的模板:
class OrderRouterGroup : IRouterGroup {
override fun getGroupMap(): MutableMap<String, Class<out IRouterPath>> {
val map = mutableMapOf<String, Class<out IRouterPath>>()
map["component_order"] = OrderRouterPath::class.java
return map
}
}
下面是生成GroupFile的方法:
private fun generateGroupFile() {
// class OrderRouterGroup : IRouterGroup {
//
// override fun getGroupMap(): MutableMap<String, Class<out IRouterPath>> {
// val map = mutableMapOf<String, Class<out IRouterPath>>()
// map["component_order"] = OrderRouterPath::class.java
// return map
//
// }
//
// }
// 方法返回類型,泛型為String,RouteBean
val routePathInter = ClassName("com.kangf.router.api", "IRouterPath")
val returnType = MutableMap::class.java.asClassName().parameterizedBy(
String::class.java.asTypeName().javaToKotlinType(),
Class::class.java.asClassName().parameterizedBy(
WildcardTypeName.producerOf(routePathInter)
)
).javaToKotlinType()
// path對(duì)應(yīng)的類名
val putClazz = ClassName(mGeneratePackage, "RouterPath_${mModuleName}")
val funSpec = FunSpec.builder(ProcessorConfig.GROUP_METHOD_NAME)
.returns(returnType)
.addModifiers(KModifier.OVERRIDE)
.addStatement(
"val %N = mutableMapOf<%T, %T>()",
ProcessorConfig.GROUP_VAR_MAP,
String::class.java.asTypeName().javaToKotlinType(),
Class::class.java.asClassName().parameterizedBy(
WildcardTypeName.producerOf(routePathInter)
)
)
.addStatement(
"%N[%S] = %T::class.java",
ProcessorConfig.GROUP_VAR_MAP,
mModuleName ?: "",
putClazz
)
.addStatement("return %N", ProcessorConfig.GROUP_VAR_MAP)
.build()
val superInter = ClassName("com.kangf.router.api", "IRouterGroup")
val fileName = "RouteGroup_${mModuleName}"
val typeSpec = TypeSpec.classBuilder(fileName)
.addSuperinterface(superInter)
.addFunction(funSpec)
.build()
FileSpec.builder(mGeneratePackage, fileName)
.addType(typeSpec)
.build()
.writeTo(mFiler)
}
最終生成的文件是這樣的:
package com.kangf.route.generate
import com.kangf.router.api.IRouterGroup
import com.kangf.router.api.IRouterPath
import java.lang.Class
import kotlin.String
import kotlin.collections.Map
public class RouteGroup_component_order : IRouterGroup {
public override fun getGroupMap(): Map<String, Class<out IRouterPath>> {
val groupMap = mutableMapOf<String, Class<out IRouterPath>>()
groupMap["component_order"] = RouterPath_component_order::class.java
return groupMap
}
}
這樣我們的注解處理器就算完成了~!接下來(lái)再次修改跳轉(zhuǎn)邏輯:
find<TextView>(R.id.tvGoods).setOnClickListener {
/**
* 經(jīng)過(guò)注解處理器封裝
*/
// 找到組map
val groupClazz = Class.forName("com.kangf.route.generate.RouteGroup_component_order")
val groupInstance = groupClazz.newInstance() as IRouterGroup
// 通過(guò)組找到路徑的map
val pathInstance = (groupInstance.getGroupMap()["component_order"] ?: error("")).newInstance() as IRouterPath
// 通過(guò)路徑的map找到組對(duì)應(yīng)的routeBean
val routeBean = pathInstance.getPath()["order/list"]
// 找到對(duì)應(yīng)的class進(jìn)行跳轉(zhuǎn)
val clazz = routeBean!!.clazz
startActivity(Intent(this, clazz))
/**
* 第1次封裝
*/
// // 找到組map
// val groupClazz = Class.forName("com.kangf.art.order.router.OrderRouterGroup")
// val groupInstance = groupClazz.newInstance() as IRouterGroup
// // 通過(guò)組找到路徑的map
// val pathInstance = groupInstance.getGroupMap()["component_order"]!!.newInstance() as IRouterPath
// // 通過(guò)路徑的map找到組對(duì)應(yīng)的routeBean
// val routeBean = pathInstance.getPath()["component_order/list"]
// // 找到對(duì)應(yīng)的class進(jìn)行跳轉(zhuǎn)
// val clazz = routeBean!!.clazz
// startActivity(Intent(this, clazz))
/**
* 使用全局map
*/
// val clazz = RecordPathManager.startActivity("order", "order/list")
// startActivity(Intent(this, clazz))
/**
* 類加載
*/
// val clazz = Class.forName("com.kangf.art.order.OrderActivity")
// startActivity(Intent(this, clazz))
}
封裝跳轉(zhuǎn)邏輯
效果這里就不演示了,跟上面是一樣的。當(dāng)然這還不夠,跳轉(zhuǎn)邏輯也是一堆重復(fù)性工作,我們何不再封裝一層呢?說(shuō)干就干:
package com.kangf.router.api
import android.content.Context
import android.content.Intent
/**
* Created by kangf on 2020/12/7.
*/
class IRouterUtils {
private var mPath = ""
companion object {
val instance by lazy { IRouterUtils() }
fun build(path: String): IRouterUtils {
val utils = instance
utils.mPath = path
return utils
}
}
fun navigation(context: Context) {
val finalGroup: String = mPath.split("/")[0] // finalGroup = order
// 找到組map
val groupClazz =
Class.forName("com.kangf.route.generate.RouteGroup_component_${finalGroup}")
val groupInstance = groupClazz.newInstance() as IRouterGroup
// 通過(guò)組找到路徑的map
val pathInstance = (groupInstance.getGroupMap()["component_${finalGroup}"]
?: error("")).newInstance() as IRouterPath
// 通過(guò)路徑的map找到組對(duì)應(yīng)的routeBean
val routeBean = pathInstance.getPath()[mPath]
// 找到對(duì)應(yīng)的class進(jìn)行跳轉(zhuǎn)
val clazz = routeBean!!.clazz
context.startActivity(Intent(context, clazz))
}
}
跳轉(zhuǎn)邏輯再次優(yōu)化,實(shí)現(xiàn)一行代碼即可跳轉(zhuǎn),我們添加個(gè)按鈕,改造一下:
find<TextView>(R.id.tvGoods).setOnClickListener {
// 跳轉(zhuǎn)到訂單列表
IRouterUtils.build("order/list").navigation(this)
}
find<TextView>(R.id.tvDetail).setOnClickListener {
// 跳轉(zhuǎn)到訂單詳情
IRouterUtils.build("order/detail").navigation(this)
}
看一下效果吧

總結(jié)
上面講的其實(shí)就是ARouter的組件化原理 ,當(dāng)然我們?cè)S多邏輯尚不完善,但研究ARouter源碼已經(jīng)足夠了,項(xiàng)目已經(jīng)上傳到GitHub,有興趣的過(guò)來(lái)看看吧!
這里還有一份java版本的,使用的是javapoet,邏輯相對(duì)完善一些: