Android--利用APT+kotlinpoet實現(xiàn)組件化開發(fā)Router機制

上一篇我們使用了一個全局Map緩存來所有的Activity類,顯然這是非常麻煩的,一旦有所改動,就要手動修改該Map

為此,我們希望將key和Activity類的映射關(guān)系,通過一定方式自動導(dǎo)入Map。利用注解解析器(APT)和代碼生成器(kotlinpoet)可以根據(jù)注解在編譯期間就生成相應(yīng)的代碼,業(yè)界稱之為Router機制

一、Gradle配置及架構(gòu)分層

在實現(xiàn)Router機制之前,我們還可以對項目的組織架構(gòu)進行優(yōu)化,將gradle中公用部分抽出來
有了上一篇的基礎(chǔ),我們初步實現(xiàn)了架構(gòu)分層,目前有三個module:


其依賴關(guān)系為:app << libmodule_a << libase,但是每個module的gradle中都有重復(fù)的內(nèi)容,如版本號、版本名、SDK版本、重復(fù)依賴等,我們可以利用groovy和gradle的知識,為它們設(shè)計成共用屬性

1.創(chuàng)建config.gradle

在工程下新建一個config.gradle文件



將重復(fù)的內(nèi)容設(shè)置成全局屬性:

ext {
    isDebug = false

    kotlinVersion = "1.5.31"

    // 版本信息
    androidVersion = [
            compileSdk : 31,
            minSdk     : 21,
            targetSdk  : 31,
            versionCode: 1,
            versionName: '1.0'
    ]

    applicationId = [
            app     : "com.aruba.arouterapplication",
            module_a: "com.aruba.libmodule_a"
    ]

    androidxCore = 'androidx.core:core-ktx:1.3.2'
    androidAppCompat = 'androidx.appcompat:appcompat:1.2.0'
    androidMaterial = 'com.google.android.material:material:1.3.0'
    androidConstraintLayout = 'androidx.constraintlayout:constraintlayout:2.0.4'
}
2.在主工程Gradle中,引入config.gradle
apply from: 'config.gradle'

