Android Studio 4.1+自定义代码生成插件

目录

前言:

1、环境

 2、创建代码仓

2.1、打开JetBrains模板仓库

2.2、使用官方模板仓库

 2.3、创建自己的仓库

 3、模板工程配置

3.1、将模板工程clone到本地用AS打开

 3.2、引入wizard-template.jar

 3.2、修改MyProjectManagerListener文件

3.3、注释MyApplicationService中的TODO

3.4、注释MyProjectService中的TODO

3.5、修改gradle.properties版本号与插件平台

3.6、修改plugin.xml

4、开始写插件

4.1、创建Provider

 4.2、创建模板

4.2.1、目录结构

4.2.2、工具类 

 4.2.3、LayoutSimpleTemp.kt

 4.2.4、LayoutMvpTemp.kt

4.2.5、SimpleActivityTemp.kt

4.2.6、MvpActivityTemp.kt 

 4.2.7、MvpModelTemp.kt

4.2.8、MvpViewTemp.kt 

 4.2.9、MvpPresenterTemp.kt

4.3、创建文件写入类 

 4.4、创建生成器

 4.5、Provider中添加代码生成器

5、打包

5.1、设置RunConfig为Run Plugin

 5.2、编译通过后会打开一个IDEA窗口

 5.3、测试插件是否可以工作

 5.4、找到生成的插件jar包

5.5、安装插件 

5.6、安装完成测试是否可用 

5.7、齐活 


前言:

        自从AS4.1+以后,更改了自定义模板的使用方法,以前在安装目录中自定义FreeMarker模板引擎的方式被弃用。然后我就改用Java生成代码的方式,外部引入一个Generator工程,虽然一样可以用,但是无法向Manifest文件中注册Activity,并不完美。

        最近闲暇,想研究一下代码生成插件。对于面向百度编程人员,当然是直接网上搜索。但是网上相关资料很少,就是一个人的分享的内容被反复转载。感谢作者提供的思路。每个人都有自己惯用的框架,代码生成插件每个人都需要自己定制。我将这2天的插件编写过程整理出一个傻瓜流程,给工作紧张没有时间研究的朋友参考。按照流程走一遍可以轻松定义一套自己适用的代码生成插件。

        为了让大家节省时间,我会尽量写的详细。Let's go...

1、环境

Android Studio:Android Studio Electric Eel | 2022.1.1 Patch 1

kotlin:1.8

gradle:6.7

jdk:11.0.17

 2、创建代码仓

2.1、打开JetBrains模板仓库

官方模板仓库地址:

https://github.com/JetBrains/intellij-platform-plugin-template

2.2、使用官方模板仓库

需要先登录Github,然后点击绿色Use this template按钮。选择Create a new repository

 2.3、创建自己的仓库

 3、模板工程配置

3.1、将模板工程clone到本地用AS打开

 3.2、引入wizard-template.jar

1、打开android studio目录,找到wizard-template.jar文件,文件位置为:AS目录\plugins\android\lib

2、项目根目录创建 lib 文件夹,将wizard-template.jar文件复制到lib文件夹中。

3、修改build.gradle.kts,添加wizard-template.jar的依赖

dependencies {
    compileOnly(files("lib/wizard-template.jar"))
}

 3.2、修改MyProjectManagerListener文件

package com.github.summersrest.androidgenerator.listeners

import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener
import com.github.summersrest.androidgenerator.services.MyProjectService

internal class MyProjectManagerListener : ProjectManagerListener {

    override fun projectOpened(project: Project) {
        projectInstance = project
        project.getService(MyProjectService::class.java)
    }

    override fun projectClosing(project: Project) {
        projectInstance = null
        super.projectClosing(project)
    }

    companion object {
        var projectInstance: Project? = null
    }
}

3.3、注释MyApplicationService中的TODO

class MyApplicationService {

    init {
        println(MyBundle.message("applicationService"))

        //注释掉或者删除
//        System.getenv("CI")
//            ?: TODO("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
    }
}

3.4、注释MyProjectService中的TODO

class MyProjectService(project: Project) {

    init {
        println(MyBundle.message("projectService", project.name))

        //注释掉或者删除
//        System.getenv("CI")
//            ?: TODO("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
    }

    /**
     * Chosen by fair dice roll, guaranteed to be random.
     */
    fun getRandomNumber() = 4
}

