Android开发注解排列组合出启动任务ksp

目录
  • 背景
  • 开卷开卷
    • Ksp解析注解
    • Task生成还需要结合TaskGroup概念
    • 拆分启动步骤
  • 依赖注入
  • TODO
  • 总结

背景

之前我不想用注解来写启动框架,因为启动框架需要的参数太多了。将参数都定义在注解内和写一个task就没有本质上的差别,所以一直觉得没必要用注解来搞。

但是之前和另外一个同事聊了下,如果注解也可以进行排列组合,那么貌似就可以用注解来解决这个问题咯,感觉这样用起来就会很好玩了。

开卷开卷

首先要做的事情就是定义出我们想要的注解,可以基于我们之前对于task的定义来进行注解的定义,比如是不是主线程,是否需要等待完成,task的依赖任务,是否是锚点任务等等。

AndroidStartUp Demo地址

// 是否异步
@Async
// 是否等待
@Await
// 锚点任务
@MustAfter
// 依赖关系
@DependOn(
    dependOn = [AsyncTask1Provider::class, SimpleTask2Provider::class],
    dependOnTag = ["taskB"]
)

注解呢上面的这些就是我定义出来的新增的注解的,我后续会通过这些注解来组合出我所想要的启动的Task。

Ksp解析注解

这里我定义了一个Startup的注解,这个注解的目的就是标识当前的类是一个启动的Task。因为在ksp或者aptcompiler环节上,都会先尝试获取到当前语法树的所有注解的类。

package com.kronos.startup.annotation.startup
import com.kronos.startup.annotation.Process
/**
 *
 *  @Author LiABao
 *  @Since 2021/12/31
 *
 */
@Target(
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.CLASS
)
@Retention
annotation class Startup(
  // 进程策略
    val strategy: Process = Process.ALL,
    //进程名
    val processName: Array<String> = []
)

demo开始逐步推导我打算咋写这些东西。下面是我定义的一个简单的启动任务,Task具体内容应该有apt来生成。

@Async
@Await
@MustAfter
@DependOn(
    dependOn = [AsyncTask1Provider::class, SimpleTask2Provider::class],
    dependOnTag = ["taskB"]
)
// 执行的进程名
@Startup(strategy = Process.MAIN)
class SampleGenerate1Task : TaskRunner {
    override fun run(context: Context) {
        info("SampleGenerate1Task")
    }
}

还是我一开始的说法,我们的第一个切入点是Startup注解,然后获取到SampleGenerate1Task的抽象语法树信息,之后再进行下一步操作。

private fun addStartUp(type: KSAnnotated) {
    logger.check(type is KSClassDeclaration && type.origin == Origin.KOTLIN, type) {
        "@JsonClass can't be applied to $type: must be a Kotlin class"
    }
    if (type !is KSClassDeclaration) return
    val startupAnnotation = type.findAnnotationWithType(startupType) ?: return
    taskMap.add(StartupTaskBuilder(type, startupAnnotation))
}

基于KSClassDeclaration语法树的信息,我们可以获取到当前类上的注解,然后在收集完成之后再来生成对应的启动任务。首先我们先要获取到类上的所有的注解,然后进行遍历,当当前注解符合我们所需要的类型情况下,调整数据结构信息就可以了。

class StartupTaskBuilder(type: KSClassDeclaration, startupAnnotation: KSAnnotation?) {
    val className = type.toClassName()
    var isAsync = false
    var isAwait = false
    var strategy: String
    var processList: ArrayList<String> = arrayListOf()
    val dependOnClassList = mutableListOf<ClassName>()
    val dependOnStringList = mutableListOf<String>()
    var mustAfter: Boolean = false
    var lifecycle: Lifecycle = Lifecycle.OnApplicationCrate
    init {
        type.annotations.forEach {
            val annotation = it.annotationType.resolve().toClassName()
            if (annotation.canonicalName == "com.kronos.startup.annotation.startup.Async") {
                isAsync = true
            }
            if (annotation.canonicalName == "com.kronos.startup.annotation.startup.Await") {
                isAwait = true
            }
            if (annotation.canonicalName == "com.kronos.startup.annotation.startup.MustAfter") {
                mustAfter = true
            }
            if (annotation.canonicalName == "com.kronos.startup.annotation.startup.DependOn") {
                val value = it.getMember<ArrayList<ClassName>>("dependOn")
                dependOnClassList.addAll(value)
                val dependOnTag = it.getMember<ArrayList<String>>("dependOnTag")
                dependOnStringList.addAll(dependOnTag)
            }
            if (annotation.canonicalName == "com.kronos.startup.annotation.Step") {
                val value = it.arguments.firstOrNull {
                    it.name?.asString() == "lifecycle"
                }?.value.toString().nameToLifeCycle()
                lifecycle = value
                mLogger?.warn("stage:$value")
            }
        }
        type.getAllSuperTypes().forEach {
            it.toClassName()
        }
        strategy = startupAnnotation?.arguments?.firstOrNull {
            it.name?.asString() == "strategy"
        }?.value.toString().toValue()
        val list = startupAnnotation?.getMember<ArrayList<String>>("processName")
        list?.let { processList.addAll(it) }
    }
    xxxxxxx
  }

