Android Studio Assistant 基础功能 —— Action

avatar
mPaaS 官方专栏号 @蚂蚁集团

小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 状态管理

点完这个按钮后,如果能有一个提示来告诉用户是否接入成功,这简直就太好啦:

image.png

这需要一个叫 ActionStateManager 的组件

<extensions defaultExtensionNs="com.android.tools.idea.assistant">
  <actionStateManager implementation="com.alipay.mpaas.assistant.actions.MPIntegrateBaselineStateManager" />
</extensions>

我们同时也来写一个这样的组件,它继承于 AssistActionStateManager 里面有几个类需要复写

image.png

这里的 getId() 返回你刚刚注册 Action 的那个 id 即可,我这里就是 mpaas.integrate_dependencies 这里我们 getState() 支持返回的状态有这么几种可以选:

image.png

当然,你也可以根据提示自己新建一种状态,按照这个枚举的名字,和里面参数的定义,我们能知道每一种状态代表的含义。

因为这个类的生命周期没有相关的回调,如果我们需要这个类的实例,因此我们需要在 AS 调用 init 方法的时候,自己拿到它的实例存起来。


------ END ------