作者简介
王昭霞,软件开发工程师,先后从事脚本工具编写、工具开发、Android基础模块开发等工作。
大家在使用Android Studio开发的时候都会使用一些插件,来方便我们的开发工作,提升工作效率。在进行手机京东Android客户端瘦身工作时,我们将压缩图片的相关功能封装成了 IDEA 插件:ImgOptimi 图片优化工具(参考链接http://sdk.av.jd.com/share/ImgOptimi/img-optimi.html)。这里总结一下 Intellij IDEA 插件开发的知识,供大家参考。
本篇文章包含以下内容:
IntelliJ IDEA 简称 IDEA,是 Jetbrains 公司旗下的一款 JAVA 开发工具,支持 Java、Scala、Groovy 等语言的开发,同时具备支持目前主流的技术和框架,擅长于企业应用、移动应用和 Web 应用的开发,提供了丰富的功能,智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等。
IntelliJ Platform 是一个构建 IDE 的开源平台,基于它构建的 IDE 有 IntelliJ IDEA、WebStorm、DataGrip、以及 Android Studio 等等。IDEA 插件也是基于 IntelliJ Platform 开发的。
本章节介绍 IDEA 插件开发环境的搭建与配置
开发工具使用 Intellij IDEA,下载地址:https://www.jetbrains.com/idea/
IDEA 分为两个版本:
开发IDEA的插件推荐使用社区版,因为社区版是开源的,在开发插件的时候,可以调试源代码。
Plugin DevKit 是 IntelliJ 的一个插件,它使用 IntelliJ IDEA 自己的构建系统来为开发 IDEA 插件提供支持。开发 IDEA 插件之前需要安装并启用 Plugin DevKit 。
打开 IDEA,导航到 Settings | Plugins,若插件列表中没有 Plugin DevKit,点击 Install JetBrains plugin,搜索并安装。
IntelliJ Platform Plugin SDK 就是开发 IntelliJ 平台插件的SDK, 是基于 JDK 之上运行的,类似于开发 Android 应用需要 Android SDK。
IntelliJ IDEA 插件以 Debug/Run 模式运行时是在 SandBox 中进行的,不会影响当前的 IntelliJ IDEA;但是同一台机器同时开发多个插件时默认使用的同一个 sandbox,即在创建 IntelliJ Platform SDK 时默认指定的 Sandbox Home
如果需要每个插件的开发环境是相互独立的,可以创建多个 IntelliJ Platform SDK,为 Sandbox Home 指定不同的目录 。
本篇介绍插件的创建、配置、运行、打包流程,以及 action
选择 File | New | Project,左侧栏中选择 IntelliJ Platform Plugin 工程类型
点击 Next,设置工程名称及位置,点击 Finish 完成创建。可以到 File | Project Structure 来自定义工程设置。
插件工程内容:
PluginDemo/ resources/ META-INF/ plugin.xml src/ com/foo/... ... ...
下面示例描述了可在 plugin.xml 文件配置的主要元素:
MyPlugin com.example.plugin.myplugin my plugin description Initial release of the plugin. 1.0 MyFirstPlugin com.foo.Component1Interface com.foo.impl.Component1Impl com.foo.Component2 com.foo.Component3 ... ... ...
一个 Action 表示 IDEA 菜单里的一个 menu item 或工具栏上的一个按钮,通过继承 AnAction
class 实现,当选择一个 menu item 或点击工具栏上的按钮时,就会调用 AnAction 类的 actionPerformed
方法。
实现自定义 Action 分两步:
1、定义 Action
定义一个 Java class,继承 AnAction
类,并重写 actionPerformed
方法, 如
public class TextBoxes extends AnAction { public void actionPerformed(AnActionEvent event) { Project project = event.getData(PlatformDataKeys.PROJECT); Messages.showInputDialog( project, "What is your name?", "Input your name", Messages.getQuestionIcon()); } }
2、注册 Action
在 plugin.xml 文件的
元素内注册
元素会定义一个 action,指定 action 的 id、实现类、显示文本、描述
元素会定义一个 action group(多个action),设置 action group 的 id、文本、描述
元素指定其外部 action 或 action group 被添加到的位置上面示例会定义一个被添加到 IDEA 主菜单的最后面的 “SampleMenu” 的菜单,点击该菜单将弹出一个 “Text Boxes” item,如图
点击该 “Text Boxes” item,弹出一个提示输入名字的对话框
更多 action 信息请移步 IntelliJ Platform Action System(http://www.jetbrains.org/intellij/sdk/docs/basics/action_system.html)
3、快速创建 Action
IntelliJ Platform 提供了 New Action 向导,它会帮助我们创建 action class 并配置 plugin.xml 文件:
在目标 package 上右键,选择 New | Plugin DevKit | Action:
在弹出的对话框中填充下列字段,然后点击 OK:
PluginName.ID
注意:该向导只能向主菜单中已存在的 action group 或工具栏上添加 action,若要创建新的 action group,请参考前面的内容。
运行/调试插件可直接在 IntelliJ IDEA 进行,选择 Run | Edit Configurations...,若左侧栏没有 Plugin 类型的 Configuration, 点击右上角 + 按钮,选择 Plugin 类型, 如图
Use classpath of module 选择要调试的 module,其余配置一般默认即可;切换到 Logs 选项卡,如果勾选了 idea.log,运行插件时 idea.log 文件的内容将输出到 idea.log console。
运行插件点击工具栏上
按钮即可,IntelliJ IDEA 会另启一个装有该插件的 IDEA 窗口
1、打包插件
选择 Build | Prepare Plugin Module ‘module name’ for Deployment 来打包插件:
插件包位置:一般在工程根目录下
如果插件没有依赖任何 library,插件会被打包成一个 .jar
,否则会被打包成一个 .zip
,zip 中包含了所有的插件依赖
jar类型的插件包:
PluginDemo.jar/ com/foo/... ... ... META-INF/ plugin.xml
zip类型的插件包:
PluginDemo.zip/ lib/ libfoo.jar libbar.jar PluginDemo.jar/ com/foo/... ... ... META-INF/ plugin.xml
2、安装插件
Install JetBrains plugin... 从 JetBrains 仓库(https://plugins.jetbrains.com/)中安装插件
Browse repositories... 添加并管理自己的仓库
IntelliJ IDEA 的组件模型是基于 PicoContainer(https://www.cnblogs.com/yaoxiaohui/archive/2009/03/08/1406228.html) 的,组件都包含在这些容器中,但容器有三种级别:application container,project container 以及 module container。application container 可以包含多个 project container,而 project container 可以包含多个 module container。
Components 是插件开发的基础,Components 有三种类型:
components 需要配置在 plugin.xml 中,并指定 interface 和 implementation,interface 类用于从其他组件中检索组件,implementation 类用于实例化组件。示例:
//创建一个 application level component public interface Component1 extends ApplicationComponent { } public class Component1Impl implements Component1 { @Override public String getComponentName() { return "PluginDemo.Component1"; } }
plugin.xml
com.example.test.Component1 com.example.test.Component1Impl
注意:
getComponentName()
返回,推荐使用 .
格式。ApplicationComponent 的生命周期方法:
//构造方法 public constructor(){ } //初始化 public void initComponent() { } public void disposeComponent() { }
ProjectComponent 的生命周期方法:
//构造方法 public constructor(){ } //通知一个project已经完成加载 public void projectOpened() { } public void projectClosed() { } //执行初始化操作以及与其他 components 的通信 public void initComponent() { } //释放系统资源或执行其他清理 public void disposeComponent() { }
ModuleComponent 的生命周期方法:
ModuleComponent 的生命周期方法中比 ProjectComponent 多一个 moduleAdded()
,用于通知 module 已经被添加到 project 中。
Application 级别的 components 在 IDEA 启动时加载,Project 和 Module 级别的 components 在项目启动时共同加载。
一个组件加载过程:
initComponent()
方法projectOpened()
方法; 如果是 Module 组件,会依次调用 moduleAdded()
和 projectOpened()
方法如果 component 在加载时需要用到其他 component,我们只需在该 component 的构造方法的参数列表声明即可,在这种情况下,IntelliJ IDEA 会按正确的顺序实例化所依赖的 component。
示例:
public class MyComponent implements ApplicationComponent { private final MyOtherComponent otherComponent; public MyComponent(MyOtherComponent otherComponent) { this.otherComponent = otherComponent; } ... }
一个组件卸载过程:
projectClosed()
disposeComponent()
将被调用前面我们提到有三种不同的容器,application container 实现 Application 接口; project container 实现 Project 接口;
module container 实现 Module 接口。每一个容器都有自己的方法去获取容器内的 component。
获取 application 容器及其内部的组件:
//获取application容器 Application application = ApplicationManager.getApplication(); //获取application容器中的组件 MyComponent myComponent = application.getComponent(MyComponent.class);
获取 project / module 容器及其内部的组件:
在 component 构造方法的参数列表中声明:
public class MyComponent implements ProjectComponent { Project project; public MyComponent(Project project){ this.project = project; } public void initComponent() { OtherComponent otherComponent = project.getComponent(OtherComponent.class); } }
在这个例子中,组件在构造方法中获取了容器对象,将其保存,然后在 component 其他地方进行引用。但是需要注意一点:
Be careful when passing this reference to other components (especially >application-level ones). If an application- level component does not release the reference, but saves it inside itself, >all the resources used by a project or module will not be unloaded from the memory on the project closing.
或者从action的事件处理的context的获取:
public class TextBoxes extends AnAction { @Override public void actionPerformed(AnActionEvent anActionEvent) { DataContext dataContext = e.getDataContext(); Project project = (Project)dataContext.getData(DataConstants.PROJECT); Module module =(Module)dataContext.getData(DataConstants.MODULE); } }
如果插件需要扩展 IDEA Platform 或 其他插件的功能,或为其他插件提供可以扩展自己的接口,那么就要用到 extensions 和 extension points,用于与 IDEA 和其他插件交互。
extension point 用于数据信息扩展,使其他插件可以扩展本插件的功能,可通过plugin.xml 的
元素声明,如下示例:
@Attribut
e
(https://upsource.jetbrains.com/idea-ce/file/idea-ce-d00d8b4ae3ed33097972b8a4286b336bf4ffcfab/xml/dom-openapi/src/com/intellij/util/xml/Attribute.java)注解的属性
指明类名或接口名。示例上述代码中的 MyExtensionPoint1 的 beanClass:
public class MyBeanClass extends AbstractExtensionPointBean { @Attribute("key") public String key; @Attribute("implementationClass") public String implementationClass; ... }
如果插件需要扩展 IntelliJ Platform 或其他插件的功能,需要声明一个或多个 extension。
的 defaultExtensionNs
属性 若是扩展 IntelliJ Platform,设为 com.intellij
若是扩展其他插件,则设为 pluginId
interface
声明的,那么使用 implementation
属性指明 interface 的实现类beanClass
声明的,那么就要为 beanClass 中被 @Attribute
注解的属性指定属性值示例:
插件的 service 的实现就是扩展 IDEA Platform 的 applicationService
或 projectService
两个 extension points
IntelliJ Platform 的 extension points:
其他插件的 extension points:可以从被扩展插件的 plugin.xml 文件中获取
Service 也是一种按需加载的 component,在调用 ServiceManager.getService(Class)
(https://upsource.jetbrains.com/idea-ce/file/idea-ce-d00d8b4ae3ed33097972b8a4286b336bf4ffcfab/platform/core-api/src/com/intellij/openapi/components/ServiceManager.java)时才会加载,且程序中只有一个实例。
Serivce 在 IntelliJ IDEA 中是以 extension point 形式提供的,实现自己的 service 需要扩展相应 extension point。
声明 service 时必须包含 serviceImplementation
属性用于实例化 service, serviceInterface
属性是可选的,可用于获取 service 实例。
在需要放置 service 的 package 上右键, New | Plugin DevKit | xxxxService,如图
选择相应 service,弹出如下对话框,填写 interface 类和 implementation 类,若不勾选 Separate interface from implementation,只需填写 implementation 类。
IntelliJ IDEA 会自动创建相应类并配置 plugin.xml 文件。
示例:
plugin.xml:
生成的 service 类:
package com.example.test.service; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; public interface MyProjectService { //获取 service 实例 static MyProjectService getInstance(@NotNull Project project) { return ServiceManager.getService(project, MyProjectService.class); } }
package com.example.test.service.impl; import com.example.test.service.MyProjectService; import com.intellij.openapi.project.Project; public class MyProjectServiceImpl implements MyProjectService { public MyProjectServiceImpl(Project project) { } }
MyApplicationService applicationService = ServiceManager.getService(MyApplicationService.class); //获取 project 级别的 service,需要提供 project 对象 MyProjectService projectService = ServiceManager.getService(project, MyProjectService.class); //获取 module 级别的 service,需要提供 module 对象 MyModuleService moduleService = ModuleServiceManager.getService(module, MyModuleService.class);
我们在使用 IDE 开始开发工作之前,总是要先在 settings 页面进行一些设置,且每次重新打开 IDE 后这些设置仍然保留着,那么这些设置是如何保存下来的呢?
IntelliJ Platform 提供了一些 API,可以使 components 或 services 在每次打开 IDE 时仍然使用之前的数据,即持久化其状态。
对于一些简单少量的值,我们可以使用 PropertiesComponent
,它可以保存 application 级别和 project 级别的值。
下面方法用于获取 PropertiesComponent 对象:
//获取 application 级别的 PropertiesComponent PropertiesComponent.getInstance() //获取 project 级别的 PropertiesComponent,指定相应的 project PropertiesComponent.getInstance(Project) propertiesComponent.setValue(name, value) propertiesComponent.getValue(name)
PropertiesComponent
保存的是键值对,由于所有插件使用的是同一个 namespace,强烈建议使用前缀来命名 name,比如使用 plugin id。
PersistentStateComponent(https://upsource.jetbrains.com/idea-ce/file/idea-ce-d00d8b4ae3ed33097972b8a4286b336bf4ffcfab/platform/projectModel-api/src/com/intellij/openapi/components/PersistentStateComponent.java) 用于持久化比较复杂的 components 或 services,可以指定需要持久化的值、值的格式以及存储位置。
要使用 PersistentStateComponent
持久化状态:
PersistentStateComponent
接口的实现类(component 或 service),指定类型参数,重写 getState() 和 loadState() 方法PersistentStateComponent
实现类本身。PersistentStateComponent
的实现类上,通过 @com.intellij.openapi.components.State
注解指定存储的位置下面通过两个例子进行说明:
class MyService implements PersistentStateComponent{ //这里 state 是一个 bean class static class State { public String value; ... } //用于保存当前的状态 State myState; // 从当前对象里获取状态 public State getState() { return myState; } // 从外部加载状态,设置给当前对象的相应字段 public void loadState(State state) { myState = state; } }
// 这里的 state 就是实现类本身 class MyService implements PersistentStateComponent{ public String stateValue; ... public MyService getState() { return this; } public void loadState(MyService state) { XmlSerializerUtil.copyBean(state, this); } }
1、实现 State 类
a、字段要求
state 类中可能有多个字段,但不是所有字段都可以被持久化,可以被持久化的字段:
这些字段也有类型要求:
如果不希望某个字段被持久化,可以使用 @com.intellij.util.xmlb.annotations.Transient
注解。
b、构造器要求
state 类必须有一个默认构造器,这个构造器返回的 state 对象被认为是默认状态,只有当当前状态与默认状态不同时,状态才会被持久化。
2、定义存储位置
我们可以使用 @State
注解来定义存储位置
@State(name = "PersistentDemo", storages = {@Storage(value = "PluginDemo.xml")}) public class PersistentDemo implements PersistentStateComponent{ ... }
name: 定义 xml 文件根标签的名称
storages: 一个或多个 @Storage
,定义存储的位置
~/IdeaICxxxx/system/plugins-sandbox/config/options
正式环境时 xml 文件的位置: ~/IdeaICxxxx/config/options
.idea/misc.xml
,若指定为 StoragePathMacros.WORKSPACE_FILE
,则会被保存在 .idea/worksapce.xml
3、生命周期
getState()
返回的状态与默认状态相同,那么什么都不会被保存。4、组件声明
持久化组件可以声明为 component,也可以声明为 service
声明为 service,plugin.xml 文件如下配置:
代码中获取状态与获取 service 的方式一样:
PersistentDemo persistDemo = ServiceManager.getService(PersistentDemo.class); PersistentDemo2 persistDemo2 = ServiceManager.getService(project,PersistentDemo.class);
声明为 component,plugin.xml 文件如下配置:
com.example.persistentdemo.PersistentComponent
获取状态与获取 component 的方式一样:
public static PersistentComponent getInstance() { return ApplicationManager.getApplication().getComponent(PersistentComponent.class); } public static PersistentComponent getInstance(Project project) { return project.getComponent(PersistentComponent.class); }
开发插件时可能会用到其他插件,可能是 IDEA 绑定的,也可能是第三方的插件。
配置插件依赖需要将插件包添加到 SDK 的 classpath 中,并在 plugin.xml 配置。
plugins/
或 plugins//lib
下。 如果插件是第三方或自己的,那么需要先运行一次 sandbox(其实我们在运行调试插件的时候就是在运行sandbox)并从本地或插件仓库安装依赖插件。 安装好后,插件包会放在 sandbox 目录下的 config/plugins/
或 config/plugins//lib
, 查看 sandbox 目录:打开 IntelliJ Platform SDK 配置页面,其中 Sandbox Home 就是其目录。
部分添加所依赖插件的id。org.jetbrains.kotlin
plugin id 可以从插件包的 plugin.xml 文件查看。
GUI 是 IntelliJ IDEA 提供的一个自动生成 java 布局代码的工具,它使用 JDK 中的 Swing 控件来实现 UI 界面。
使用步骤:
JPanel
,可将该 root 对象传给需要该布局的类。 注意:左下角的属性面板,只有当填写了 field name 属性时该控件的对象才会被当成成员变量,否则为局部变量。编译按钮,即可生成 java 的源码文件。 GUI 生成的方法名前后都有三个 $
标识,当再次修改布局时,GUI 只会修改 $
标识的方法。