多模块项目的生活并非如此简单。 为了避免创建新模块的例行程序,我们为Android Studio创建了自己的插件。 在实施过程中,我们遇到了缺乏实际文档的情况,尝试了几种方法并挖掘了许多陷阱。 结果是两篇文章:“理论”和“实践” 。 认识我!

我要说什么
- 为什么要使用插件? 为什么要使用插件?
- 插件开发基础
- IDEA内部:组件,PSI
为什么要使用插件? 为什么要使用插件?
如果您正在开发一个多模块Android项目,那么您就会知道每次创建一个新模块是什么样的例程。 您需要创建一个模块,在其中配置Gradle,添加依赖项,等待同步,不要忘记在应用程序模块中修复某些东西-这都需要很多时间。 我们希望使例程自动化,因此我们首先编写了每次创建新模块时要做的事情的清单。
1.首先,我们通过菜单File-> New-> New module-> Android library创建模块本身。

2.我们在settings.gradle文件中写入模块的路径,因为我们有几种类型的模块-核心模块和功能模块,它们位于不同的文件夹中。
3.在生成的build.gradle中更改常量compileSdk , minSdk和targetSdk :将其替换为在root build.gradle中定义的常量 。
注意:我们最近将工作的这一部分移到了Gradle插件中,该插件可帮助多行配置build.gradle文件的所有必需参数。
4.由于我们正在用Kotlin编写所有新代码,因此作为标准,我们连接了两个插件: kotlin-android和kotlin-kapt 。 如果该模块以某种方式与UI连接,我们还可以连接kotlin-android-extensions模块。
5.我们连接通用库和核心模块。 核心模块是例如记录器,分析程序,一些常规实用程序和库-RxJava,Moxy等。
6.为牙签设置kapt。 牙签是我们的核心DI框架。 您很可能知道:为了在发行版本中使用代码生成而不是反射,您需要配置注释处理器,以便其了解从何处获取所创建对象的工厂:
注意:在hh.ru中,我们使用第一个版本的Toothpick,在第二个版本中,我们删除了使用代码生成的功能 。
7.我们在创建的模块内为Moxy配置kapt。 Moxy是用于在应用程序中创建MVP的主要框架, 您需要对其 稍加改动 ,使其可以在多模块项目中工作。 特别是,在kapt参数中注册创建的模块的软件包:
注意:我们已经切换到新版本的Moxy ,并且代码生成的这一部分已经失去了意义。
8.我们生成了一堆新文件。 我的意思不是不是那些自动创建的文件(AndroidManifest.xml,build.gradle,.gitignore),而是新模块的常规框架:交互器,存储库,DI模块,演示者,片段。 这些文件很多,开始时它们具有相同的结构,创建它们是一个例程。

9.我们将创建的模块连接到应用程序模块。 在此步骤中,您必须记住在应用程序模块的build.gradle文件中配置Toothpick。 为此,我们将创建的模块的程序包添加到特殊的参数注释处理器-toothpick_registry_children_package_names中 。
之后,我们在应用程序模块中配置Moxy。 我们有一个标有@RegisterMoxyReflectorPackages批注的类-我们在其中添加已创建模块的包名称:
最后,不要忘记将创建的模块连接到应用程序模块的依赖关系块:
我们得到了九点清单。
由于有很多要点,很可能会忘记一些东西。 然后花数小时来思考发生了什么以及为什么项目不进行。
我们认为您不能过这种生活,您需要做出一些改变。
清单自动化选项
编制完清单后,我们开始寻找自动执行其项目的选项。
第一种选择是尝试执行“ Ctrl + C,Ctrl + V” 。 我们试图找到一种用于创建Android库模块的实现,该模块可“直接使用”。 在带有Android Studio的文件夹中(对于MacOs: / Applications / Android \ Studio.app/Contents/plugins/android/lib/templates/gradle-projects/ ),您可以找到一个特殊的文件夹,其中包含当您选择文件时看到的那些项目的模板- >新建->新建模块。 我们试图通过更改template.xml.ftl文件中的id来复制NewAndroidModule模板。 然后他们启动了IDE,开始创建一个新模块,然后... Android Studio崩溃了,因为您在菜单中看到的用于创建新模块的模块列表是硬编码的,因此无法使用原始的复制粘贴进行更改。 当您尝试添加,删除或更改元素时,Android Studio会崩溃。