接下来我们用了一个数据结构来收集这些注解信息,然后和上篇文章说的一样。我们会在注解信息收集完毕之后在finish方法进行代码的生成逻辑。有兴趣的同学可以自己看下GenerateTaskKt,逻辑相对来说比较简单,基于数据结构插入不同的kt代码逻辑。

    //SymbolProcessor
    override fun finish() {
       super.finish()
       try {
           val taskGenerate = GenerateTaskKt(taskMap, codeGenerator)
           taskGenerate.procTaskGroupMap.forEach {
               val list = procTaskGroupMap.getValueByDefault(it.key) {
                   mutableListOf()
               }
               list.addAll(it.value)
           }
         }
       }

代码链接

Task生成还需要结合TaskGroup概念

因为我们之前的设想是回生成一个任务的分组StartupTaskProcessGroup,所以这部分代码上传的Task也需要保持一致。

我们需要做的就是将这些由ksp生成的Task类信息也带到TaskGroup的生成逻辑中去。

由于我们之前的底子比较好,所以我们只要在将这些类生成的信息插入到原来的list中去则就可以完成这个操作了。

    private val procTaskGroupMap =
        hashMapOf<Lifecycle, MutableList<TaskBuilder>>()
        val taskGenerate = GenerateTaskKt(taskMap, codeGenerator)
               taskGenerate.procTaskGroupMap.forEach {
                   val list = procTaskGroupMap.getValueByDefault(it.key) {
                       mutableListOf()
                   }
                   list.addAll(it.value)
               }

其实我们在上面的Task遍历的时候就已经对于这个list进行了代码插入的操作了。这样就能做到后续的插入逻辑了。

拆分启动步骤

接下来我想说的就是另外一个概念了, 因为现在有很多隐私合规的诉求,所以大部分的公司都需要做一件事,就是把隐私前的初始化逻辑和隐私后的初始化逻辑进行拆分。

这也就有了我想说的分步骤的事情了,所以我们需要在重新定义一个新的注解出来。

@Target(
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.CLASS
)
@Retention
annotation class Step(val lifecycle: Lifecycle = Lifecycle.OnApplicationCrate)
enum class Lifecycle(val value: String) {
    AttachApplication("AttachApplication"), OnApplicationCrate("OnApplicationCrate"),
    AfterUserPrivacy("AfterUserPrivacy")
}

这个就是我设想的分阶段分步骤的概念,我在demo中设置了三个不同的阶段,分别对应application的attach和create,还有隐私同意之后的代码。

这次呢,我在上述task的基础上又再次加了点东西进去,我希望一个module对外输出的是一个包含了所有阶段的StartupTaskProcessGroup的数组,我把它叫做StepTaskBuilder

之后我们只要将所有模块的StepTaskBuilder收集到一起,则就可以完成自动的初始化任务,这样做的一个好处就是后续这种依赖关系就可以在编译打包阶段都完成了,代码内只需要加入调用就可以了。

val stageGenerator = StageGenerateKt(
             "${moduleName.upCaseKeyFirstChar()}StepBuilder",
             nameList,
             codeGenerator
         )
         stageGenerator.generateKt()

我在finish方法的最后面加入了这段,就是拿来生成StepTaskBuilder用的。逻辑也相对比较简单,大家自取看看就好了。

依赖注入

看到这个小标题,我知道各位都会有迷惑。为什么一个破启动框架还需要依赖注入的逻辑?

正常情况下,我们在写sdk的时候,会有很多的初始化参数都需要使用方来定义的,比如okhttp的超时时间,缓存路径,线程大小这类的变更的参数。

那么同样的情况也会出现在启动框架内,我们想做的是一个能自动的初始化框架,那么这部分变更的参数就需要被注入。

demo中使用koin来完成的依赖注入,将依赖翻转到最外层,将变化的部分由app来设置,基本就能满足我的诉求了。

application内的实现类设置具体的实现如下。

val appModule = module {
    single&lt;ReportInitDelegate&gt; {
        ReportInitDelegateImp()
    }
}
private class ReportInitDelegateImp : ReportInitDelegate {
    override fun getAppName(): String {
        return "123444556"
    }
}