buildscript {
    repositories {
...
3.改造module的gradle

libbase的gradle改造為:

plugins {
    id 'com.android.library'
    id 'kotlin-android'
}

//使用一個變量,減少代碼量
def config = rootProject.ext

android {
    compileSdk config.androidVersion.compileSdk

    defaultConfig {
        minSdk config.androidVersion.minSdk
        targetSdk config.androidVersion.targetSdk
        versionCode config.androidVersion.versionCode
        versionName config.androidVersion.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    api androidxCore
    api androidAppCompat
    api androidMaterial
    api androidConstraintLayout
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

libmoudle_a的gradle改造為:

def config = rootProject.ext

if (!config.isDebug) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
apply plugin: 'kotlin-android'

android {
    compileSdk config.androidVersion.compileSdk

    defaultConfig {
        if (config.isDebug) {
            applicationId config.applicationId.module_a
        }
        minSdk config.androidVersion.minSdk
        targetSdk config.androidVersion.targetSdk
        versionCode config.androidVersion.versionCode
        versionName config.androidVersion.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    //根據(jù)變量配置不同manifest
    sourceSets {
        main {
            if (!config.isDebug) {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    api project(path: ':libbase')
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

app的gradle改造為:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

def config = rootProject.ext

android {
    compileSdk config.androidVersion.compileSdk

    defaultConfig {
        applicationId config.applicationId.app
        minSdk config.androidVersion.minSdk
        targetSdk config.androidVersion.targetSdk
        versionCode config.androidVersion.versionCode
        versionName config.androidVersion.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation project(path: ':libmodule_a')
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
4.歸納module路徑

項目后續(xù)可能會有很多個module,如果你想單獨使用文件夾進行分類,比如基礎(chǔ)的組件放入基礎(chǔ)的文件夾下,可以在settings.gradle中為module重新設(shè)置新路徑

我將libbase放入了新建文件夾lib_comm下:



修改settings.gradle配置:

include ':libbase'

project(':libbase').projectDir = new File('lib_comm/libbase')

其他的module也可以用該方法歸類

二、定義注解

要用到APT,那么肯定要自定義注解,來指定APT解析的注解

1.新建一個AnnotationModule

該module會被業(yè)務(wù)module和插件moudle依賴


2.定義Router注解

在需要跳轉(zhuǎn)的Activity上使用該注解,使用group和path來區(qū)分需要跳轉(zhuǎn)的目標(biāo)

/**
 * 表示一個跳轉(zhuǎn)目標(biāo)(Activity、fragment)需要加入路由表
 * Created by aruba on 2021/11/22.
 */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Router(
    val group: String,//表示組,不可為空
    val path: String//表示目標(biāo),不可重復(fù)
)
3.定義Router包裝類

包裝類用來最后跳轉(zhuǎn)使用,里面主要是存放著被注解的Activity類

/**
 * 路由包裝類
 * Created by aruba on 2021/11/22.
 */
data class RouterMeta(
    val type: Type = Type.ACTIVITY,//類型
    val path: String,//路由注解的path
    val group: String,//路由注解的group
    val clazz: Class<*>? = null//類對象
) {
    var element: Element? = null//節(jié)點

    enum class Type {
        ACTIVITY,
        ISERVICE
    }
}

三、Router組件

上面我們的注解類中定義了group和path,還有RouterMeta包裝類,最后生成的Map的對應(yīng)關(guān)系如下圖:


上面的是一個group對應(yīng)一個IPathRouter生成類,IPathRouter是一個接口,我們會在Router組件中定義出來,下面是一個path對應(yīng)一個RouterMeta包裝類
這兩個Map,用來替換我們上一篇中,自己要手動導(dǎo)入Activity關(guān)系的Map,看到這你也許會懵,沒關(guān)系往下看接口的定義

1.新建Router Module

Route組件是實現(xiàn)功能的中間件,業(yè)務(wù)組件通過調(diào)用該組件方法來進行跳轉(zhuǎn)

依賴以下包:

dependencies {
    implementation androidAppCompat
    implementation kotlinCoroutine
    api project(path: ':librouter_annotation')
}
2.定義接口
2.1 path-RouterMeta映射關(guān)系

首先是需要往一個map里存入path-RouterMeta映射關(guān)系
IRouterPath:

/**
 * 根據(jù)path集合將RouterMeta存入緩存map
 * Created by aruba on 2021/11/23.
 */
interface IRouterPath {
    public fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>)
}

為了方便理解,寫一個測試類來實現(xiàn)該接口,我們最后通過kotlinpoet生成的類也是參考該實現(xiàn)類:

class RouterPathTest : IRouterPath {
    override fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>) {
        map["path1"] = RouterMeta(
            type = RouterMeta.Type.ACTIVITY,
            path = "path1",
            group = "group1",
            clazz = TestActivity::class.java
        )
        map["path2"] = RouterMeta(
            type = RouterMeta.Type.ACTIVITY,
            path = "path2",
            group = "group1",
            clazz = Test2Activity::class.java
        )
       ...
    }
}

就是在上一篇中,我們手動將映射關(guān)系存入Map的操作

2.2 group-IPathRouter映射關(guān)系

為了方便管理和擴展,我們引入了group的概念,group類似IRouterPath實現(xiàn)類的代理,一個group對應(yīng)一個IRouterPath實現(xiàn)類,最終我們通過group將IRouterPath實現(xiàn)類存入Map
IRouterGroup:

/**
 * 根據(jù)group將IRouterPath存入緩存map
 * Created by aruba on 2021/11/23.
 */
interface IRouterGroup {
    public fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>)
}

同樣的寫一個測試類,IRouterGroup 的實現(xiàn)就簡單了,只需要一對一的關(guān)系:

class RouterGroupTest : IRouterGroup {
    override fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>) {
        map["group1"] = RouterPathTest::class.java
    }
}
3.定義全局緩存

第二步我們需要往兩個Map中存入映射關(guān)系,來獲取跳轉(zhuǎn)時對應(yīng)的類,現(xiàn)在把它們定義出來

/**
 * 映射關(guān)系緩存
 * Created by aruba on 2021/11/23.
 */
object CacheMap {
    // 根據(jù)group可以獲取到RouterPath
    val RouterPathByGroup: LinkedHashMap<String, Class<out IRouterPath>> = LinkedHashMap()

    // 根據(jù)path可以獲取到RouterMeta
    val RouterMetaByPath: LinkedHashMap<String, RouterMeta> = LinkedHashMap()
}

四、APT+kotlinpoet自動生成類

有了上面的接口和全局緩存,我們就需要自動生成兩個實現(xiàn)類了

1.新建插件Module
2.配置Gradle

需要支持APT和kotlinpoet

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:$kotlinVersion"
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
    implementation project(path: ':librouter_annotation')
    // apt依賴
    compileOnly "com.google.auto.service:auto-service:1.0-rc7"
    kapt "com.google.auto.service:auto-service:1.0-rc7"
    // kotlinpoet
    implementation group: 'com.squareup', name: 'kotlinpoet', version: '1.10.2'
}
3.定義一些全局變量

APT解析節(jié)點和kotlinpoet代碼生成時需要用到:類的包名和類名、方法名、生成的文件名(也是類名)、生成的類的包路徑等

object Const {
    const val ACTIVITY = "android.app.Activity"
    const val ISERVICE = "android.app.Fragment"

    // 生成的類實現(xiàn)的接口
    const val IROUTER_GROUP = "com.aruba.librouter_router.`interface`.IRouterGroup"
    const val IROUTER_PATH = "com.aruba.librouter_router.`interface`.IRouterPath"

    // 接口的package
    const val IROUTER_PACKAGE = "com.aruba.librouter_router.`interface`"

    // 接口的simplename
    const val IROUTER_GROUP_SIMPLENAME = "IRouterGroup"
    const val IROUTER_PATH_SIMPLENAME = "IRouterPath"

    //接口需要實現(xiàn)的方法
    const val METHOD_IROUTER_PATH = "cacheInRouterMetaByPath"
    const val METHOD_IROUTER_GROUP = "cacheInRouterPathByGroup"

    //生成類名的string format
    const val FILENAME_FORMAT_PATH = "%s_%s_path"
    const val FILENAME_FORMAT_GROUP = "%s_%s_group"
    
    // 生成的類文件前綴
    const val FILENAME_PREFIX = "Router"
    // 生成的類的package
    const val GENERATE_PACKAGE = "com.aruba.router"
}
4.使用注解解釋器及kotlinpoet

每個使用了插件的業(yè)務(wù)module都會執(zhí)行一次注解解釋器的方法,我們對注解的處理主要分為兩步:

  • 使用APT獲取Router注解的類,并進行包裝,最后存入一個group-RouterMeta列表的Map中
  • group-RouterMeta列表的Map進行處理,首先遍歷RouterMeta列表,使用kotlinpoet生成IRouterPath的實現(xiàn)類,再根據(jù)group和IRouterPath實現(xiàn)類的文件名(類名),生成實現(xiàn)IRouterGroup的類
/**
 * 注解解析器,每個模塊都會執(zhí)行一次該類中的方法
 * Created by aruba on 2021/11/22.
 */
@AutoService(Processor::class)
//指定要處理的注解
@SupportedAnnotationTypes("com.aruba.librouter_annotation.Router")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class AnnotationProcessor : AbstractProcessor() {
    // 日志打印
    private lateinit var MSG: Messager
    private fun Messager.print(msg: String) {
        printMessage(Diagnostic.Kind.NOTE, msg);
    }

    // 節(jié)點工具類 (類、函數(shù)、屬性都是節(jié)點) 用來獲取節(jié)點
    private lateinit var mElementUtils: Elements

    // type(類信息)工具類   用來比對節(jié)點
    private lateinit var mTypeUtils: Types

    // 文件操作類  用來生成kotlin文件
    private lateinit var mFiler: Filer

    // Activity類的節(jié)點描述
    private val typeActivity by lazy { mElementUtils.getTypeElement(Const.ACTIVITY).asType() }

    // IService接口的節(jié)點描述
    private val typeIService by lazy { mElementUtils.getTypeElement(Const.ISERVICE).asType() }

    // 組名:RouterMeta列表的Map
    private val routerMetaByGroup: MutableMap<String, MutableList<RouterMeta>>
            by lazy { mutableMapOf() }

    // 組名:文件名列表的Map
    private val fileNameByGroup: MutableMap<String, String>
            by lazy { mutableMapOf() }

    private var isInit: Boolean = false

    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        //初始化
        processingEnv?.apply {
            MSG = messager
            MSG.print("init")
            mElementUtils = elementUtils
            mTypeUtils = typeUtils
            mFiler = filer
        }
    }

    /**
     * 開始執(zhí)行
     */
    override fun process(
        typeElementSet: MutableSet<out TypeElement>?,
        roundEnvironment: RoundEnvironment?
    ): Boolean {
        if (isInit) return true
        isInit = true

        typeElementSet?.let {
            // 1.獲取所有使用了Router注解的節(jié)點并遍歷
            roundEnvironment?.getElementsAnnotatedWith(Router::class.java)?.forEach { element ->
                // 2.獲取注解
                val router = element.getAnnotation(Router::class.java).apply {
                    if (path.isEmpty() || group.isEmpty()) {
                        MSG.print("$element group or path can not be empty")
                        throw RuntimeException()
                    }
                }
                // 3.獲取包裝類
                getRouterMeta(element, router, mTypeUtils) {
                    MSG.print(it.toString())
                }.apply {
                    MSG.print("group:${group} path:${path}")
                    // 4.加入緩存,即根據(jù)group分組
                    routerMetaByGroup.getOrPut(group) {
                        mutableListOf()
                    }.add(this)
                }
            }

            MSG.print(routerMetaByGroup.toString())
            // 5.利用kotlinpoet生成代碼
            generateClassByKotlinPoet()
        }
        return true
    }

    /**
     * 獲取節(jié)點包裝類方法
     * 我們定義Router注解只能使用在Activity類和實現(xiàn)了IService接口的類
     */
    private fun getRouterMeta(
        element: Element,
        router: Router,
        typeUtils: Types,
        mirror: (mirror: TypeMirror) -> Unit = {}
    ): RouterMeta {
        // 獲取當(dāng)前節(jié)點的描述
        val type = element.asType().apply {
            mirror(this)
        }

        // 根據(jù)描述是否是typeActivity或者typeIService生成包裝類
        return when {
            typeUtils.isSubtype(type, typeActivity) -> {//是Activity子類
                RouterMeta(path = router.path, group = router.group).apply {
                    this.element = element
                }
            }
            typeUtils.isSubtype(type, typeIService) -> {//實現(xiàn)了IService接口
                RouterMeta(
                    path = router.path, group = router.group,
                    type = RouterMeta.Type.ISERVICE
                ).apply {
                    this.element = element
                }
            }
            else -> {// 其他情況使用了Router注解,直接拋出異常
                MSG.print("$element type:$type not support router")
                throw RuntimeException()
            }
        }
    }

    /**
     * 利用kotlinpoet生成代碼
     */
    private fun generateClassByKotlinPoet() {
        routerMetaByGroup.forEach { (group, routerMetas) ->
            // 分別生成兩個類并實現(xiàn)上面兩個接口,以便在Router中獲取
            // 1.先來生成 根據(jù)path集合將RouterMeta包裝類存入一個map  的類
            generateRouterPathByKotlinPoet(group, routerMetas)
        }

        fileNameByGroup.forEach { (group, clzName) ->
            // 2.再生成 根據(jù)group將1.生成的類存入一個map  的類
            generateRouterGroupByKotlinPoet(group, clzName)
        }
    }

    /**
     * 生成RouterPath類
     */
    private fun generateRouterPathByKotlinPoet(
        group: String,
        routerMetas: MutableList<RouterMeta>
    ) {
        // 可以自己先實現(xiàn)一個,再參考著寫
//        class RouterPathTest : IRouterPath {
//            override fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>) {
//                map["path1"] = RouterMeta(
//                    type = RouterMeta.Type.ACTIVITY,
//                    path = "path1",
//                    group = "group1",
//                    clazz = TestActivity::class.java
//                )
//                map["path2"] = RouterMeta(
//                    type = RouterMeta.Type.ACTIVITY,
//                    path = "path2",
//                    group = "group1",
//                    clazz = Test2Activity::class.java
//                )
//            }
//        }

        // 1.創(chuàng)建方法,方法名為 cacheInRouterMetaByPath
        val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_PATH)
            .addModifiers(KModifier.OVERRIDE)// 方法標(biāo)識override關(guān)鍵字
            .addParameter(//添加入?yún)?map: LinkedHashMap<String, RouterMeta>
                "map",
                LinkedHashMap::class.parameterizedBy(String::class, RouterMeta::class)
            )

        // 2.為方法添加代碼
//        map["path1"] = RouterMeta(
//            type = RouterMeta.Type.ACTIVITY,
//            path = "path1",
//            group = "group1",
//            clazz = TestActivity::class.java
//        )
        routerMetas.forEach { routerMeta ->
            funcSpecBuilder.addStatement(
                """
                    |map[%S] = RouterMeta(
                    |   type = %T.%L,
                    |   path = %S,
                    |   group = %S,
                    |   clazz = %T::class.java
                    |)
                    |
                """.trimMargin(),
                routerMeta.path,//key
                RouterMeta.Type::class,//type的類
                routerMeta.type,//type的值
                routerMeta.path,//path
                routerMeta.group,//group
                routerMeta.element!!.asType()//clazz
            )
        }

        // 3.創(chuàng)建類
        val superInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_PATH_SIMPLENAME)
        //生成的文件名 如:Router_module_a_path
        val fileName = String.format(Const.FILENAME_FORMAT_PATH, Const.FILENAME_PREFIX, group)
        val typeSpec = TypeSpec.classBuilder(fileName)
            .addFunction(funcSpecBuilder.build()) // 類中添加方法
            .addSuperinterface(superInter) // 實現(xiàn)IRouterPath接口:根據(jù)path集合將RouterMeta存入緩存
            .build()

        // 4.創(chuàng)建文件
        FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名
            .addType(typeSpec)
            .build()
            .writeTo(mFiler) // 寫入文件

        MSG.print("創(chuàng)建文件成功:${fileName}")

        // 加入緩存中,后續(xù)生成RouterGroup類用
        fileNameByGroup[group] = fileName
    }

    /**
     * 生成RouterGroup類
     */
    private fun generateRouterGroupByKotlinPoet(group: String, clzName: String) {
//        class RouterGroupTest : IRouterGroup {
//            override fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>) {
//                map["group1"] = RouterPathTest::class.java
//            }
//        }
        // 1.創(chuàng)建方法,方法名為 cacheInRouterPathByGroup
        val routePathInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_PATH_SIMPLENAME)
        val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_GROUP)
            .addModifiers(KModifier.OVERRIDE)// 方法標(biāo)識override關(guān)鍵字
            .addParameter(//添加入?yún)?map: LinkedHashMap<String, Class<out IRouterPath>>
                "map",
                LinkedHashMap::class.java.asClassName().parameterizedBy(
                    String::class.asTypeName(),//String
                    Class::class.java.asClassName().parameterizedBy(//Class<out IRouterPath>
                        WildcardTypeName.producerOf(routePathInter)
                    )
                )
            )


        // 2.為方法添加代碼  map["group1"] = RouterPathTest::class.java
        // 生成的RouterPath類
        val generatedRoutePath = ClassName(Const.GENERATE_PACKAGE, clzName)
        funcSpecBuilder.addStatement(
            "map[%S] = %T::class.java".trimMargin(),
            group,
            generatedRoutePath
        )

        // 3.創(chuàng)建類
        //生成的文件名 如:Router_module_a_group
        val routeGroupInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_GROUP_SIMPLENAME)
        val fileName = String.format(Const.FILENAME_FORMAT_GROUP, Const.FILENAME_PREFIX, group)
        val typeSpec = TypeSpec.classBuilder(fileName)
            .addFunction(funcSpecBuilder.build()) // 類中添加方法
            .addSuperinterface(routeGroupInter) // 實現(xiàn)IRouterGroup接口:根據(jù)group將IRouterPath存入緩存
            .build()

        // 4.創(chuàng)建文件
        FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名
            .addType(typeSpec)
            .build()
            .writeTo(mFiler) // 寫入文件

        MSG.print("創(chuàng)建文件成功:${fileName}")
    }
}