3.5、修改gradle.properties版本号与插件平台

pluginVersion = 0.0.1
platformPlugins = java, com.intellij.java, org.jetbrains.android, android, org.jetbrains.kotlin

3.6、修改plugin.xml

设置插件名称,作者,兼容平台

<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
    <id>com.github.summersrest.androidgenerator</id>
    <name>Android Generator</name>
    <vendor>SummersRest</vendor>

    <!-- Product and plugin compatibility requirements -->
    <!-- https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html -->
    <depends>org.jetbrains.android</depends>
    <depends>org.jetbrains.kotlin</depends>
    <depends>com.intellij.modules.java</depends>
    <depends>com.intellij.modules.platform</depends>

    <extensions defaultExtensionNs="com.intellij">
        <applicationService serviceImplementation="com.github.summersrest.androidgenerator.services.MyApplicationService"/>
        <projectService serviceImplementation="com.github.summersrest.androidgenerator.services.MyProjectService"/>
    </extensions>

    <applicationListeners>
        <listener class="com.github.summersrest.androidgenerator.listeners.MyProjectManagerListener"
                  topic="com.intellij.openapi.project.ProjectManagerListener"/>
    </applicationListeners>
</idea-plugin>

4、开始写插件

4.1、创建Provider

1、在kotlin目录下创建开发包,名字自己起,比如generator

2、在generator文件夹中创建一个provider,继承WizardTemplateProvider

package generator

import com.android.tools.idea.wizard.template.Template
import com.android.tools.idea.wizard.template.WizardTemplateProvider

/**
 * @author  LiuJiang
 * Desc:    GeneratorProvider
 */
class PluginGeneratorProvider : WizardTemplateProvider() {

    override fun getTemplates(): List<Template> = listOf()
}

3、别忘记在plugin.xml中注册该provider

<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
    <wizardTemplateProvider implementation="generator.PluginGeneratorProvider" />
</extensions>

 4.2、创建模板

for(int i = 0; i < 3; i++) {

        此处各位请根据自己的代码自己创建模板,个人习惯使用MVP,我以我的代码为例。

}

4.2.1、目录结构

这是我的目录结构,个人根据自己的习惯分包。

4.2.2、工具类 

Utils.kt

package generator.util

import java.text.SimpleDateFormat
import java.util.*

/**
 * 获取当前时间
 */
fun time(): String {
    val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.CHINA)
    return dateFormat.format(Date())
}

/**
 * 下划线转驼峰
 */
fun underlineToHump(param: String): String {
    if (param.isEmpty()) {
        return ""
    }
    //全部转小写
    return param.lowercase().split("_").map { item ->
        //每个单词首字母大写
        item.replaceFirstChar { first ->
            first.uppercase()
        }
    }.joinToString("")
}

PackageName.kt

package generator.util

import com.android.tools.idea.wizard.template.Constraint
import com.android.tools.idea.wizard.template.stringParameter

/**
 * @author  LiuJiang
 * Desc:    获取包名
 */
val defaultPackageNameParameter
    get() = stringParameter {
        name = "Package name"
        visible = { !isNewModule }
        default = "com.github.summersrest"
        constraints = listOf(Constraint.PACKAGE)
        suggest = { packageName }
    }

 Contant.kt

package generator.util

/**
 * 应用代码路径
 */
const val APP_PATH = "application"

/**
 * Activity目录名称
 */
const val ACTIVITY_PATH = "activity"

/**
 * View目录名称
 */
const val VIEW_PATH = "view"

/**
 * Model目录名称
 */
const val MODEL_PATH = "model"

/**
 * Presenter目录名称
 */
const val PRESENTER_PATH = "presenter"

/**
 * Fragment目录名称
 */
const val FRAGMENT_PATH = "fragment"

/**
 * Bean目录名称
 */
const val BEAN_PATH = "pojo"

 4.2.3、LayoutSimpleTemp.kt

package generator.mvp.simple_activity.template.kotlin

/**
 * @author  LiuJiang
 * Desc:    简单的Activity layout模板
 */
fun simpleLayoutTemp(
    desc: String
) = """
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.sum.titlebar.TitleBar
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tb_title="$desc" />

</LinearLayout>
"""

 4.2.4、LayoutMvpTemp.kt

package generator.mvp.simple_activity.template.kotlin

/**
 * @author  LiuJiang
 * Desc:    Mvp Layout模板
 */