自动执行清单的第二个选项是FreeMarker模板引擎 。 在尝试不成功的复制粘贴之后,我们决定仔细查看模块模板,并在幕后找到FreeMarker模板。
我不会告诉您FreeMarker的详细信息-RedMadRobot有一篇不错的文章,以及Lesha Bykov的 MosDroid的视频 。 但简而言之-这是一个使用模板和特殊的Map-ki Java对象生成文件的引擎。 您提供模板,对象,然后FreeMarker在输出中生成代码。
但是,请再次查看清单:

如果仔细观察,您会发现它分为两大组任务:
- 生成新代码(1、3、4、5、6、7、8)的任务,以及
- 修改现有代码的任务(2、7、8、9)
而且,如果FreeMarker能够应付第一组的任务,那么它根本就无法应付第二组的任务。 举一个小例子:在Android Studio中FreeMarker集成的当前实现中,当您尝试在settings.gradle文件中插入以'include'开头的行时, Studio将会崩溃 。 在这里,我们感到悲伤,并决定放弃使用FreeMarker。
FreeMarker失败后,想到了编写我自己的控制台实用程序来执行清单的想法。 在Intellij IDEA内部可以使用终端,为什么不呢? 让我们在bash上写一个脚本,总的业务:

但是,由于我们希望能够灵活地配置所创建的模块,因此我们将不得不输入许多不同的标志,这在控制台中打印将不太方便。
之后,我们退后一步,并想起我们正在Intellij IDEA内部工作。 以及如何安排? 有一定的类核心,引擎上附有许多插件,这些插件增加了我们所需的功能。
在屏幕快照中,有多少人看到了两个以上已连接的插件?

在这里,它们至少连接三个。 如果您使用Kotlin,则您已启用Kotlin插件。 如果您正在使用Gradle处理项目,则还包括Gradle插件。 如果您在使用版本控制系统(Git,SVN或其他版本)的项目中工作,则将包含用于集成此VCS的适当插件。
我们调查了官方的 JetBrains 插件存储库 ,结果发现已经有4000多个正式注册的插件! 几乎全世界都在写插件,这些插件可以做任何事情:从将编程语言集成到IDEA中开始,再到可以在IDEA中运行的特定工具结束。
简而言之,我们决定编写自己的插件。
插件开发基础
我们继续介绍插件开发的基础知识。 首先,您只需要三件事:
- IntelliJ IDEA ,最低社区版本(您可以使用Ultimate版本,但是在开发插件时不会带来特殊优势);
- 与之相连的插件DevKit是一个特殊的插件,它增加了编写其他插件的能力。
- 以及您要用来编写插件的任何JVM语言。 可能是Kotlin,Java,Groovy等等。
我们首先创建一个插件项目。 我们选择New project ,指向Gradle ,勾选IntelliJ Platform Plugin并创建项目。

注意:如果没有看到IntelliJ Platform Plugin复选框,则意味着您没有安装Plugin DevKit。
填写必填字段后,我们将看到一个空的插件结构。

