通过KSP/APT及AGP实现的在Android工程多模块之间获取接口的实例对象的辅助工具。

Overview

Discovery

通过KSP/APTAGP实现的在Android工程多模块之间获取接口的实例对象的辅助工具。

通过在接口上添加@Discoverable注解后,在工程中的任意模块中通过Discoveries类获取该接口的实例,辅助开发者在模块之间访问数据。

原理

Discovery由3个功能模块构成,分别是注解处理器模块、Gradle插件模块以及一个kotlin库模块。

  • kotlin模块

    • 包含两个注解,及一个Discoveries类。
  • 注解处理器模块

    • 在编译前,获取所有被Discoverable注解标记的接口的信息,生成一个列表并记录下来。
  • Gradle插件模块

    • 在编译中,扫描每个模块中的类文件,并将上述列表中接口的实现类通过ASM注册到Discoveries类中。

安装

当前最新版本:Maven Central

  1. 在根模块的build.gradle的适当位置添加以下代码:

    buildscript {
       repositories {
           ...
           mavenCentral()
           //如果使用KSP,则必需配置以下仓库
           google()
           gradlePluginPortal()
       }
       dependencies {
           ...
           //添加Discovery插件
           classpath("cn.numeron:discovery.plugin:latest_version")
           //添加KSP插件,如果使用APT,则不需要添加
           classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.5.21-1.0.0-beta06")
       }
    }
  2. 在需要使用注解的模块中启用注解处理器,选择aptksp其中的一种方式配置即可。

    • KSP方式(Kotlin工程推荐使用)
    plugins {
        id("com.android.library")
        ...
        //应用KSP插件
        id("com.google.devtools.ksp")
    }
    
    ...
    
    dependencies {
        ...
        //应用Discovery的KSP插件
        ksp("cn.numeron:discovery.ksp:latest_version")
        //添加Discovery library库
        implementation("cn.numeron:discovery.library:latest_version")
    }
    
    ...
    
    ksp {
        //设置此模块的唯一标识和编译根目录
        arg("projectName", "module-name")
        arg("rootProjectBuildDir", rootProject.buildDir.absolutePath)
    }
    • APT方式(Java请使用此方式)
     plugins {
         id("com.android.library")
         ...
         //应用kapt插件
         id("kotlin-kapt")
     }
     
     ...
     
     dependencies {
         ...
         //应用Discovery的APT插件
         kapt("cn.numeron:discovery.apt:latest_version")
         //添加Discovery library库
         implementation("cn.numeron:discovery.library:latest_version")
     }
     
     ...
     
     kapt {
        arguments {
            //设置此模块的唯一标识和编译根目录
            arg("projectName", "asset-api")
            arg("rootProjectBuildDir", rootProject.buildDir.absolutePath)
        }
     }
  3. 在主模块的build.gradle文件中添加以下代码:

    plugins {
        id("com.android.application")
        ...
        //应用Discovery插件
        id("discovery")
    }

配置

  • Discovery默认情况下,通过扫描所有的class文件来获取接口的实现类,在大型项目中可能比较慢。为了节省编译时间,在1.2.0版本开始加入了可配置功能,配置方式如下:

    //在应用了Discovery插件的模块的build.gradle文件中添加以下代码
    discovery {
        //Discovery有Scan和Mark两种工作模式
        //Scan为默认工作模式,会扫描除依赖以外所有的类文件
        //Mark为可选的工作模式,此模式下只会处理被Implementation注解标记的类文件
        //Mark模式需要在每一个实现类上添加Implementation注解,并配置注解处理器方可工作
        mode = cn.numeron.discovery.core.Modes.Mark
    }

使用

  • 获取其它模块的业务服务

    1. 在接口上使用@Discovrable注解
    @Discoverable
    interface ISignInService {
    
        /** 判断当前是否已登录 */
        suspend fun isSignIn(context: Context): Boolean
    
        /** 通过用户名和密码进行登录 */
        suspend fun signInByPassword(username: String, password: String)
    
    }
    1. 在任意模块中实现ISignInService接口
    //如果工作模式配置为Mark,需要在此类上添加Implementation注解
    class SignInServiceImpl: ISignInService {
    
        override suspend fun isSignIn(context: Context): Boolean {
            TODO("判断是否已经登录")
        }
    
        override suspend fun signInByPassword(username: String, password: String) {
            TODO("根据提供的账号密码进行登录")
        }
    
    }
    1. 在任意模块的代码中通过Discoveries获取接口实例
    lifecycleScope.launch {
        val signInService = Discoveries.getInstance<ISignInService>()
        if (!signInService.isSignIn(requireContext())) {
            //未登录, do something...
        }
    }
  • 获取所有模块中的所有实例

    1. 在基础模块中声明初始化接口
    @Discoverable
    interface IInitializer {
    
        fun init(application: Application)
    
    }
    1. 在其它模块中实现该接口
    //需要初始化的A模块
    //如果工作模式配置为Mark,需要在此类上添加Implementation注解
    class AModuleInitializer: IInitializer {
        override fun init(application: Application) {
            //init a module
        }
    }
    
    //需要初始化的B模块
    //如果工作模式配置为Mark,需要在此类上添加Implementation注解
    class BModuleInitializer: IInitializer {
        override fun init(application: Application) {
            //init b module
        }
    }
    1. Application中获取所有实例并初始化
    class MyApplication: Application() {
    
        override fun onCreate() {
            //获取所有IInitiator的实现,并执行init方法
            val initializerList = Discoveries.getAllInstances<IInitializer>()
            initializerList.forEach {
                it.init(this)
            }
        }
    
    }

版本更新记录

  • 1.2.1

    1. Implementation注解标记的类实现了多个接口时,会忽略掉未被Discovrable注解标记的接口。
  • 1.2.0

    1. 新增Discovery的配置选项,可配置实现类的处理方式。
    2. 默认为Scan模式,即全局扫描,可配置为Mark模式,需要使用Implementation注解标记实现类,可免去扫描过程,以节省编译时间。
  • 1.1.0

    1. 注解处理器新增APT的实现,兼容java项目,与KSP任选其一即可。
  • 1.0.0

    1. 正式发布,由KSPAGP实现主要功能。
You might also like...
Comments
  • 获取不到父类为抽象类的实现

    获取不到父类为抽象类的实现

    定义如下:

    @Discoverable
    abstract class InitTask {
    
        abstract fun wang(): String
    }
    

    实现类:

    @Implementation
    class SampleTask : InitTask() {
        override fun wang(): String {
            return "I'm sample task"
        }
    }
    
    @Implementation
    class SampleTask2 : InitTask() {
        override fun wang(): String {
            return "I'm sample task2"
        }
    }
    

    获取代码:

    val allInstances = Discoveries.getAllInstances<InitTask>() // size = 0
    val allImplementClasses = Discoveries.getAllImplementClasses<InitTask>() // size = 0
    

    PS:

    • Plugin 版本测试了 1.4.0 和 1.4.1 版本
    • 相同配置 InitTask 定义为 interface 时获取代码可以正常工作

    最小可复现工程: DiscoveryDemo.zip

    opened by twiceyuan 0
Owner
null