我们在使用Android Studio开发的时候都会使用一些插件,来方便我们的开发工作,提升工作效率。IntelliJ IDEA 可能有的人已经很熟悉,强大的开源IDE。Android Studio 就是基于IDEA社区版开发的。下载安装后打开软件我们就会发现,IntelliJ IDEA的界面、菜单、状态栏以及Preference 和 安卓开发ide android studio 几乎是一模一样的。
IntelliJ IDEA 简称 IDEA,是 Jetbrains 公司旗下的一款 JAVA 开发工具,支持 Java、Scala、Groovy、kotlin 等语言的开发,同时具备支持目前主流的技术和框架,擅长于企业应用、移动应用和 Web 应用的开发,提供了丰富的功能,智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等。
在介绍IDEA插件开发流程之前,先了解下idea插件有哪些类型及其作用。
从官方文档可以看出,idea开发的插件最常见的有5种类型。分别是
UI主题使设计人员能够自定义内置IDE UI元素的外观。自定义UI主题可以:
可供下载的UI主题 说明创造的可能性。
自定义语言支持提供了使用特定编程语言的基本功能。包括:
详情点击:自定义语言支持插件开发
框架集成由改进的代码洞察特性组成,这些特性是给定框架的典型特征,以及直接从IDE使用特定于框架的功能的选项。有时它还包括自定义语法或DSL的语言支持元素
参考案例: Struts 2 plugin开发
工具集成使直接从IDE操作第三方工具和组件成为可能,而无需切换上下文
这就意味着:
参考案例: Gerrit integration
此类别中的插件对IDE的标准用户界面应用各种更改. 一些新添加的组件是交互式的,并且提供了新的功能,而另一些则仅限于可视化修改.
IDEA会自动装配IDEA版本的maven,所以只需要配置settings.xml
就可以实现变更国内镜像。
IDEA插件有两种开发方式:1、DevKit方式 2、Gradle方式
Plugin DevKit_是一个捆绑的IntelliJ IDEA插件,用于使用IntelliJ IDEA为IntelliJ平台开发插件自己的构建系统. 它提供了自定义SDK类型和一组用于在IDE中构建插件的操作.早期的插件项目都是DevKit方式。
DevKit的plugin项目,项目工程的配置文件是imi文件。
Sandbox
IntelliJ IDEA 插件以 Debug/Run 模式运行时是在 SandBox 中进行的,不会影响当前的 IntelliJ IDEA;但是同一台机器同时开发多个插件时默认使用的同一个 sandbox,即在创建 IntelliJ Platform SDK 时默认指定的 Sandbox Home.
如果需要每个插件的开发环境是相互独立的,可以创建多个 IntelliJ Platform SDK,为 Sandbox Home 指定不同的目录 。
Run/Debug
新建或修改运行/调试配置,选择使用的module后,可直接点击图标运行或调试。
Prepare Plugin For Deployment
如果项目中没有任何依赖,打出来的插件包是jar包
如果项目中有依赖其他插件或Library,则打出来的插件包是zip文件
插件的配置文件是plugin.xml,是整个插件的核心,见Gradle方式中的plugin.xml说明。
Gradle插件是构建IntelliJ插件的推荐解决方案.该插件负责处理插件项目的依赖性 - 基本IDE和插件可能依赖的其他插件. 它还提供了使用插件运行IDE并将插件发布到JetBrains插件存储库的任务.对于新项目,官方推荐Gradle方式。
Gradle的plugin项目,项目工程的配置文件是build.gradle文件
gradle插件源码及版本信息
兼容性
如果依赖的插件版本和intellij版本不兼容,编译则会报如下图错误
关于兼容性,官方也作了说明
例如koltin插件,可以去JetBrains Plugins Repository查询各版本及其兼容的IDE版本
可以根据项目需要添加更多的配置
plugin.xml是整个项目的核心配置文件,一些Action、Extension组件需要在这里先进行注册和声明,和Android 开发的AndroidManifest.xml注册组件类似。
MyPlugin
com.example.plugin.myplugin
my plugin description
Initial release of the plugin.
1.0
MyFirstPlugin
com.jetbrains.php
com.intellij.modules.platform
com.foo.Component1Interface
com.foo.impl.Component1Impl
com.foo.Component2
com.foo.Component3
...
...
...
Action 用于描述一个动作、行为,可以通过快捷键、点选的方式进行触发。一个 Action 是一个 class,是 AnAction 的子类,actionPerformed 方法在菜单Item或者标题栏按钮被选中的时候会被调用。
Action 允许添加到右键菜单或者Toolbar菜单上面。Action也可以成组添加到具体的一个Group下面。
继承AnAction,并重写actionPerformed方法,此方法是action触发执行的入口。
插件组件是插件集成的基础概念,我们插件定义的每一个组件(component) 应该在配置文件中配置相关的接口(可选)和实现类。有三种组件:
Components 接口类型 | 描述 |
Application Component | IDEA启动时会初始化,IDEA生命周期中仅存在一个实例。 |
Project Component | IDEA 会为每一个 Project 实例创建一个 Project 级别的component |
Module Component | IDEA 会为每一个 Project 的加载过的Module实例Module级别的component |
Application Component
public class TestApplication implements ApplicationComponent {
public TestApplication() {
}
@Override
public void initComponent() {
// TODO: insert component initialization logic here
}
@Override
public void disposeComponent() {
// TODO: insert component disposal logic here
}
@Override
@NotNull
public String getComponentName() {
return "TestApplication";
}
}
Project Component
public class TestProject implements ProjectComponent {
public TestProject(Project project) {
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
@Override
@NotNull
public String getComponentName() {
return "TestProject";
}
@Override
public void projectOpened() {
// called when project is opened
}
@Override
public void projectClosed() {
// called when project is being closed
}
}
Module Component
public class TestModule implements ModuleComponent {
public TestModule(Module module) {
}
@Override
public void initComponent() {
// TODO: insert component initialization logic here
}
@Override
public void disposeComponent() {
// TODO: insert component disposal logic here
}
@Override
@NotNull
public String getComponentName() {
return "TestModule";
}
@Override
public void moduleAdded() {
// Invoked when the module corresponding to this component instance has been completely
// loaded and added to the project.
}
}
components 需要配置在 plugin.xml 中,并指定 interface 和 implementation,interface 类用于从其他组件中检索组件,implementation 类用于实例化组件。
Application Component
com.example.test.TestApplication
Project Component
com.example.test.TestProject
Module Component
com.example.test.TestModule
注意:
getComponentName()
返回,推荐使用 .
格式。获取 Application Component
通过 ApplicationManager获取
//获取application容器中的组件
TestApplication testApplication = ApplicationManager.getApplication().getComponent(TestApplication.class);
获取project
通过action获取
DataContext dataContext = anActionEvent.getDataContext();
// DataConstants 被标记为 @deprecated
mProject = (Project)dataContext.getData(DataConstants.PROJECT);
Project project = anActionEvent.getData(PlatformDataKeys.PROJECT);
通过PsiElement获取
/**
* The common base interface for all elements of the PSI tree.
*
* Please see IntelliJ Platform Architectural Overview
* for high-level overview.
*/
public interface PsiElement extends UserDataHolder, Iconable {
/**
* The empty array of PSI elements which can be reused to avoid unnecessary allocations.
*/
PsiElement[] EMPTY_ARRAY = new PsiElement[0];
ArrayFactory ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new PsiElement[count];
/**
* Returns the project to which the PSI element belongs.
*
* @return the project instance.
* @throws PsiInvalidElementAccessException if this element is invalid
*/
@NotNull
@Contract(pure=true)
Project getProject() throws PsiInvalidElementAccessException;
}
获取module
通过project获取
TestModule testModule = project.getComponent(TestModule.class);
通过action获取
DataContext dataContext = anActionEvent.getDataContext();
// DataConstants 被标记为 @deprecated
mModule =(Module)dataContext.getData(DataConstants.MODULE);
Module module = anActionEvent.getData(LangDataKeys.MODULE);
@Deprecated
@SuppressWarnings({"JavadocReference"})
public interface DataConstants {
/**
* Returns {@link com.intellij.openapi.project.Project}
*
* @deprecated use {@link PlatformDataKeys#PROJECT} instead
*/
@Deprecated String PROJECT = CommonDataKeys.PROJECT.getName();
/**
* Returns {@link com.intellij.openapi.module.Module}
*
* @deprecated use {@link com.intellij.openapi.actionSystem.LangDataKeys#MODULE} instead
*/
@Deprecated @NonNls String MODULE = "module";
}
Component加载
Application 级别的 components 在 IDEA 启动时加载,Project 和 Module 级别的 components 在项目启动时共同加载。
一个组件加载过程:
initComponent()
方法projectOpened()
方法; 如果是 Module 组件,会依次调用 moduleAdded()
和 projectOpened()
方法。ModuleComponent 的生命周期方法中比 ProjectComponent 多一个 moduleAdded()
,用于通知 module 已经被添加到 project 中。
如果 component 在加载时需要用到其他 component,我们只需在该 component 的构造方法的参数列表声明即可,在这种情况下,IntelliJ IDEA 会按正确的顺序实例化所依赖的 component。
示例:
public class MyComponent implements ApplicationComponent {
private final MyOtherComponent otherComponent;
public MyComponent(MyOtherComponent otherComponent) {
this.otherComponent = otherComponent;
}
...
}
Component 卸载
一个组件卸载过程:
projectClosed()
disposeComponent()
将被调用对于IDEA插件的一些配置,一般情况下都不会希望用户每次使用插件时都要配置一遍,所以 IntelliJ Platform 提供了一些 API,来做数据的持久化。
对于简单的 key - value 数据结构,可以使用 PropertiesComponent,用于保存 application 和 project 级别的数据。用法如下:
//获取 application 级别的 PropertiesComponent
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
//获取 project 级别的 PropertiesComponent,指定相应的 project
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(Project);
// set & get
propertiesComponent.setValue(name, value)
propertiesComponent.getValue(name)
所有的 PropertiesComponent设置的键值对共用同一个namespance,所以需要避免key冲突。
对于复杂的数据结构,可以使用 PersistentStateComponent,PersistentStateComponent 可以指定持久化的存储位置。
@State(name = "PersistentStateComponentImpl",
storages = {
@Storage(value = "PersistentStateComponentImpl.xml")
})
class PersistentStateComponentImpl implements PersistentStateComponent {
State myState;
// 当组件被创建或 xml 文件被外部改变(比如git更新)时被调用
public State getState() {
return myState;
}
// 当 settings 被保存时,该方法会被调用并保存状态值。
public void loadState(State state) {
myState = state;
}
}
class State {
public State() {}
// 支持基本的数据类型、Map、Collection、enum
public String value;
@Transient
public String disableSave;
}
持久化组件可以声明为 Service,也可以声明为 Component,声明为 Component 则与前面介绍注册与获取的方法一致,声明为Service如下:
获取方式为:
Intellij 平台提供了允许一个插件与其他插件或者 IDE 交互的 extensions 以及 extension points 的概念。
如果你想要你的插件扩展其他插件或者 Intellij 平台,你必须声明一个或多个 extensions
如果你想要你的插件可以被其他插件使用,那么你必须在你的插件内声明一个或多个扩展点(extension points)。每个扩展点定义了允许访问这个点的类或者接口。
你可以在你的 plugin.xml 中的
GUI 是 IntelliJ IDEA 提供的一个自动生成 java 布局代码的工具,它使用 JDK 中的 Swing 控件来实现 UI 界面。
面板编辑完成之后,点击Toolbar工具条那里的编译按钮。编译完成后,GUI的代码会生成在 对应的 Java文件里面。
Gradle task 和 android studio的 类似,常用的也是打包、调试和发布task,只不过task 名不同。
更多说明和示例可见JetBrains/gradle-intellij-plugin
buildPlugin 构建插件,常说的打包 插件包默认输出目录为build/distributions/插件名.zip
runIde 调试运行
现在双击runIde即可调出另外一个安装了这个插件的IDEA界面,然后可以看运行结果进行调试。 runIde还支持debug模式,不过运行时要右击选择:
如果setIdeDirectory设置为Android studio路径,则调试过程如下,会重新启动一个main(Android studio)
publishPlugin 发布插件
上传插件市场
1、官方插件市场
官方文档:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html
2、自定义个人/公司插件存储服务器
如果要安装的插件非JetBrains官方插件市场发布的插件,则需要配置插件存储服务器地址,才能在Marketplace搜索到然后install。
搜索但还未安装时,右侧会显示updatePlugins.xml文件中配置的插件信息;从硬盘选择安装或已安装的插件,会显示plugin.xml文件中配置的插件信息。
到这里,一个IDE插件的的开发、配置、调试运行、发布、安装的基本流程就介绍完毕了。插件的应用非常广泛,可以应用所有基于IntelliJ Platform开发的IDE软件。如果想深入学习IDE插件开发,建议阅读官方文档。官方文档内容更加全面丰富,但是由于文档精炼、简洁,所以在学习过程中,很多地方需要我们去揣摩、整理和总结。