让我们仔细看看。 它包括:
- 您将在其中编写未来项目代码的文件夹; ( main / java , main / kotlin等);
- build.gradle文件,您将在其中声明某些库上插件的依赖项,还将配置gradle-intellij-plugin这样的东西。
gradle-intellij-plugin -Gradle插件,可让您将Gradle用作插件构建系统。 之所以方便,是因为几乎每个Android开发人员都熟悉Gradle并且知道如何使用它。 另外,gradle-intellij-plugin向您的项目添加了有用的gradle任务,尤其是:
- runIde-此任务将使用您正在开发的插件启动一个单独的IDEA实例,以便您可以对其进行调试;
- buildPlugin-收集插件的zip存档,以便您可以在本地或通过官方IDEA存储库分发它;
- verifyPlugin-此任务检查您的插件是否存在严重错误,这些错误可能不允许其集成到Android Studio或某些其他IDEA中。
gradle-intellij-plugin还提供什么? 有了它的帮助,添加对其他插件的依赖关系变得更加容易,但是我们稍后再讨论,但是现在我可以说gradle-intellij-plugin是您的兄弟,请使用它。
回到插件结构。 任何插件中最重要的文件是plugin.xml 。
plugin.xml <idea-plugin> <id>com.experiment.simple.plugin</id> <name>Hello, world</name> <vendor email="myemail@yourcompany.com" url="http://www.mycompany.com"> My company </vendor> <description><![CDATA[ My first ever plugin - try to open Hello world dialog<br> ]]></description> <depends>com.intellij.modules.lang</depends> <depends>org.jetbrains.kotlin</depends> <depends>org.intellij.groovy</depends> <idea-version since-build="163"/> <actions> <group description="My actions" id="MyActionGroup" text="My actions"> <separator/> <action id="com.experiment.actions.OpenHelloWorldAction" class="com.experiment.actions.OpenHelloWorldAction" text="Show Hello world" description="Open dialog"> <add-to-group group-id="NewGroup" anchor="last"/> </action> </group> </actions> <idea-plugin>
该文件包含:
- 您的插件元数据:标识符,名称,描述,供应商信息,更改日志
- 对其他插件的依赖关系的描述;
- 在这里,您还可以指定插件可以正常使用的IDEA版本
- 动作也在这里描述。
动作
什么是动作 ? 假设您打开一个菜单来创建一个新文件。 实际上,此菜单的每个元素都是通过某种插件添加的:

