这篇文章旨在向读者介绍 IntelliJ IDE 插件的开发流程以及常用的一些通用功能,任何基于 IntelliJ 开发的 IDE 都可以通过该方式制作插件,例如 Android Studio(AS),本篇也将基于 Android Studio 进行展开介绍,读者将从 0 到 1 学习到 Android Studio 插件开发。
IDE 插件是将一些功能集成到了 IDE 界面当中,当我们使用 IDE 进行开发工作时能很方便的通过 UI 界面使用这些功能,例如大家熟悉的 project 工程目录,Gradle 工具栏,IDE 底部的 Run、Terminal、Build 界面等,都是通过 IDE 插件来实现的,可以说大部分需要通过命令行执行、或用户手动的一些操作都可以通过插件实现,并以UI的形式呈现。
如下图:左图为 Android Studio IDE 界面右侧 Gradle 工具栏,包含了很多 Gradle 任务,点击 UI 的效果等同于用户在命令行中输入 Gradle 命令。右图为 IDE 顶部菜单栏版本控制部分,其中对于版本的提交、拉取等按钮等价于命令行输入对应指令。
IntelliJ IDEA。
(下载链接:
https://www.jetbrains.com/idea/download/#section=mac
)
intellij {
version '2020.1.4'
localPath '/Applications/Android Studio.app/Contents'
plugins = ['Kotlin','android','git4idea']
}
声明我们的插件需要并且和AS相兼容:增加 android 和 android studio modules 作
为依赖。
<idea-plugin>
...
<depends>com.intellij.modules.platform</depends>
<depends>org.jetbrains.android</depends>
<depends>com.intellij.modules.androidstudio</depends>
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
</extensions>
<actions>
<!-- Add your actions here -->
</actions>
</idea-plugin>
项目名称/Tasks/intelliJ/runIde
路径。运行runIde
任务,因为我们配置了 Android Studio 为启动路径,所以一个 Android Studio 模拟 IDE 会打开,所有内容都和我们本地的 Android Studio 没有差别。
Actions官方介绍: The system of actions allows plugins to add their own items to IDEA menus and toolbars. An action is a class, derived from the AnAction.
Actions是用户调用插件功能最常见的方式,如下图的工具目录是开发者经常用到的,里面所有的可选项都是一个Action,可以进一步展开的则是Action Group。
【code implementation - 实现 Action 的具体代码逻辑】:决定了这个 action在哪个 context 下有效,并且在 UI 中被选择后的功能(继承父类 AnAction 并重写 actionPerformed() 方法,用于 Action 被执行后的回调)。
【registered - 在配置文件中注册】:决定了这个 action 在 IDE 界面的哪个位置出现(创建新的 group 或存放进现有的 ActionGroup,以及在 group 中的位置)。
class HelloWorldAction : AnAction() {
override fun actionPerformed(event: AnActionEvent) {
//这里创建了一个消息提示弹窗,在IDE中展示“Hello World”
val notificationGroup = NotificationGroup(
displayId = "myActionId",
displayType = NotificationDisplayType.BALLOON
)
val notification = notificationGroup.createNotification(
title = "chentao Demo",
content = "Hello World",
type = NotificationType.INFORMATION
).notify(event.project) //从方法的Event对象中获取到当前IDE正在展示的project,在该project中展示弹窗
}
}
<actions>
<!-- Add your actions here -->
<!-- 创建了一个ActionGroup -->
<group id = "ChentaoDemo.TopMenu"
text="ChentaoDemo Plugin"
description="Demo Plugin in top menu">
<!-- 注册HelloWorld Action -->
<action class="com.chentao.demo.actions.HelloWorldAction"
id="DemoAction"
text="Hello World Action"
description="This is a test action">
<!-- 设置 HelloWorld Action 的键盘快捷键-->
<keyboard-shortcut first-keystroke="control alt p" keymap="$default"/>
<!-- 将HelloWorld Action添加到剪切拷贝组中 -->
<add-to-group group-id="CutCopyPasteGroup" anchor="last"/>
</action>
<!-- 将这个Group添加到主菜单 -->
<add-to-group group-id="MainMenu" anchor="last"/>
</group>
</actions>
runIde
Task,顶部的主菜单栏末尾出现了我们添加的ActionGroup,展开可看见 HelloWorldAction,点击 Action,右下角弹出 “Hello World” 提示信息。我们不仅可以创建 Group 来放置 Action,还可以将Action添加进 IDE 已有的 Group 当中,如下左图中,我们将 HelloWorld Action 添加进了 IDE 的CutCopyPasteGroup,和复制粘贴等 Action 放在了一起。
Wizard 意为向导程序,就是指引使用者完成某个功能的程序,通常为单个或多个指引界面组成。例如下面两幅图为 Android Studio 中经典的创建新工程窗口,就包含两个页面的向导程序。下面将介绍如何制作出和图中主题完全相同的向导程序。
class CreateNewProjectAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
StudioWizardDialogBuilder(
ModelWizard.Builder().addStep(NewProjectStep()).build(),
"Create New MARS Project"
).build().show()
}
}
class ProjectWizardModel : WizardModel() {
//记录一些希望保存的字段
//...
override fun handleFinished() {
//处理最后的逻辑
}
}
class NewProjectStep : ModelWizardStep<ProjectWizardModel?>(ProjectWizardModel(), "Create MARS Project") {
init {
//创建Step页面的UI
}
//链接下一个Step
override fun createDependentSteps(): MutableCollection<out ModelWizardStep<*>> {
return arrayListOf(SelectBaselineStep(model))
}
}
Performs lazy initialization of a tool window registered in {@code plugin.xml}.
public class MyToolWindowFactory implements ToolWindowFactory {
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
// 初始化自定义组件对象
MyToolWindow myToolWindow = new MyToolWindow(project, toolWindow);
// 组件添加到AS中
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(myToolWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
}
}
declarative setup:可以理解为静态,在plugin.xml
文件中注册,始终可见用户随时都可以使用。
Programmatic Setup:通过 API 接口动态注入,可以在一些操作前后出现和隐藏。
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<toolWindow id="MyToolWindow" secondary="true" anchor="right" factoryClass="com.volcengine.plugin.toolwindow.MyToolWindowFactory"/>
</extensions>
updateBaselineBtn.addActionListener(e -> {
BaselineWindow baselineWindow = new BaselineWindow(versionsJson, project, toolWindow);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(baselineWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
toolWindow.getContentManager().setSelectedContent(content);
});
Service 接口类型 | 作用描述 |
---|---|
Application Level | IDEA 启动时会初始化,IDEA 生命周期中仅存在一个实例 |
Project Level | IDEA 会为每一个 Project 实例创建一个 Project 级别的实例 |
Module Level | IDEA 会为每一个 Project 的加载过的 Module 实例 Module 级别的实例,在多模块项目中容易导致内存泄露 |
这块代码是模拟在IDE启动时,自动检验当前是否存在新版本的功能,若有新版本则进行更新操作,就是使用了持久化存储来实现的。
@State(name = "DemoConfiguration", storages = [
Storage(value = "demoConfiguration.xml")
])
class DemoComoponent:ApplicationComponent, PersistentStateComponent<DemoComoponent>, Serializable {
var version = 1
var localVersion = 0;
private fun isANerVersion() = localVersion < version
private fun updateVersion(){
localVersion = version
}
override fun initComponent() {
if(isANerVersion()){
updateVersion()
}
}
override fun getState(): DemoComoponent? = this
override fun loadState(state: DemoComoponent) {
XmlSerializerUtil.copyBean(state, this)
}
}
//获取 application 级别的 PropertiesComponent
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
//获取 project 级别的 PropertiesComponent,指定相应的 project
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(Project);
// set & get
propertiesComponent.setValue(name, value)
propertiesComponent.getValue(name)
public interface PersistentStateComponent<T> {
@Nullable
T getState();
void loadState(T state);
}
创建一个PersistentStateComponent的实现类,T表示需要持久化的数据结构类型,可以是任意类,甚至是实现类本身,然后重写getState和loadState方法。
若要指定存储的位置,需要在显现类上增加*@State*注解。
若不希望其中的某个字段被持久化,可以在该字段上增加*@Transient* 注解。
@State(
name = "ChentaoPlugin" ,
storages = [Storage("chentao-plugin.xml")]
)
class AarCheckBoxSettings :PersistentStateComponent<HashMap<String, AarCheckBoxState>> {
var checkBoxStateList = HashMap<String, AarCheckBoxState>()
override fun getState(): HashMap<String, AarCheckBoxState>? {
return checkBoxStateList
}
override fun loadState(stateList: HashMap<String, AarCheckBoxState>) {
checkBoxStateList = stateList
}
//将持久化组件声明为Serveice的获取方式是通过ServiceManager
companion object{
@JvmStatic
fun getInstance(): PersistentStateComponent<HashMap<String, AarCheckBoxState>>{
return ServiceManager.getService(AarCheckBoxSettings::class.java)
}
}
}
data class AarCheckBoxState(val componentId:String, val isSelected:Boolean)
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.volcengine.plugin.actions.AarCheckBoxSettings"/>
</extensions>
二、插件打包与安装
打包:在 Gradle 工具栏中运行 assemble 任务,即可在 /build/distribution/ {插件名称}-{插件版本}.zip 路径下找到打包好的插件 zip 包。
本地安装:还没将插件发布到插件市场前我们可以选择安装本地插件,打开 AS菜单栏 /Android Studio/Preference/Plugins/Install Plugin from Disk... 安装后即可使用。
发布插件市场:
访问 https://hub.jetbrains.com/ ,创建账号。
使用账号登陆 jetbrains marketplace https://plugins.jetbrains.com/,发布插件(需官方审核 2 个工作日)。
插件的第一个版本都需要在网站手动上传,之后的版本可以使用 hub 账号中的 token 自动更新。
字节跳动应用开发套件MARS是字节跳动终端技术团队过去九年在抖音、今日头条、西瓜视频、飞书、懂车帝等 App 的研发实践成果,面向移动研发、前端开发、QA、 运维、产品经理、项目经理以及运营角色,提供一站式整体研发解决方案,助力企业研发模式升级,降低企业研发综合成本。
可扫码添加小助手微信,了解更多详情