sdk模块初始化的时候通过依赖注入抽象接口的实现,这样就可以再SDK直接进行初始化代码的编写了。可以规避掉一些sdk的初始化顺序问题。

@Async
@Await
@DependOn(dependOn = [NetworkSdkTaskProvider::class])
@Startup
class ReportSdkTask : KoinComponent, TaskRunner {
    private val initDelegate: ReportInitDelegate by inject()
    override fun run(context: Context) {
        info("ReportSdkTask appName is:${initDelegate.getAppName()}")
    }
}

TODO

还有个收集所有module内的StepTaskBuilder功能没写,这部分需要增加一个Plugin,在编译阶段收集好所有,基本就可以完成对应的功能了。

总结

这部分我觉得还是挺有意思的,我们原来的设计上就是通过这种自动化的启动框架,后续来完成所有壳工程的建设,让开发同学尽量少感知到启动流程相关的逻辑。

以上就是Android开发注解排列组合出启动任务ksp的详细内容,更多关于Android注解排列组合启动任务ksp的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin + Flow 实现Android 应用初始化任务启动库

    特性 Kotlin + Flow 实现的 Android 应用初始化任务启动库. 支持模块化,按模块加载任务 可指定工作进程名称,main 表示仅在主进程运行,all 表示在所有进程运行,默认值all 可指定任务仅在工作线程执行 可指定任务仅在调试模式执行 可指定任务在满足合规条件后执行 可指定任务优先级,决定同模块内无依赖同步任务的执行顺序 可指定依赖任务列表,能检测循环依赖 使用 Flow 调度任务 仅200多行代码,简单明了 有耗时统计 引入依赖 项目地址:github.com/czy11

  • Android 反射注解与动态代理综合使用详解

    前言 本章内容主要研究一下java高级特性-反射.android注解.和动态代理的使用,通过了解这些技术,可以为了以后实现组件化或者Api hook相关的做一些技术储备. 反射 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高 比较常用的方法 getDeclare

  • Android Studio如何为Activity添加自定义注解信息

    普通Java-Kotlin类添加注释 添加类时注释作者信息和日期时间 依次打开 File->Settings->editor->File and Code Templates->Include->File Header->"添加以下代码" 时间表达式 @Date: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE} Activity添加注释 添加Activity(或者说是四大组件)注解的方式 1. 首先添加一个live

  • 如何使用Android注解处理器

    我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器. 注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息. 本篇用我之前写的Saber举例说明. 1.定义注解 推荐New -> Module -> Java Library,新建一个Java Library Module,命名为xx-annotation.用来单独存放注解. 既然是注解处理器,那么首先需要有注解.自定义一个注解使用@interface关键字. public @inte

  • 自定义Android注解系列教程之注解变量

    前言 对于Android注解,或多或少都有一点接触,但相信大多数人都是在使用其它依赖库的时候接触的.因为有些库如果你想使用它就必须使用它所提供的注解.例如:ButterKnife.Dagger2.Room等等. 至于为何使用注解?使用过的应该都知道,最明显的就是方便.简洁.通过使用注解可以在项目编译阶段,帮助我们自动生成一些重复的代码,减轻我们的负担.典型的ButterKnife本质就是使用Android注解,通过注解来减少我们对view.findViewById的编写,提高我们的开发效率.上一

  • Android基于注解的6.0权限动态请求框架详解

    前言 安卓6.0之后,一些敏感权限需要进行动态请求,虽说编写请求授权代码并不难,但是每次一需要权限就需要在视图中添加一段代码,严重影响代码美观,同时也增加了一点点工作量. 于是,小盆友闲暇之余基于AOP封装了一个基于注解的权限请求框架.如果有幸加入您的项目,使用过程中有问题或是有哪些不便,请留言区或github上与我交流,共同进步.如果喜欢这个框架请给个star和❤️. github地址:https://github.com/zincPower/JPermission 先上图,看看效果...第一

  • Android开发注解排列组合出启动任务ksp

    目录 背景 开卷开卷 Ksp解析注解 Task生成还需要结合TaskGroup概念 拆分启动步骤 依赖注入 TODO 总结 背景 之前我不想用注解来写启动框架,因为启动框架需要的参数太多了.将参数都定义在注解内和写一个task就没有本质上的差别,所以一直觉得没必要用注解来搞. 但是之前和另外一个同事聊了下,如果注解也可以进行排列组合,那么貌似就可以用注解来解决这个问题咯,感觉这样用起来就会很好玩了. 开卷开卷 首先要做的事情就是定义出我们想要的注解,可以基于我们之前对于task的定义来进行注解的

  • Android开发之开发者头条(一)启动页实现

    废话就不多说了,开始今天的正题,带你实现开发者头条APP的启动页. 一.老规矩,先上效果图 从效果图中我们可以看出,整个滑动的界面就是一个ViewPager实现,然后监听ViewPager的滑动事件,改变底部四个小图标的切换,以及跳转到首页的按钮的隐藏显示. 二.代码实现 1).整个布局文件.上面是ViewPager,下面是四个小图标存放的容器. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/androi

  • Android开发实现popupWindow弹出窗口自定义布局与位置控制方法

    本文实例讲述了Android开发实现popupWindow弹出窗口自定义布局与位置控制方法.分享给大家供大家参考,具体如下: 布局文件: 主布局文件:activity_main: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&q

  • android开发环境遇到adt无法启动的问题分析及解决方法

    开始研究android开发,搭建开发环境的时候就出了问题--果然是好事多磨~ 安装了jdk,配置环境变量,安装了完整版的adt.创建了helloworld程序,启动的时候就报错 "Please ensure that adb is correctly located at 'D:\adt-bundle-windows-x86_64\sdk\platform-tools\adb.exe' and can be executed." 网上查了大量的资料,群里问了很多人,都说是配置环境有问题

  • android开发教程之用命令启动android模拟器并设置其内存大小

    用命令启动android模拟器并设置其内存大小的代码 在终端里输入 复制代码 代码如下: emulator -avd Android2.3 -partition-size 512

  • Android开发之开发者头条(二)实现左滑菜单

    在上篇文章给大家介绍了Android开发之开发者头条(一)启动页实现,感兴趣的朋友可以参考下. title: 带你实现开发者头条(二) 实现左滑菜单 tags: 左滑菜单,android 自带侧滑,DrawerLayout grammar_cjkRuby: true 今天开始模仿开发者头条的侧滑菜单,是本系列第二篇文章,相信大家已经看到很多app使用这种侧滑.今天我来教大家用android自带DrawerLayout控件实现. DrawerLayout是SupportLibrary包中实现了侧滑

  • Android开发之开发者头条APP(三)实现首页

    相关阅读: Android开发之开发者头条(一)启动页实现 Android开发之开发者头条(二)实现左滑菜单 title: 带你实现开发者头条APP(三) 首页实现 tags: 轮播广告,ViewPager切换,圆形图片 grammar_cjkRuby: true 一.前言 今天实现开发者头条APP的首页.是本系列的第三篇文章,效果图如下: 从gif动态效果图中我们可以看出,最外层有三个tab(精选,订阅,发现),在精选界面顶部有一个轮播的图片广告,广告下面是一个精选文章列表. 二.外层三个ta

  • Android开发仿QQ空间根据位置弹出PopupWindow显示更多操作效果

    我们打开QQ空间的时候有个箭头按钮点击之后弹出PopupWindow会根据位置的变化显示在箭头的上方还是下方,比普通的PopupWindow弹在屏幕中间显示好看的多. 先看QQ空间效果图: 这个要实现这个效果可以分几步进行 1.第一步自定义PopupWindow,实现如图的样式,这个继承PopupWindow自定义布局很容易实现 2.得到点击按钮的位置,根据位置是否在屏幕的中间的上方还是下方,将PopupWindow显示在控件的上方或者下方 3.适配问题,因为PopupWindow上面的操作列表

  • Android开发基础之创建启动界面Splash Screen的方法

    本文实例讲述了Android开发基础之创建启动界面Splash Screen的方法.分享给大家供大家参考.具体如下: 启动界面Splash Screen在应用程序是很常用的,往往在启动界面中显示产品Logo.公司Logo或者开发者信息,如果应用程序启动时间比较长,那么启动界面就是一个很好的东西,可以让用户耐心等待这段枯燥的时间. Android 应用程序创建一个启动界面Splash Screen非常简单.比如创建一个工程MySample,主Acitity就叫MySample,创建另一个Activ

  • Android开发实现长按返回键弹出关机框功能

    本文实例讲述了Android开发实现长按返回键弹出关机框功能.分享给大家供大家参考,具体如下: 今天刚好在PhoneWindowManager.java下看,当看到长按Home键的功能时,突然想到是不是可以长按back键来弹出关机框. 有想法就试试呗.当然想法是根据长按home键来的,那么我们应该可以模仿长按Home键来做.经过一番实验,貌似好像可以,拿出来给大家分享一下!!! 先找到PhoneWindowManager.java文件,在framework/base/policy/src/com

随机推荐