操作是用户插件的入口点。 每次用户单击菜单项时,都可以在插件内部进行控制,您可以响应此单击并执行必要的操作。
如何创建动作? 让我们编写一个简单的动作,该动作将显示一个带有消息“ Hello,World”的对话框。
OpenHelloWorldAction class OpenHelloWorldAction : AnAction() { override fun actionPerformed(actionEvent: AnActionEvent) { val project = actionEvent.project Messages.showMessageDialog( project, "Hello world!", "Greeting", Messages.getInformationIcon() ) } override fun update(e: AnActionEvent) { super.update(e)
为了创建一个动作,我们首先创建一个从AnAction类继承的类。 其次,我们必须重新定义actionPerformed方法,其中AnActionEvent类的特殊参数来自 。 此参数包含有关您的操作的执行上下文的信息。 上下文是指您在其中工作的项目,现在在用户代码编辑器中打开的文件,在项目树中选择的元素以及其他有助于处理任务的数据。
为了显示“ Hello,world”对话框,我们首先获取项目(仅从AnActionEvent参数中获取),然后使用实用程序类Messages来显示对话框。
我们在Action中拥有哪些其他功能 ? 我们可以重写两个方法: update和beforeActionPerformedUpdate 。
每当您的Action的执行上下文更改时,都会调用update方法。 为什么对您有用:例如,更新插件添加的菜单项。 假设您编写了只能使用Kotlin文件的Action,并且用户现在已经打开了Groovy文件。 然后,在更新方法中,您可以使操作不可访问。
beforeActionPerformedUpdate方法类似于update方法,但是在actionPerformed之前被调用。 这是影响您行动的最后机会。 文档建议您不要在此方法中执行任何“繁重的操作”,以使其尽快运行。
您还可以将Actions绑定到IDEA界面的某些元素,并为它们设置默认的按键组合以进行调用-我建议在此处阅读更多内容。
插件中的UI开发
如果您需要自己的对话框设计,则必须努力工作。 我们开发UI是因为我们希望拥有一个方便的图形界面,在其中可以标记一些刻度线,并为枚举值提供选择器,等等。
用于开发UI的插件DevKit添加了一些操作,例如GUI form和Dialog 。 第一个为我们创建一个空表单,第二个为带有两个按钮的表单: Ok和Cancel 。

好的, 有一位表单设计师 ,但他是…一般。 相比之下,即使是Android Studio中的布局设计器,看起来也很舒适。 整个UI是在Java Swing之类的库上开发的。 该表单设计器生成人类可读的XML文件。 如果您无法在表单设计器中执行任何操作(例如:在同一个网格单元中插入多个控件并隐藏其中的一个控件,则全部隐藏),则需要转到此文件并进行更改-IDEA会选择这些更改。
几乎每种形式都由两个文件组成:第一个文件具有扩展名.form ,这只是XML文件,第二个是所谓的Bound类 ,可以用Java,Kotlin或任何您想要的语言编写。 它充当表单控制器。 出乎意料,但是用Java编写比使用其他语言容易得多。 例如,因为对Kotlin的调音还不是很完美。 在使用Java类添加新组件时,这些组件会自动添加到类中,并且在设计器中更改组件名称时,会自动将其拉出。 但是对于Kotlin,不添加任何组件-不会发生集成,您可能会忘记一些东西,但不了解为什么什么都不起作用。
我们总结基础知识
- 要创建插件,您将需要:IDEA社区版,插件DevKit和与其连接的Java。
- gradle-intellij-plugin是您的兄弟,它将大大简化您的生活,我建议您使用它。
- 除非必要,否则不要编写自己的UI。 IDEA中有许多实用程序类,它们使您可以立即创建自己的UI。 如果您需要复杂的东西,请准备好努力工作。
- 该插件可以具有任意数量的动作。 相同的插件可以为您的IDEA添加很多功能。
IDEA内部:组件,PSI
让我们讨论一下IDEA的肠道,以及它在内部的排列方式。 我告诉您,当我讲解实际部分时,您的脑海不会崩溃,以便您了解其来源。
IDEA如何安排? 在层次结构的第一层是一个类,例如Application 。 这是一个单独的IDEA实例。 对于每个IDEA实例,将创建一个Application类对象。 例如,如果您同时运行AppCode,Intellij IDEA,Android Studio,则将获得Application类的三个单独的实例。 此类旨在处理输入/输出流。
下一个级别是Project类。 这是与在IDEA中打开新项目时看到的最接近的概念。 通常需要Project才能在IDEA中获得其他组件:实用程序类,管理器等等。
下一个详细级别是Module类。 通常,模块是分组到一个文件夹中的类的层次结构。 但是这里的模块是指Maven模块,Gradle模块。 首先,需要使用此类来确定模块之间的依赖性,其次,要在这些模块内搜索类。
下一级别的细节是VirtualFile类。 这是磁盘上真实文件的抽象 。 多个VirtualFile实例可以对应于每个真实文件,但是它们都相等。 同时,如果删除了真实文件,则VirtualFile将不会自行删除,而只会变得无效。
诸如Document之类 的实体与每个VirtualFile相关联。 它是文件文本的抽象。 需要文档 ,以便您可以跟踪与文件文本更改相关的事件:用户插入行,删除行等,等等。
这个层次结构的另一端是Editor类-它是一个代码编辑器。 每个项目可以有一个编辑器 。 需要它以便跟踪与代码编辑器有关的事件:用户突出显示了插入符号所在的行,依此类推。
我想谈的最后一件事是PsiFile 。 这也是对真实文件的抽象,但是从表示代码元素的角度来看。 PSI代表程序结构接口。
每个程序由什么组成? 考虑一个普通的Java类。
普通的Java类 package com.experiment; import javax.inject.Inject; class SomeClass { @Inject String injectedString; public void someMethod() { System.out.println(injectedString); } }
它由指定包,导入,类,字段,方法,注释,关键字,数据类型,修饰符,标识符,方法引用,表达式和标记组成。 并且对于每个元素都有PsiElement的抽象。 也就是说,每个程序都由PsiElements组成。

而PsiFile是一种树结构,其中每个元素可以有一个父代和许多后代。

我想提一下,PSI不等于抽象语法树 。 抽象语法树是解析器通过您的程序后程序的表示树,并且它与任何编程语言都分离 。 相反,PSI与特定的编程语言相关。 当您使用Java类时,您正在处理Java PsiElement。 使用Groovy类时-使用Groovy PsiElements等等。 , PSI- - , , , – .
PSI – PSI- IDEA. , , , . .
IDEA
- PSI IDEA;
- PSI- IDEA, ;
- PsiElement-.
, . .
- ,
- Tool window
- IntelliJ IDEA
- IDEA
- IntelliJ IDEA
- , , . , .
- Droidcon Italy 2017 , , , , FreeMarker- . , .
- KotlinConf , Square SQLDelight, Java, Kotlin.