fun mvpLayoutTemp(
    desc: String
) = """
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.sum.titlebar.TitleBar
            android:id="@+id/title_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tb_title="$desc" />

        <com.sum.multiple.MultipleStatusView
            android:id="@+id/mu_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </com.sum.multiple.MultipleStatusView>
    </LinearLayout>
""".trimIndent()

4.2.5、SimpleActivityTemp.kt

package generator.mvp.simple_activity.template.kotlin

import generator.util.*


/**
 * @author  LiuJiang
 * Desc:    简单的Activity模板
 */
fun simpleActivityTemp(
    applicationPackage: String?,
    packageName: String,
    activityClass: String,
    desc: String
) = """
package ${packageName}.${ACTIVITY_PATH}

import android.os.Bundle
import ${applicationPackage}.base.activity.BaseActivity
import ${applicationPackage}.databinding.Activity${activityClass}Binding

/**
 * @author  LiuJiang
 * created  at: ${time()}
 * Desc:    $desc
 */
class ${activityClass}Activity: BaseActivity<Activity${activityClass}Binding>() {
    override fun getBinding() = Activity${activityClass}Binding.inflate(layoutInflater)

    override fun initView(savedInstanceState: Bundle?) {
    
    }
}
"""

4.2.6、MvpActivityTemp.kt 

package generator.mvp.simple_activity.template.kotlin

import generator.util.*

/**
 * @author  LiuJiang
 * Desc:    Mvp Activity模板
 */
fun mvpActivityTemp(
    applicationPackage: String?,
    packageName: String,
    activityClass: String,
    presenterName: String,
    viewName: String,
    desc: String
) = """
    package ${packageName}.${ACTIVITY_PATH}

    import android.os.Bundle
    import ${packageName}.${PRESENTER_PATH}.$presenterName
    import ${packageName}.${VIEW_PATH}.$viewName
    import ${applicationPackage}.base.activity.BaseMvpActivity
    import ${applicationPackage}.databinding.Activity${activityClass}Binding

    /**
     * @author  LiuJiang
     * created  at: ${time()}
     * Desc:    $desc
     */
    class ${activityClass}Activity : BaseMvpActivity<Activity${activityClass}Binding, ${presenterName}>(), $viewName {
        override fun createPresenter() = ${presenterName}(this, this)

        override fun getBinding() = Activity${activityClass}Binding.inflate(layoutInflater)

        override fun initView(savedInstanceState: Bundle?) {
            
        }


    }
""".trimIndent()

 4.2.7、MvpModelTemp.kt

package generator.mvp.simple_activity.template.kotlin

import generator.util.*

/**
 * @author  LiuJiang
 * Desc:    Mvp model 模板
 */
fun mvpModelTemp (
    applicationPackage: String?,
    packageName: String,
    modelName: String,
    desc: String
) = """
    package ${packageName}.${MODEL_PATH}

    import android.content.Context
    import ${applicationPackage}.base.mvp.BaseModel

    /**
     * @author  LiuJiang
     * created  at: ${time()}
     * Desc:    $desc
     */
    class ${modelName}(context: Context?) : BaseModel(context) {

    }
""".trimIndent()

4.2.8、MvpViewTemp.kt 

package generator.mvp.simple_activity.template.kotlin

import generator.util.*

/**
 * @author  LiuJiang
 * Desc:    mvp view 模板
 */
fun mvpViewTemp(
    applicationPackage: String?,
    packageName: String,
    viewName: String,
    desc: String
) = """
    package ${packageName}.${VIEW_PATH}

    import ${applicationPackage}.base.mvp.BaseView

    /**
     * @author  LiuJiang
     * created  at: ${time()}
     * Desc:    $desc
     */
    interface $viewName : BaseView {
    
    }
""".trimIndent()

 4.2.9、MvpPresenterTemp.kt

package generator.mvp.simple_activity.template.kotlin

import generator.util.*

/**
 * @author  LiuJiang
 * Desc:    mvp presenter 模板
 */
fun mvpPresenterTemp(
    applicationPackage: String?,
    packageName: String,
    presenterName: String,
    modelName: String,
    viewName: String,
    desc: String
) = """
    package ${packageName}.${PRESENTER_PATH}

    import android.content.Context
    import ${packageName}.${MODEL_PATH}.$modelName
    import ${packageName}.${VIEW_PATH}.$viewName
    import ${applicationPackage}.base.mvp.BasePresenter

    /**
     * @author  LiuJiang
     * created  at: ${time()}
     * Desc:    $desc
     */
    class ${presenterName}(listener: ${viewName}, context: Context?) : BasePresenter<${modelName}, ${viewName}>(listener, context) {
        override fun createModel() = ${modelName}(context)

        
    }
""".trimIndent()