五、Router組件實現(xiàn)

之前只實現(xiàn)了對外的接口,接下來實現(xiàn)真正的跳轉(zhuǎn)功能,編譯期的代碼已經(jīng)生成了,運行時我們需要獲取到它,加載類并利用反射實例化

1.獲取生成類的工具類
/**
 * 獲取所有生成的代碼全路徑
 * Created by aruba on 2021/11/23.
 */
object ClassUtils {
    /**
     * 獲得程序所有的apk(instant run會產(chǎn)生很多split apk)
     */
    private fun getSourcePaths(context: Context): List<String> {
        context.packageManager.getApplicationInfo(
            context.packageName, 0
        ).apply {
            val sourcePaths: MutableList<String> = mutableListOf()
            sourcePaths.add(sourceDir)
            //instant run
            splitSourceDirs?.let {
                sourcePaths.addAll(it)
            }

            return sourcePaths
        }
    }

    /**
     * 根據(jù)包名獲取路由表
     *
     * @param context
     * @param packageName 包名
     * @return
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     * @throws InterruptedException
     */
    fun getFileNameByPackageName(context: Application, packageName: String): Set<String> =
        runBlocking {
            val classNames: MutableSet<String> = mutableSetOf()
            val paths = getSourcePaths(context)
            val coroutineScope = CoroutineScope(Dispatchers.IO);
            val jobs = mutableListOf<Job>()
            for (path in paths) {
                coroutineScope.launch {
                    var dexfile: DexFile? = null
                    try {
                        // 加載 apk中的dex 并遍歷 獲得所有包名為 {packageName} 的類
                        dexfile = DexFile(path)
                        val dexEntries = dexfile.entries()
                        while (dexEntries.hasMoreElements()) {
                            val className = dexEntries.nextElement()
                            if (className.startsWith(packageName)) {
                                classNames.add(className)
                            }
                        }
                    } catch (e: IOException) {
                        e.printStackTrace()
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close()
                            } catch (e: IOException) {
                                e.printStackTrace()
                            }
                        }
                    }
                }.apply {
                    jobs.add(this)
                }
            }
            jobs.forEach {
                it.join()
            }
            classNames
        }
}
2.Router實現(xiàn)

