目录
3.2、修改MyProjectManagerListener文件
3.3、注释MyApplicationService中的TODO
3.5、修改gradle.properties版本号与插件平台
前言:
自从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 文件