4.3、创建文件写入类 

创建SimpleActivityRecipe.kt

package generator.mvp.simple_activity

import com.android.tools.idea.wizard.template.Language
import com.android.tools.idea.wizard.template.ModuleTemplateData
import com.android.tools.idea.wizard.template.RecipeExecutor
import com.android.tools.idea.wizard.template.impl.activities.common.generateManifest
import generator.mvp.simple_activity.template.kotlin.*

/**
 * @author  LiuJiang
 * Desc:    生成文件
 */
fun RecipeExecutor.simpleActivityRecipe(
    moduleData: ModuleTemplateData,
    packageName: String,
    activityClass: String,
    layoutName: String,
    desc: String,
    isMvp: Boolean,
    modelName: String,
    viewName: String,
    presenterName: String,
    language: Language
) {
    val (projectData, srcOut, resOut) = moduleData
    val ktOrJavaExt = language.extension
    //插入Manifest
    generateManifest(
        moduleData = moduleData,
        activityClass = "${activityClass}Activity",
        packageName = packageName.replace(projectData.applicationPackage!!, "") + ".activity",
        isLauncher = false,
        hasNoActionBar = false,
        isNewModule = false,
        isLibrary = false,
        generateActivityTitle = false
    )
    if (language == Language.Kotlin) {
        if (isMvp) {
            //生成activity
            save(mvpActivityTemp(projectData.applicationPackage, packageName, activityClass, presenterName, viewName, desc), srcOut.resolve("activity/${activityClass}Activity.${ktOrJavaExt}"))
            //生成presenter
            save(mvpPresenterTemp(projectData.applicationPackage, packageName, presenterName, modelName, viewName, desc), srcOut.resolve("presenter/${presenterName}.${ktOrJavaExt}"))
            //生成model
            save(mvpModelTemp(projectData.applicationPackage, packageName, modelName, desc), srcOut.resolve("model/${modelName}.${ktOrJavaExt}"))
            //生成view
            save(mvpViewTemp(projectData.applicationPackage, packageName, viewName, desc), srcOut.resolve("view/${viewName}.${ktOrJavaExt}"))
            //生成layout
            save(mvpLayoutTemp(desc), resOut.resolve("layout/${layoutName}.xml"))
        } else {
            //生成activity
            save(simpleActivityTemp(projectData.applicationPackage, packageName, activityClass, desc), srcOut.resolve("activity/${activityClass}Activity.${ktOrJavaExt}"))
            //生成layout
            save(simpleLayoutTemp(desc), resOut.resolve("layout/${layoutName}.xml"))
        }

    } else if (language == Language.Java) {

    }

}

 4.4、创建生成器

创建SimpleActivityGenerator.kt

package generator.mvp.simple_activity

import com.android.tools.idea.wizard.template.*
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API
import generator.util.defaultPackageNameParameter

/**
 * @author  LiuJiang
 * Desc:    简单的Activity生成器
 */
