在实现Router机制之前,我们还可以对项目的组织架构进行优化,将gradle中公用部分抽出来
有了上一篇的基础,我们初步实现了架构分层,目前有三个module:
其依赖关系为: app << libmodule_a << libase,但是每个module的gradle中都有重复的内容,如版本号、版本名、SDK版本、重复依赖等,我们可以利用groovy和gradle的知识,为它们设计成共用属性 1.创建config.gradle
在工程下新建一个config.gradle文件
将重复的内容设置成全局属性:
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" } //根据变量配置不同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路径
项目后续可能会有很多个module,如果你想单独使用文件夹进行分类,比如基础的组件放入基础的文件夹下,可以在settings.gradle中为module重新设置新路径
我将libbase放入了新建文件夹lib_comm下:
修改settings.gradle配置:
include ':libbase' project(':libbase').projectDir = new File('lib_comm/libbase')
其他的module也可以用该方法归类
二、定义注解要用到APT,那么肯定要自定义注解,来指定APT解析的注解
1.新建一个AnnotationModule该module会被业务module和插件moudle依赖
2.定义Router注解在需要跳转的Activity上使用该注解,使用group和path来区分需要跳转的目标
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class Router( val group: String,//表示组,不可为空 val path: String//表示目标,不可重复 )3.定义Router包装类
包装类用来最后跳转使用,里面主要是存放着被注解的Activity类
data class Routermeta( val type: Type = Type.ACTIVITY,//类型 val path: String,//路由注解的path val group: String,//路由注解的group val clazz: Class<*>? = null//类对象 ) { var element: Element? = null//节点 enum class Type { ACTIVITY, ISERVICE } }三、Router组件
上面我们的注解类中定义了group和path,还有Routermeta包装类,最后生成的Map的对应关系如下图:
上面的是一个group对应一个IPathRouter生成类,IPathRouter是一个接口,我们会在Router组件中定义出来,下面是一个path对应一个Routermeta包装类
这两个Map,用来替换我们上一篇中,自己要手动导入Activity关系的Map,看到这你也许会懵,没关系往下看接口的定义
Route组件是实现功能的中间件,业务组件通过调用该组件方法来进行跳转
依赖以下包:
dependencies { implementation androidAppCompat implementation kotlinCoroutine api project(path: ':librouter_annotation') }2.定义接口 2.1 path-Routermeta映射关系
首先是需要往一个map里存入path-Routermeta映射关系IRouterPath:
interface IRouterPath { public fun cacheInRoutermetaByPath(map: linkedHashMap) }
为了方便理解,写一个测试类来实现该接口,我们最后通过kotlinpoet生成的类也是参考该实现类:
class RouterPathTest : IRouterPath { override fun cacheInRoutermetaByPath(map: linkedHashMap) { 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 ) ... } }
就是在上一篇中,我们手动将映射关系存入Map的 *** 作
2.2 group-IPathRouter映射关系为了方便管理和扩展,我们引入了group的概念,group类似IRouterPath实现类的代理,一个group对应一个IRouterPath实现类,最终我们通过group将IRouterPath实现类存入MapIRouterGroup:
interface IRouterGroup { public fun cacheInRouterPathByGroup(map: linkedHashMap>) }
同样的写一个测试类,IRouterGroup 的实现就简单了,只需要一对一的关系:
class RouterGroupTest : IRouterGroup { override fun cacheInRouterPathByGroup(map: linkedHashMap3.定义全局缓存>) { map["group1"] = RouterPathTest::class.java } }
第二步我们需要往两个Map中存入映射关系,来获取跳转时对应的类,现在把它们定义出来
object CacheMap { // 根据group可以获取到RouterPath val RouterPathByGroup: linkedHashMap四、APT+kotlinpoet自动生成类> = linkedHashMap() // 根据path可以获取到Routermeta val RoutermetaByPath: linkedHashMap = linkedHashMap() }
有了上面的接口和全局缓存,我们就需要自动生成两个实现类了
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解析节点和kotlinpoet代码生成时需要用到:类的包名和类名、方法名、生成的文件名(也是类名)、生成的类的包路径等
object Const { const val ACTIVITY = "android.app.Activity" const val ISERVICE = "android.app.Fragment" // 生成的类实现的接口 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" //接口需要实现的方法 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
每个使用了插件的业务module都会执行一次注解解释器的方法,我们对注解的处理主要分为两步:
- 使用APT获取Router注解的类,并进行包装,最后存入一个group-Routermeta列表的Map中
- 对group-Routermeta列表的Map进行处理,首先遍历Routermeta列表,使用kotlinpoet生成IRouterPath的实现类,再根据group和IRouterPath实现类的文件名(类名),生成实现IRouterGroup的类
@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); } // 节点工具类 (类、函数、属性都是节点) 用来获取节点 private lateinit var mElementUtils: Elements // type(类信息)工具类 用来比对节点 private lateinit var mTypeUtils: Types // 文件 *** 作类 用来生成kotlin文件 private lateinit var mFiler: Filer // Activity类的节点描述 private val typeActivity by lazy { mElementUtils.getTypeElement(Const.ACTIVITY).asType() } // IService接口的节点描述 private val typeIService by lazy { mElementUtils.getTypeElement(Const.ISERVICE).asType() } // 组名:Routermeta列表的Map private val routermetaByGroup: MutableMap五、Router组件实现> by lazy { mutableMapOf() } // 组名:文件名列表的Map private val fileNameByGroup: MutableMap 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 } } override fun process( typeElementSet: MutableSet ?, roundEnvironment: RoundEnvironment? ): Boolean { if (isInit) return true isInit = true typeElementSet?.let { // 1.获取所有使用了Router注解的节点并遍历 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.加入缓存,即根据group分组 routermetaByGroup.getOrPut(group) { mutableListOf() }.add(this) } } MSG.print(routermetaByGroup.toString()) // 5.利用kotlinpoet生成代码 generateClassByKotlinPoet() } return true } private fun getRoutermeta( element: Element, router: Router, typeUtils: Types, mirror: (mirror: TypeMirror) -> Unit = {} ): Routermeta { // 获取当前节点的描述 val type = element.asType().apply { mirror(this) } // 根据描述是否是typeActivity或者typeIService生成包装类 return when { typeUtils.isSubtype(type, typeActivity) -> {//是Activity子类 Routermeta(path = router.path, group = router.group).apply { this.element = element } } typeUtils.isSubtype(type, typeIService) -> {//实现了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() } } } private fun generateClassByKotlinPoet() { routermetaByGroup.forEach { (group, routermetas) -> // 分别生成两个类并实现上面两个接口,以便在Router中获取 // 1.先来生成 根据path集合将Routermeta包装类存入一个map 的类 generateRouterPathByKotlinPoet(group, routermetas) } fileNameByGroup.forEach { (group, clzName) -> // 2.再生成 根据group将1.生成的类存入一个map 的类 generateRouterGroupByKotlinPoet(group, clzName) } } private fun generateRouterPathByKotlinPoet( group: String, routermetas: MutableList ) { // 可以自己先实现一个,再参考着写 // class RouterPathTest : IRouterPath { // override fun cacheInRoutermetaByPath(map: linkedHashMap ) { // 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.创建方法,方法名为 cacheInRoutermetaByPath val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_PATH) .addModifiers(KModifier.OVERRIDE)// 方法标识override关键字 .addParameter(//添加入参 map: linkedHashMap "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.创建类 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) // 实现IRouterPath接口:根据path集合将Routermeta存入缓存 .build() // 4.创建文件 FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名 .addType(typeSpec) .build() .writeTo(mFiler) // 写入文件 MSG.print("创建文件成功:${fileName}") // 加入缓存中,后续生成RouterGroup类用 fileNameByGroup[group] = fileName } private fun generateRouterGroupByKotlinPoet(group: String, clzName: String) { // class RouterGroupTest : IRouterGroup { // override fun cacheInRouterPathByGroup(map: linkedHashMap >) { // map["group1"] = RouterPathTest::class.java // } // } // 1.创建方法,方法名为 cacheInRouterPathByGroup val routePathInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_PATH_SIMPLENAME) val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_GROUP) .addModifiers(KModifier.OVERRIDE)// 方法标识override关键字 .addParameter(//添加入参 map: linkedHashMap > "map", linkedHashMap::class.java.asClassName().parameterizedBy( String::class.asTypeName(),//String Class::class.java.asClassName().parameterizedBy(//Class 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.创建类 //生成的文件名 如: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) // 实现IRouterGroup接口:根据group将IRouterPath存入缓存 .build() // 4.创建文件 FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名 .addType(typeSpec) .build() .writeTo(mFiler) // 写入文件 MSG.print("创建文件成功:${fileName}") } }
之前只实现了对外的接口,接下来实现真正的跳转功能,编译期的代码已经生成了,运行时我们需要获取到它,加载类并利用反射实例化
1.获取生成类的工具类object ClassUtils { private fun getSourcePaths(context: Context): List2.Router实现{ context.packageManager.getApplicationInfo( context.packageName, 0 ).apply { val sourcePaths: MutableList = mutableListOf() sourcePaths.add(sourceDir) //instant run splitSourceDirs?.let { sourcePaths.addAll(it) } return sourcePaths } } fun getFileNameByPackageName(context: Application, packageName: String): Set = runBlocking { val classNames: MutableSet = mutableSetOf() val paths = getSourcePaths(context) val coroutineScope = CoroutineScope(Dispatchers.IO); val jobs = mutableListOf () 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 } }
首先我们获取到所有IRouterGroup的生成类,类加载并实例化后,调用方法,存入group-IRouterPath映射关系Map中
fun init(context: Application) { // 获取类 ClassUtils.getFileNameByPackageName(context, GENERATE_PACKAGE) .filter { className -> className.endsWith("group")//只获取RouterGroup }.forEach { className -> // 实例化并且调用方法 (Class.forName(className).getConstructor().newInstance() as IRouterGroup) .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入缓存中 } }
再实现跳转功能,先从path-Routermeta映射关系Map中获取,如果缓存中没有,那么利用group-RouterPath映射关系Map获取到IRouterPath类,同样进行类加载并实例化后,调用方法,存入path-Routermeta映射关系Map
fun navigation(context: Context, group: String, path: String) { // 获取缓存中的Routermeta CacheMap.RoutermetaByPath.getOrPut(path) { // 缓存中没有,就利用RouterPath生成类实例化后,调用cacheInRoutermetaByPath加入 (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath) .cacheInRoutermetaByPath(CacheMap.RoutermetaByPath) CacheMap.RoutermetaByPath[path]!! }.let { routermeta -> context.startActivity(Intent(context, routermeta.clazz)) } }
完整ARouter代码:
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 -> // 实例化并且调用方法 (Class.forName(className).getConstructor().newInstance() as IRouterGroup) .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入缓存中 } } fun navigation(context: Context, group: String, path: String) { // 获取缓存中的Routermeta CacheMap.RoutermetaByPath.getOrPut(path) { // 缓存中没有,就利用RouterPath生成类实例化后,调用cacheInRoutermetaByPath加入 (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath) .cacheInRoutermetaByPath(CacheMap.RoutermetaByPath) CacheMap.RoutermetaByPath[path]!! }.let { routermeta -> context.startActivity(Intent(context, routermeta.clazz)) } } }六、测试跳转功能 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进行跳转
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") } }
最终效果:
最后细心的人可能已经发现,不同组件之间,group是不能重复的,一个moudle中可以有多个group
最后附上一张结构图:项目地址:https://gitee.com/aruba/arouter-application.git
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)