前情回顧
上一篇文章我們主要講的gradle的統(tǒng)一管理强品,組件之間的通信方案澜建,以及如何使用全局map進行頁面跳轉嫉你。每個頁面都需要在application中進行注冊九孩,這樣肯定是不行的,根據(jù)面向對象的思想阱扬, 我們先對其進行一層封裝泣懊,做到在編譯器自動進行注冊。
封裝全局Map
首先我們將通信方案作為一個組件麻惶,創(chuàng)建一個irouter_api的module馍刮,由base去依賴它,然后把RouteBean實體類挪進來窃蹋,為了方便以后擴展卡啰,我們在RouteBean中增加幾個屬性
class RouteBean {
/**
* 為了方便擴展静稻,這里做一個標識
*/
enum class TypeEnum {
ACTIVITY,
FRAGMENT
}
// 組名: order | goods ...
var group: String? = null
// 路徑: order/order_list
var path: String? = null
// 類: OrderActivity.class
var clazz: Class<*>? = null
// 標識是Activity, Fragment匈辱,或是其他
var typeEnum: TypeEnum? = null
}
組名振湾,路徑和類不變, 增加了一個enum的標識亡脸,用來標記是個Fragment押搪,還是一個Activity,這時候項目結構應該是這樣的
不妨再考慮一下浅碾,只使用一個全局map的話大州,隨著項目越來越大,activity越來越多垂谢,查找肯定會帶來一定的效率問題摧茴,如果我們一個模塊使用一個map呢?這樣可以在很大程度上解決這種性能影響埂陆。怎么實現(xiàn)呢?我們先畫畫圖娃豹,理清思路
首先是一個全局的IRouterGroup
, 里面有一個全局map焚虱,key就是module的group, value用于存放每個模塊的路徑懂版,我們把value進一步封裝成IRouterPath
鹃栽,IRouterPath
通過組名存放組下面的路徑。思路都理清了躯畴,下面進入擼碼階段民鼓。
基于面向接口編程,我們定義一個path接口蓬抄,用于存放某一個模塊中的所有的路徑:
interface IRouterPath {
/**
* Map :
* key -》》》》 gourp
* value -》》》》 path丰嘉, class, 等信息
*/
fun getPath(): Map<String, RouteBean>
}
再定義一個group接口嚷缭,用于存放某個group的所有的path饮亏,因為class是要實現(xiàn)IRouterPath
接口的,所以使用out限定符:
interface IRouterGroup {
/**
* Map
* key ->>>>> group
*
* value ->>>>> path集合
*/
fun getGroupMap(map: Map<String, Class<out IRouterPath>>)
}
下面我們基于order模塊進行實現(xiàn)阅爽,比如我們訂單模塊有一個訂單列表和一個訂單詳情路幸,這時候path的實現(xiàn)應該是這樣的:
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的實現(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
}
}
這樣處理之后,就可以從其他組件跳轉了付翁,比如從goods模塊跳轉到訂單列表简肴,之前的邏輯就應該改成這樣:
find<TextView>(R.id.tvGoods).setOnClickListener {
// 找到組map
val groupClazz = Class.forName("com.kangf.art.order.router.OrderRouterGroup")
val groupInstance = groupClazz.newInstance() as IRouterGroup
// 通過組找到路徑的map
val pathInstance = groupInstance.getGroupMap()["component_order"]!!.newInstance() as IRouterPath
// 通過路徑的map找到組對應的routeBean
val routeBean = pathInstance.getPath()["component_order/list"]
// 找到對應的class進行跳轉
val clazz = routeBean!!.clazz
startActivity(Intent(this, clazz))
}
我們來看一下運行效果
WTF? 我只要一個跳轉頁面百侧,你給我搞這么一堆邏輯砰识?還好意思叫封裝能扒?各位請收好手中的菜葉子臭雞蛋,我們下一步才是真正的開始仍翰,好戲還在后頭呢赫粥!
注解處理器
上面的做法顯然 是不行的,在頁面增多的情況下予借,我們將會做更多的重復性的工作越平,那何不將這些工作完全交給編譯器來解決呢?這時候就用到了APT技術灵迫。 APT可以在編譯時檢查所有的注解秦叛,而我們正好可以借助它生成代碼,來替我們完成這些重復的工作瀑粥。
APT是什么挣跋?
APT(Annotation Processing Tool) 是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation狞换,根據(jù)注解自動生成代碼避咆,如果想要自定義的注解處理器能夠正常運行,必須要通過APT工具來進行處理修噪。 也可以樣理解查库,只有通過聲明APT工具后,程序在編譯期間自定義注解解釋器才能執(zhí)行黄琼。 通俗理解:根據(jù)規(guī)則樊销,幫我們生成代碼、生成類文件脏款。
注解
有注解處理器围苫,就不得不提到注解,對注解不了解的撤师,可以先看一下這篇文章:
IOC依賴注入(一)— 手寫B(tài)utterKnife框架
講的是運行時注解剂府,而我們現(xiàn)在用到的,是編譯時注解丈氓。
注解處理器的API
首先我們創(chuàng)建一個java/kotlin的module作為注解處理器周循,新建一個類繼承javax.annotation.processing.AbstractProcessor
class IRouterProcessor : AbstractProcessor() {
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
return true
}
}
我們需要了解注解處理器用到的API,這對大部分人來說可能是陌生的万俗,不過這很簡單湾笛,大家用過一次之后都會對其有所了解。
Element
可以看到process方法中有一個MutableSet
闰歪,這個就是所有注解的元素的集合嚎研,它的泛型是TypeElement
的下限類型。
對于java源文件來說,它其實是一種結構體語言临扮,這些結構組成就是一個個的Element組成的论矾,在注解處理器中,Element是一個非常重要的元素杆勇。而我們注解的每一個元素贪壳,其實就是被包裝成了一個個的Element放進了MutableSet
集合中。Element有以下幾個實現(xiàn)類蚜退,代表了不同的元素:
PackageElement 表示一個包程序元素闰靴。提供對有關包及其成員的信息的訪問
ExecutableElement 表示某個類或接口的方法、構造方法或初始化程序(靜態(tài)或實例)
TypeElement 表示一個類或接口程序元素钻注。提供對有關類型及其成員的信息的訪問
VariableElement 表示一個字段蚂且、enum 常量、方法或構造方法參數(shù)幅恋、局部變量或異常參數(shù)
Element節(jié)點中的API
getEnclosedElements() 返回該元素直接包含的子元素
getEnclosingElement() 返回包含該element的父element杏死,與上一個方法相反
getKind() 返回element的類型,判斷是哪種element
getModifiers() 獲取修飾關鍵字,入public static final等關鍵字
getSimpleName() 獲取名字捆交,不帶包名
getQualifiedName() 獲取全名淑翼,如果是類的話,包含完整的包名路徑
getParameters() 獲取方法的參數(shù)元素品追,每個元素是一個VariableElement
getReturnType() 獲取方法元素的返回值
getConstantValue() 如果屬性變量被final修飾窒舟,則可以使用該方法獲取它的值
Element中有以上幾個方法,我們一會將會頻繁的用到诵盼。
kotlinpoet
javapoet是square推出的開源java代碼生成框架,提供Java Api生成.java源文件 這個框架功能非常實用银还,也是我們習慣的Java面向對象OOP語法 可以很方便的使用它根據(jù)注解生成對應代碼通過這種自動化生成代碼的方式风宁, 可以讓我們用更加簡潔優(yōu)雅的方式要替代繁瑣冗雜的重復工作。kotlinpoet顧名思義蛹疯,是針對kotlin的一套框架戒财,我們今天要用到的就是kotlinpoet,它可以幫助我們生成kotlin文件捺弦。
kotlinpoet API
在kotlinpoet中饮寞,每一個節(jié)點都對應一個Spec
類對象 說明
MethodSpec 代表一個構造函數(shù)或方法聲明
TypeSpec 代表一個類,接口列吼,或者枚舉聲明
FieldSpec 代表一個成員變量幽崩,一個字段聲明
JavaFile 包含一個頂級類的Java文件
ParameterSpec 用來創(chuàng)建參數(shù)
AnnotationSpec 用來創(chuàng)建注解
ClassName 用來包裝一個類
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()
}
那么在注解處理器中理郑,就要這樣實現(xiàn):
// 創(chuàng)建一個類類型
val greeterClass = ClassName("", "Greeter")
// 創(chuàng)建名為HelloWorld的文件
val file = FileSpec.builder("", "HelloWorld")
// 文件中添加一個Greeter類
.addType(TypeSpec.classBuilder("Greeter")
// 類中的構造方法中蹄溉,增加一個name屬性
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build())
// 類中增加一個方法
.addFunction(FunSpec.builder("greet")
// 方法中的語句咨油,%P通配符代表了字符串模板
.addStatement("println(%P)", "Hello, \$name")
.build())
.build())
// 在HelloWorld的文件中增加一個main方法
.addFunction(FunSpec.builder("main")
// main方法中增加一個args的可變參數(shù)
.addParameter("args", String::class, VARARG)
// main方法中調用Greeter類中的greet方法
.addStatement("%T(args[0]).greet()", greeterClass)
.build())
.build()
// 將文件寫入輸出流。
file.writeTo(System.out)
關于kotlin的詳細使用柒爵,可以看它的官方文檔役电。
擼碼
好了,以上就是關鍵的API了棉胀,下面正式進入擼碼階段:
首先創(chuàng)建注解module法瑟,用于存放注解,同時我們需要將RouteBean對象移動到注解module中膏蚓,因為注解處理器和router_api模塊需要這個實體類瓢谢,同時再次對RouteBean進行擴展:
class RouteBean {
/**
* 為了方便擴展,這里做一個標識
*/
enum class TypeEnum {
ACTIVITY,
FRAGMENT
}
// 組名: order | goods ...
var group: String? = null
// 路徑: order/order_list
var path: String? = null
// 類: OrderActivity.class
var clazz: Class<*>? = null
// 標識是Activity驮瞧, Fragment氓扛,或是其他
var typeEnum: TypeEnum? = null
// 類節(jié)點信息
var element: Element? = null
}
我們增加一個類節(jié)點信息,方便以后使用论笔。 下面定義一個編譯時注解采郎,將作用在Activity或Fragment中:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class IRouter(val path: String)
這個注解需要出入path。萬事俱備狂魔,接下來就輪到注解處理器了蒜埋。
首先在注解處理器模塊的gradle中引入AutoService,用于幫我們生成MATE-INF.services下的文件最楷,需要這個文件系統(tǒng)才能幫我們識別是一個注解處理器
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
// 文件操作類籽孙,我們將通過此類生成kotlin文件
private lateinit var mFiler: Filer
// 類型工具類烈评,處理Element的類型
private lateinit var mTypeTools: Types
private lateinit var mElementUtils: Elements
// gradle傳進來的模塊名
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
}
}
在每個業(yè)務模塊中引入我們的注解處理器:
apply plugin: 'kotlin-kapt'
dependencies {
// ....
kapt project(":irouter_processor")
}
注解處理器上面的每個注解又有什么含義呢?
@AutoService
用于幫我們生成META-INF.services文件犯建,有了這個文件才能識別出來注解處理器讲冠,那么這個文件在哪呢:
我們可以看到,這個文件名是Processor的全類名适瓦,文件內容是注解處理器的全類名竿开。
@SupportedAnnotationTypes
支持的注解,這個注解內部傳入的是IRouter注解的全類名玻熙,表示我們要處理哪個注解否彩。
@SupportedSourceVersion
支持的java版本,這里 傳入1.8即可
@SupportedOptions
gradle工程的配置嗦随,gradle中如果需要動態(tài)的傳入某個變量胳搞,我們在這里可以接收,比如我們需要傳入模塊名moduleName, 那么這個注解參數(shù)就傳入moduleName
肌毅,然后在每個模塊里面?zhèn)魅雲(yún)?shù):
kapt {
arguments {
arg("moduleName", project.getName())
}
}
這樣可以動態(tài)的將模塊名傳遞到注解處理器中筷转,需要注意的是,每個模塊都會執(zhí)行一次注解處理器悬而。
編譯 一下可以看到呜舒,打印了我們的模塊名,這個將會作為組名應用到工程中笨奠。最后別忘了在Activity中 應用我們的注解袭蝗,否則process方法將不會執(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)
}
}
這時我們可以在process方法中打印一下:
override fun process(
annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {
if (annotations.isNullOrEmpty() || roundEnv == null) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "沒有地方使用注解")
return false
}
// 獲取所有的被注解的節(jié)點
val elements = roundEnv.getElementsAnnotatedWith(IRouter::class.java)
elements.forEach {
mMessage.printMessage(Diagnostic.Kind.NOTE, "類名:${it}")
}
return true
}
可以看到以下打印般婆,說明我們已經配置成功啦到腥。
注: 類名:com.kangf.art.order.OrderActivity
注: 類名:com.kangf.art.order.OrderDetailActivity
生成path
下面開始生成path,因為每一個模塊都生成一個pathMap蔚袍,map中有多個path乡范,所以我們定義一個map進行分類:
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, "沒有地方使用注解")
return false
}
// 獲取所有的被注解的節(jié)點
val elements = roundEnv.getElementsAnnotatedWith(IRouter::class.java)
// 獲取activity的類型,轉換成TypeMirror啤咽,用于判斷
val activityType = mElementUtils.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE).asType()
// 獲取fragment的類型晋辆,轉換成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
// 嚴謹性宇整,進行判空
if (path.isEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "${className}中path不能為空")
}
// 嚴謹性瓶佳,進行判空
if(mModuleName.isNullOrEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE,
"""
|請在gradle中進行配置
|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 -> {
// 否則報錯
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
}
上面代碼很簡單,就是為了在map中塞數(shù)據(jù)鳞青,如果還有不明白的霸饲,我們看一下map的打印結果:
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模塊中有一個注解臂拓,order模塊中有兩個注解贴彼。OK,接下來我們要根據(jù)map生成path文件埃儿,這個path應該是什么樣子呢?我們再把上面的代碼拿過來參考:
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
}
}
好融涣,那么我們就動態(tài)的生成這類文件童番,根據(jù)kotlinpoet官網(wǎng)學到的,首先創(chuàng)建方法威鹿,再創(chuàng)建類剃斧,再把方法加入到類中,大致上就是這么一個流程:
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)建開始 --------------------------- //
// 獲取 某個模塊的List<RouteBean>
val routeList = mPathMap[mModuleName]
if (routeList.isNullOrEmpty()) {
mMessage.printMessage(Diagnostic.Kind.NOTE, "${mModuleName}中沒有地方使用注解")
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關鍵字
.addModifiers(KModifier.OVERRIDE)
// 返回map
.returns(returnType)
.addStatement(
"val %N = mutableMapOf<%T, %T>()",
ProcessorConfig.PATH_VAR_MAP,
String::class.java.asTypeName().javaToKotlinType(),
RouteBean::class.java
)
// 添加語句
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)建開始 --------------------------- //
val superInter = ClassName("com.kangf.router.api", "IRouterPath")
val fileName = "RouterPath_${mModuleName}"
val typeSpec = TypeSpec.classBuilder(fileName)
// 類中添加方法
.addFunction(funcSpecBuilder.build())
// 實現(xiàn)IRouterPath
.addSuperinterface(superInter)
.build()
// 創(chuàng)建文件
FileSpec.builder(mGeneratePackage, fileName)
.addType(typeSpec)
.build()
// 寫入文件
.writeTo(mFiler)
// --------------------------- 類創(chuàng)建結束 --------------------------- //
mGrop
}
上面的代碼看著很多,其實很簡單根蟹,每個方法看名字都能大概知道什么意思脓杉,每一行我都有注釋,有興趣的可以自己玩玩简逮。最后生成出來的文件是這樣的:
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
這樣路徑對應的封裝就完成了球散,下面還有group封裝,就相對簡單多了散庶,還是一樣的道理蕉堰,先看看我們上面封裝的模板:
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對應的類名
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
}
}
這樣我們的注解處理器就算完成了~屋讶!接下來再次修改跳轉邏輯:
find<TextView>(R.id.tvGoods).setOnClickListener {
/**
* 經過注解處理器封裝
*/
// 找到組map
val groupClazz = Class.forName("com.kangf.route.generate.RouteGroup_component_order")
val groupInstance = groupClazz.newInstance() as IRouterGroup
// 通過組找到路徑的map
val pathInstance = (groupInstance.getGroupMap()["component_order"] ?: error("")).newInstance() as IRouterPath
// 通過路徑的map找到組對應的routeBean
val routeBean = pathInstance.getPath()["order/list"]
// 找到對應的class進行跳轉
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
// // 通過組找到路徑的map
// val pathInstance = groupInstance.getGroupMap()["component_order"]!!.newInstance() as IRouterPath
// // 通過路徑的map找到組對應的routeBean
// val routeBean = pathInstance.getPath()["component_order/list"]
// // 找到對應的class進行跳轉
// 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))
}
封裝跳轉邏輯
效果這里就不演示了,跟上面是一樣的须教。當然這還不夠皿渗,跳轉邏輯也是一堆重復性工作,我們何不再封裝一層呢没卸?說干就干:
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
// 通過組找到路徑的map
val pathInstance = (groupInstance.getGroupMap()["component_${finalGroup}"]
?: error("")).newInstance() as IRouterPath
// 通過路徑的map找到組對應的routeBean
val routeBean = pathInstance.getPath()[mPath]
// 找到對應的class進行跳轉
val clazz = routeBean!!.clazz
context.startActivity(Intent(context, clazz))
}
}
跳轉邏輯再次優(yōu)化羹奉,實現(xiàn)一行代碼即可跳轉,我們添加個按鈕约计,改造一下:
find<TextView>(R.id.tvGoods).setOnClickListener {
// 跳轉到訂單列表
IRouterUtils.build("order/list").navigation(this)
}
find<TextView>(R.id.tvDetail).setOnClickListener {
// 跳轉到訂單詳情
IRouterUtils.build("order/detail").navigation(this)
}
看一下效果吧
總結
上面講的其實就是ARouter的組件化原理 诀拭,當然我們許多邏輯尚不完善,但研究ARouter源碼已經足夠了煤蚌,項目已經上傳到GitHub耕挨,有興趣的過來看看吧!
這里還有一份java版本的尉桩,使用的是javapoet筒占,邏輯相對完善一些: