Android 通过自定义注解生成 json 配置文件(kotlin 版本)

Android 通过自定义注解生成 json 配置文件(kotlin 版本)

1. 创建注解 module;

1. 首先先创建一个用于编写 annotation 的 module;

file --> new --> new module --> 选择 Java or Kotlin Library --> 输入 module 名称 libnavannotation

2. 配置 gradle;

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

// 设置最低 Java 版本
sourceCompatibility = "8"
targetCompatibility = "8"

3. 创建 annotation(ActivityDestinationFragmentDestination

@Target(AnnotationTarget.CLASS) // 注解使用在类上
annotation class ActivityDestination(
    val pageUrl: String,
    val needLogin: Boolean = false, // 是否需要登录
    val asStarter: Boolean = false // 是否是启动页面
)
@Target(AnnotationTarget.CLASS) // 注解使用在类上
annotation class FragmentDestination(
    val pageUrl: String,
    val needLogin: Boolean = false, // 是否需要登录
    val asStarter: Boolean = false // 是否是启动页面
)

2. 创建注解编译器

1. 首先先创建一个用于编写 compiler 的 module;

file --> new --> new module --> 选择 Java or Kotlin Library --> 输入 module 名称 libnavcompiler

2. 配置 gradle;

apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

    implementation project(':libnavannotation') // 自定义注解
    implementation 'com.alibaba:fastjson:1.2.59' // 用于生成 json

    /*
     * 注意:
     * com.google.auto.service:auto-service:1.0-rc6 (auto-service 在该本下,对应的 gradle 版本及其插件是兼容的,可以生成对应的 json 文件)
     * distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
     * com.android.tools.build:gradle:3.6.3
     */
    implementation 'com.google.auto.service:auto-service:1.0-rc6' // annotationProcessor  project()应用一下,编译时就能自动执行 @AutoService 所注解的类
//    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' // 如果是 Java 代码,只需使用 annotationProcessor 即可
    kapt 'com.google.auto.service:auto-service:1.0-rc6' // 但是基于 kotlin 项目,必须使用 kapt
}

// 设置最低 Java 版本
sourceCompatibility = "8"
targetCompatibility = "8"

3. 创建解析器(NavProcessor);

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.google.auto.service.AutoService
import mm.chenme.lib.libnavannotation.ActivityDestination
import mm.chenme.lib.libnavannotation.FragmentDestination
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic
import javax.tools.StandardLocation
import kotlin.math.abs

@AutoService(Processor::class) // 告诉 annotationProcessor 在 project()(编译)应用时要自动执行该类
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 声明我们所支持的jdk版本
@SupportedAnnotationTypes(// 声明该注解处理器想要处理那些注解
    "mm.chenme.lib.libnavannotation.ActivityDestination",
    "mm.chenme.lib.libnavannotation.FragmentDestination"
)
class NavProcessor : AbstractProcessor() {

    private lateinit var messager: Messager // 日志打印,在java环境下不能使用 android.util.log.e()
    private lateinit var filer: Filer //文件处理工具

    private val OutputFileName = "destination.json" // 输出的文件名称

    override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        messager = processingEnv.messager
        filer = processingEnv.filer
    }

    override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {

        /*
         * 通过处理器环境上下文 roundEnv 分别获取项目中标记的 ActivityDestination.class 和 FragmentDestination.class 注解。
         * 此目的就是为了收集项目中哪些类被注解标记了
         */
        val activityElements = roundEnv.getElementsAnnotatedWith(ActivityDestination::class.java)
        val fragmentElements = roundEnv.getElementsAnnotatedWith(FragmentDestination::class.java)

        if (activityElements.isNotEmpty() || fragmentElements.isNotEmpty()) {

            // 分别处理 ActivityDestination  和 FragmentDestination 注解类型,并收集到 destMap 中,该 map 中以此就能记录下所有的页面信息了
            val destMap = HashMap<String, JSONObject>()
            handleDestination(activityElements, ActivityDestination::class.java, destMap)
            handleDestination(fragmentElements, FragmentDestination::class.java, destMap)

            /*
             * 将文件写到 app/src/main/assets/ 目录下面
             *
             * createResource(p1, p2, p3)
             * p1:指定文件输出的地方
             *     StandardLocation.CLASS_OUTPUT:java 文件生成 class 文件的位置,在(/app/build/intermediates/javac/debug/classes/)目录下
             *     StandardLocation.SOURCE_OUTPUT:java 文件的位置,一般在(/{项目名}/app/build/generated/source/apt/)目录下
             *     StandardLocation.CLASS_PATH 和 StandardLocation.SOURCE_PATH 用的不多,指的了这个参数,就要指定生成文件的 pkg 包名了
             * p3:输出的文件名称
             *
             * 由于我们想要把json文件生成在 app/src/main/assets/ 目录下,所以这里可以对字符串做一个截取和替换
             */
            val res = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OutputFileName) // 创建源文件
            val resPath = res.toUri().path
            messager.printMessage(Diagnostic.Kind.NOTE, "\nresPath --> $resPath\n")
            val appPath = resPath.substring(0, resPath.indexOf("app") + 4)
            val assetsPath = "${appPath}src/main/assets/"

            // app/src/main/assets/ 目录如果不存在,就创建出来
            val file = File(assetsPath)
            if (!file.exists()) {
                file.mkdirs()
            }

            // 每次都覆盖 app/src/main/assets/{OutputFileName} 文件
            val outputFile = File(file, OutputFileName)
            if (outputFile.exists()) {
                outputFile.delete()
            }
            outputFile.createNewFile()

            val fos = FileOutputStream(outputFile)
            val writer = OutputStreamWriter(fos, "UTF-8")
            writer.write(JSON.toJSONString(destMap))
            writer.flush()

            writer.close()
            fos.close()
        }
        return true
    }

    private fun handleDestination(elements: Set<Element>, annotationClz: Class<out Annotation>, destMap: HashMap<String, JSONObject>) {
        elements.forEach {
            var pageUrl = ""
            val clzName = (it as TypeElement).qualifiedName.toString()
            val id = abs(clzName.hashCode())
            var needLogin = false
            var asStarter = false
            var isFragment = false

            val annotation = it.getAnnotation(annotationClz)
            when (annotation) {
                is ActivityDestination -> {
                    pageUrl = annotation.pageUrl
                    needLogin = annotation.needLogin
                    asStarter = annotation.asStarter
                    isFragment = false
                }
                is FragmentDestination -> {
                    pageUrl = annotation.pageUrl
                    needLogin = annotation.needLogin
                    asStarter = annotation.asStarter
                    isFragment = true
                }
            }

            if (destMap.containsKey(pageUrl)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "不同的页面不允许使用相同的 pageUrl --> $clzName")
            } else {
                val obj = JSONObject()
                obj["pageUrl"] = pageUrl
                obj["clzName"] = clzName
                obj["id"] = id
                obj["needLogin"] = needLogin
                obj["asStarter"] = asStarter
                obj["isFragment"] = isFragment
                destMap[pageUrl] = obj
            }
        }
    }
}

3. 生成 json 文件;

1. 在项目 app gradle 中配置注解编译器;

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    // ... 其他配置省略

    compileOptions {
        // 设置最低 Java 版本
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        // 设置最低 Java 版本
        jvmTarget = "1.8"
    }
}

dependencies {
    // ... 其他配置省略

    implementation project(':libnavannotation') // 注解
//    annotationProcessor project(':libnavcompiler') // 如果是 Java 代码,只需使用 annotationProcessor 即可
    kapt project(":libnavcompiler") // 由于是 Kotlin,所以需要使用 kapt
}

2. 生成 json 文件;

配置完成后,同步 gradle,直接构建项目,构建完成后,就可以在 app/src/main/assets/ 目录下生成 destination.json 文件。


以上基于慕课网视频,目的是为了做一个备忘

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271