首先我們獲取到所有IRouterGroup的生成類,類加載并實例化后,調(diào)用方法,存入group-IRouterPath映射關(guān)系Map中

    /**
     * 初始化
     */
    fun init(context: Application) {
        // 獲取類
        ClassUtils.getFileNameByPackageName(context, GENERATE_PACKAGE)
            .filter { className ->
                className.endsWith("group")//只獲取RouterGroup
            }.forEach { className ->
                // 實例化并且調(diào)用方法
                (Class.forName(className).getConstructor().newInstance() as IRouterGroup)
                    .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入緩存中
            }
    }

再實現(xiàn)跳轉(zhuǎn)功能,先從path-RouterMeta映射關(guān)系Map中獲取,如果緩存中沒有,那么利用group-RouterPath映射關(guān)系Map獲取到IRouterPath類,同樣進行類加載并實例化后,調(diào)用方法,存入path-RouterMeta映射關(guān)系Map

    /**
     * 跳轉(zhuǎn)
     */
    fun navigation(context: Context, group: String, path: String) {
        // 獲取緩存中的RouterMeta
        CacheMap.RouterMetaByPath.getOrPut(path) {
            // 緩存中沒有,就利用RouterPath生成類實例化后,調(diào)用cacheInRouterMetaByPath加入
            (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath)
                .cacheInRouterMetaByPath(CacheMap.RouterMetaByPath)
            CacheMap.RouterMetaByPath[path]!!
        }.let { routerMeta ->
            context.startActivity(Intent(context, routerMeta.clazz))
        }
    }

