Android組件化方案(二)-注解處理器(APT重磅干貨)

前情回顧

上一篇文章我們主要講的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))

           
        }

我們來看一下運行效果

image

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項目主頁

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)
}

看一下效果吧

image

總結

上面講的其實就是ARouter的組件化原理 诀拭,當然我們許多邏輯尚不完善,但研究ARouter源碼已經足夠了煤蚌,項目已經上傳到GitHub耕挨,有興趣的過來看看吧!

傳送門 kotlin版本

這里還有一份java版本的尉桩,使用的是javapoet筒占,邏輯相對完善一些:

傳送門 java版本

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜘犁,隨后出現(xiàn)的幾起案子翰苫,更是在濱河造成了極大的恐慌,老刑警劉巖这橙,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏窑,死亡現(xiàn)場離奇詭異,居然都是意外死亡屈扎,警方通過查閱死者的電腦和手機埃唯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹰晨,“玉大人墨叛,你說我怎么就攤上這事止毕。” “怎么了漠趁?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵扁凛,是天一觀的道長。 經常有香客問我棚潦,道長令漂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任丸边,我火速辦了婚禮叠必,結果婚禮上,老公的妹妹穿的比我還像新娘妹窖。我一直安慰自己纬朝,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布骄呼。 她就那樣靜靜地躺著共苛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜓萄。 梳的紋絲不亂的頭發(fā)上隅茎,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音嫉沽,去河邊找鬼辟犀。 笑死,一個胖子當著我的面吹牛绸硕,可吹牛的內容都是我干的堂竟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玻佩,長吁一口氣:“原來是場噩夢啊……” “哼出嘹!你這毒婦竟也來了?” 一聲冷哼從身側響起咬崔,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤税稼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垮斯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郎仆,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年甚脉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铆农。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡牺氨,死狀恐怖狡耻,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情猴凹,我是刑警寧澤夷狰,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站郊霎,受9級特大地震影響沼头,放射性物質發(fā)生泄漏。R本人自食惡果不足惜书劝,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一进倍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧购对,春花似錦猾昆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至解幽,卻和暖如春贴见,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躲株。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工片部, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徘溢。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓吞琐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親然爆。 傳聞我的和親對象是個殘疾皇子站粟,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容