val simpleActivityGenerator
    get() = template {
        name = "Mvp Activity"
        description = "生成生成MVP框架的Activity和layout"
        minApi = MIN_API

        category = Category.Activity
        formFactor = FormFactor.Mobile
        screens = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)

        // Activity
        val activityClass = stringParameter {
            name = "Activity Name"
            default = "Xxx"
            help = "只输入名字,不要包含Activity"
            constraints = listOf(Constraint.NONEMPTY)
        }

        // layout
        val layoutName = stringParameter {
            name = "Layout Name"
            default = "activity_xxx"
            help = "请输入布局的名字"
            constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
            suggest = { "activity_${camelCaseToUnderlines(activityClass.value)}" }
        }

        //desc
        val desc = stringParameter {
            name = "description"
            default = ""
            help = "请输入注释"
            constraints = listOf(Constraint.STRING)
        }

        //是否生成mvp
        val isMvp = booleanParameter {
            name = "Is MVP"
            help = "是否生成Model,View,Presenter"
            default = true
        }

        //model
        val modelName = stringParameter {
            name = "Model Name"
            default = "XxxModel"
            help = "请输入model的名字"
            constraints = listOf(Constraint.NONEMPTY)
            suggest = { "${activityClass.value}Model" }
            visible = { isMvp.value }
        }

        //view
        val viewName = stringParameter {
            name = "View Name"
            default = "XxxView"
            help = "请输入view的名字"
            constraints = listOf(Constraint.NONEMPTY)
            suggest = { "${activityClass.value}View" }
            visible = { isMvp.value }
        }

        //presenter
        val presenterName = stringParameter {
            name = "Presenter Name"
            default = "XxxPresenter"
            help = "请输入presenter的名字"
            constraints = listOf(Constraint.NONEMPTY)
            suggest = { "${activityClass.value}Presenter" }
            visible = { isMvp.value }
        }

        val packageName = defaultPackageNameParameter

        val language = enumParameter<Language> {
            name = "Source Language"
            help = "请选择语言"
            default = Language.Kotlin
        }

        widgets(
            TextFieldWidget(activityClass),
            TextFieldWidget(desc),
            TextFieldWidget(layoutName),
            CheckBoxWidget(isMvp),
            TextFieldWidget(modelName),
            TextFieldWidget(viewName),
            TextFieldWidget(presenterName),
            PackageNameWidget(packageName),
            EnumWidget(language),
        )

        recipe = {
            simpleActivityRecipe(
                it as ModuleTemplateData,
                packageName.value,
                activityClass.value,
                layoutName.value,
                desc.value,
                isMvp.value,
                modelName.value,
                viewName.value,
                presenterName.value,
                language.value,
            )
        }
    }

 4.5、Provider中添加代码生成器

PluginGeneratorProvider中的getTemplates()函数中添加此生成器

class PluginGeneratorProvider : WizardTemplateProvider() {
    override fun getTemplates(): List<Template> = listOf(
        //添加代码生成器
        simpleActivityGenerator,
    )
}

5、打包

5.1、设置RunConfig为Run Plugin

1、点击运行配置

2、设置运行插件

5.2、编译通过后会打开一个IDEA窗口

这是系统生成的一个测试工具,默认安装了我们刚刚编写的插件,我们可以新建一个Android工程进行测试。

可以进行测试,也可以直接关掉,让他继续编译生成插件。我测试一直有问题,所以直接跳过了。

 

 

5.3、测试插件是否可以工作

如果报错,会在原工程的窗口出现错误日志

这里我测试无法通过,一直无法获取包名,但是插件实际使用没问题,原因未知,不管他了。关掉这个IDEA。

5.4、找到生成的插件jar包

生成的插件在编译生成的 build/lib

5.5、安装插件 

Settings->Plugins->Install Plugin from Disk

选择刚刚生成的 AndroidGenerator-0.0.1.jar 文件

5.6、安装完成测试是否可用 

5.7、齐活 

代码下载地址:

Github:https://github.com/summersrest/AndroidGenerator

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android Studio中,代码自动补全是一个非常方便的功能,可以大大提高我们的编码效率。下面是一些关于代码自动补全的常见问题及其解答: 1. 代码自动补全在Android Studio中如何启用? 在Android Studio中,默认情况下,代码自动补全是启用的。你只需输入部分代码,然后按下Tab键或者Enter键,就可以自动补全代码了。 2. 如何更改自动补全的触发方式? 如果你想更改自动补全的触发方式,可以按照以下步骤进行操作: - 打开Android Studio的设置(Preferences)。 - 在左侧的面板中,选择"Editor",然后选择"General"。 - 在右侧的面板中,找到"Code Completion"选项。 - 在"Code Completion"选项中,你可以选择自己喜欢的触发方式,例如按下空格键、点号(.)等。 3. 代码自动补全的功能有哪些? Android Studio的代码自动补全功能非常强大,它可以帮助你快速输入代码,并提供代码提示和建议。它可以自动补全类、方法、变量名等,并且会显示相关的文档注释和方法签名。此外,还可以根据上下文智能地推断并提供合适的代码补全选项。 4. 如何查看代码自动补全的建议列表? 当你输入代码并启用了自动补全功能后,Android Studio会显示一个弹出窗口,其中包含与你输入的内容相关的建议列表。你可以使用键盘上的上下箭头键来选择建议列表中的项,然后按下Tab键或者Enter键进行选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值