完整ARouter代碼:

/**
 * 路由中間件
 * Created by aruba on 2021/11/23.
 */
class ARouter private constructor() {

    companion object {
        val INSTANCE: ARouter by lazy { ARouter() }

        // 生成的類的package
        private val GENERATE_PACKAGE = "com.aruba.router"
    }

    /**
     * 初始化
     */
    fun init(context: Application) {
        // 獲取類
        ClassUtils.getFileNameByPackageName(context, GENERATE_PACKAGE)
            .filter { className ->
                className.endsWith("group")//只獲取RouterGroup
            }.forEach { className ->
                // 實例化并且調(diào)用方法
                (Class.forName(className).getConstructor().newInstance() as IRouterGroup)
                    .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入緩存中
            }
    }

    /**
     * 跳轉(zhuǎn)
     */
    fun navigation(context: Context, group: String, path: String) {
        // 獲取緩存中的RouterMeta
        CacheMap.RouterMetaByPath.getOrPut(path) {
            // 緩存中沒有,就利用RouterPath生成類實例化后,調(diào)用cacheInRouterMetaByPath加入
            (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath)
                .cacheInRouterMetaByPath(CacheMap.RouterMetaByPath)
            CacheMap.RouterMetaByPath[path]!!
        }.let { routerMeta ->
            context.startActivity(Intent(context, routerMeta.clazz))
        }
    }
}

