小 M 自从上次发布了新版 IDEA 插件之后,Assistant 新颖的接入方式收到了一致好评,但是第一版的 Assistant 只有介绍和基础接入功能,老板希望小M多加一些功能,比如像 Firebase 接入的时候,有按钮显示接入状态,可以 apply 一些 Gradle 的插件等等功能。
Ⅰ 起源 —— Action
这些肯定难不倒小 M,我们只要参考下 Firebase 的集成就好了。其实我们大部分业务开发都是基于“事件”这个模型的,包括普通的 Web 后端和前端上的开发,IDEA 相关的内容也不例外,IDEA 响应的用户的动作一般都是 Action,比如点击菜单里的某一项,就是响应 AnAction 的操作。比如我们举个例子,在之前 Assistant 相关的 xml 中,有如下代码:
<action key="mpaas.integrate_dependencies" label="点击添加">
....
</action>
我们需要对这个 Action 做处理,根据之前 firebase 相关的集成方式,首先需要在 plugin.xml 中注册如下内容:
<extensions defaultExtensionNs="com.android.tools.idea.assistant">
<actionHandler implementation="com.alipay.mpaas.assistant.actions.MPIntegrateBaselineActionHandler" />
</extensions>
在这个类中,复写getId()
方法:
class MPIntegrateBaselineActionHandler: AssistActionHandler {
companion object {
const val ACTION_KEY = "mpaas.integrate_dependencies"
}
override fun getId() = ACTION_KEY
}
这样就和上面 xml 中的内容对应上了,它提供了一个 handlAction 方法,我们可以在这里写逻辑:
override fun handleAction(actionData: ActionData, project: Project) {
....
}
那这里就是小助手和我们代码连接的地方了,经过以上的说明,对于响应事件这件事,我们就已经做完了。
Ⅱ 操作 Gradle 文件
第二步,就是操作 Gradle 文件了。IDEA 插件里面其实没有内置 Gradle 引擎(显然也不合算),但是它通过 Psi 提供了对 Gradle DSL 解析的弱支持(不是完整支持)。同时提供了一些语义化的模型来简化这个问题。
我们注意到 Android Studio 提供了这么一个类 GradleBuildModel 在 Anroid Studio 3.6 中,它提供的接口如下:
看 android / buildscript / dependencies / ext 我们马上可以知道,这里对应我们 build.gradle 中几个 DSL 的 block,比如我们需要往 buildscript 中加入特定的 maven,用来拉取 sdk 或者 gradle 插件的话,那么按如下方式操作:
① 获取与修改 GradleBuildModel
GradleModelProvider.get().getBuildModel(project)
//or
GradleModelProvider.get().getBuildModel(module)
GradleBuildModel 一般是在 sync 完成之后,会被 AS 缓存起来,我们能以 O(1) 的效率去获取,然后我们这边还是以 buildscript 为例,它对应 BuildScriptModel,在 build.gradle 中的内容如下:
buildscript {
ext.mpaas_artifact = "mpaas-baseline"
ext.mpaas_baseline = "10.1.68-5"
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
}
}
那么我们往repositories
添加一个阿里云的 maven 仓库的方式就很简单了,我们调用RepositoriesModel
的addMavenRepositoryByUrl
方法。
repositoriesModel.addMavenRepositoryByUrl("https://maven.aliyun.com/repository/central")
② 回写 GradleBuildModel
这一步改完后,在 BuildModel 就暂存了刚刚的内容,我们还需要把这块内容回写回去:
WriteCommandAction.runWriteCommandAction(project, buildModel::applyChanges)
最终实现效果如下:
③ 添加复杂 Maven DSL 表达式
当然,上面视频里面展示的效果是添加一个比较复杂的 maven 配置,有 name / credential 等配置,因为 RepositoriesModel 只给我们提供了添加 url 一种方式,如果想实现以上的效果,我们需要使用反射的方式,操作 GradleDslBlockModel 的方式来进行。
val myDslElementField = GradleDslBlockModel::class.java.getDeclaredField("myDslElement")
myDslElementField.isAccessible = true
val myDslElement: GradlePropertiesDslElement = myDslElementField.get(repositoryModel) as GradlePropertiesDslElement
val nameElement = GradleNameElement.create("maven")
val mavenDslElement = MavenRepositoryDslElement(myDslElement, nameElement)
val mavenCredentialsDslElement = MavenCredentialsDslElement(mavenDslElement)
mavenCredentialsDslElement.setNewLiteral("username", "xxx")
mavenCredentialsDslElement.setNewLiteral("password", "xxx")
mavenDslElement.setNewLiteral("url", ALIPAY_MAVEN_URL)
mavenDslElement.setNewLiteral("name", "alipay")
mavenDslElement.addParsedElement(mavenCredentialsDslElement)
myDslElement.setNewElement(mavenDslElement)
完成以上的工作,我们就能通过 Action 的方式操作 Gradle 文件了。
Ⅲ 状态管理
点完这个按钮后,如果能有一个提示来告诉用户是否接入成功,这简直就太好啦:
这需要一个叫 ActionStateManager 的组件
<extensions defaultExtensionNs="com.android.tools.idea.assistant">
<actionStateManager implementation="com.alipay.mpaas.assistant.actions.MPIntegrateBaselineStateManager" />
</extensions>
我们同时也来写一个这样的组件,它继承于 AssistActionStateManager 里面有几个类需要复写
这里的 getId() 返回你刚刚注册 Action 的那个 id 即可,我这里就是 mpaas.integrate_dependencies 这里我们 getState() 支持返回的状态有这么几种可以选:
当然,你也可以根据提示自己新建一种状态,按照这个枚举的名字,和里面参数的定义,我们能知道每一种状态代表的含义。
因为这个类的生命周期没有相关的回调,如果我们需要这个类的实例,因此我们需要在 AS 调用 init 方法的时候,自己拿到它的实例存起来。