六、測試跳轉(zhuǎn)功能

1.使用Router插件

在libmodule_a中使用Router插件:

dependencies {
    api project(path: ':libbase')
    implementation project(path: ':librouter_router')
    kapt project(':librouter_complier')
}

app中也進行依賴:

dependencies {
    implementation project(path: ':libmodule_a')
    implementation project(path: ':librouter_router')
    kapt  project(':librouter_complier')
}
2.使用Router注解

libmodule_a的ModuleAActivity使用注解:

@Router(group = "module_a", path = "ModuleAActivity")
class ModuleAActivity : AppCompatActivity() {

app的MainActivity使用注解:

@Router(group = "app", path = "MainActivity")
class MainActivity : AppCompatActivity() {
3.Application中初始化
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        ARouter.INSTANCE.init(this)
    }
}
4.使用Router進行跳轉(zhuǎn)

MainActivity:

@Router(group = "app", path = "MainActivity")
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun toModule(view: android.view.View) {
        ARouter.INSTANCE.navigation(this, "module_a", "ModuleAActivity")
    }

}

ModuleAActivity:

@Router(group = "module_a", path = "ModuleAActivity")
class ModuleAActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_module_aactivity)
    }

    fun toMain(view: android.view.View) {
        ARouter.INSTANCE.navigation(this, "app", "MainActivity")
    }
}

最終效果:



最后細(xì)心的人可能已經(jīng)發(fā)現(xiàn),不同組件之間,group是不能重復(fù)的,一個moudle中可以有多個group

最后附上一張結(jié)構(gòu)圖:

項目地址:https://gitee.com/aruba/arouter-application.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容