[译] 编写AndroidStudio插件(二):持久化数据

2,840 阅读5分钟

原文:Write an Android Studio Plugin Part 2: Persisting data
作者:Marcos Holgado
译者:却把清梅嗅
《编写AndroidStudio插件》系列是 IntelliJ IDEA 官方推荐的学习IDE插件开发的博客专栏,希望对有需要的读者有所帮助。

在本系列的第一部分中,我们了解了如何为Android Studio创建一个基本的插件,并编写了第一个Action。本文我们将了解如何在插件中对数据进行持久化。

请记住,您可以在GitHub上找到本系列的所有代码,还可以在对应的分支上查看每篇文章的相关代码,本文的代码在Part2分支中。

github.com/marcosholga…

我们要做什么?

我们今天的目标是在插件中对数据进行持久化。在此过程中,我们将学习什么是component以及如何使用component来管理插件的生命周期。接下来我们将开始使用它,以保证Android Studio启动并且安装了新版本的插件时显示通知。您将来可用此功能向用户显示您插件更新了哪些内容。

什么是 Component ?

在编写任何代码之前,我们需要了解什么是ComponentComponent是插件集成的基本概念,Component让我们能够控制插件的生命周期并保持其状态,以便将其自动保存和加载。

一共有三种不同类型的Component

  • Application 级别: 在IDE(Android Studio)启动时创建并初始化的Component;
  • Project 级别: 为每个项目实例创建的Component;
  • Module 级别: 为每个项目中的模块创建的Compoenent.

第一步:新建一个 Component

首先我们要确定所需Component的类型,在本文中,我们想在Android Studio启动时做一些事情,因此,通过查看不同类型的Component,我们可以清楚地知道需要一个 Application级别的Component

JetBrains官方文档这样描述:我们可以实现ApplicationComponent接口,这是可选的,但本文我们会这样做。ApplicationComponent接口将为我们提供之前提到的生命周期方法,便于我们在IDE启动时用于执行某些操作。这些方法来自ApplicationComponent的扩展类BaseComponent

public interface BaseComponent extends NamedComponent {
  /**
   * Component should perform initialization and communication with other components in this method.
   * This is called after {@link com.intellij.openapi.components.PersistentStateComponent#loadState(Object)}.
   */
  default void initComponent() {
  }

  /**
   * @see com.intellij.openapi.Disposable
   */
  default void disposeComponent() {
  }
}

现在让我们对Component进行编码,第一次迭代将非常简单,我们通过继承ApplicationComponent并重写initComponent来检查是否有新版本。

class MyComponent: ApplicationComponent {

    override fun initComponent() {
        super.initComponent()
        if (isANewVersion()) { }
    }

    private fun isANewVersion(): Boolean = true

}

第二步:实现 isANewVersion()

那么有意思的来了。我们将从声明两个新字段开始:localVersionversion。第一个将存储我们已安装的最新版本,而第二个将是我们插件的实际安装版本。

我想要比较它们,以检查版本号localVersion是否在version的后面,若如此我们就能知道用户是否刚刚安装了新版本的插件,以及是否向用户发送通知。我们还必须将localVersion更新为与version相同的值,以便下次用户启动Android Studio时不再收到欢迎信息。

首先,用户未安装我们的插件,因此我们将localVersion的值设置为0.0,因为我们的第一个版本是1.0-SNAPSHOT。您可以在build.gradle文件中更改插件的版本,但如果未更改,则应为:

version '1.0-SNAPSHOT'

为了实现isANewVersion,我们将做一些简单的事情。如果你愿意,可以适当进行修改,但是这里只是针对版本号进行简单的比较,因此我没有对边界条件相关逻辑进行全方位覆盖。

首先,我摆脱了实际上要在版本上维护的-SNAPSHOT部分。之后,我使用.作为分隔符对每个版本号进行了分割。因此我们得到一个包含所有主数字和副数字的List<String>,以便我们可以比较它们并返回truefalse。这并非最佳算法,但足以满足我们的需求。该算法的假设非常简单,两个版本号必须具有相同的长度,并且必须遵循相同的命名约定majorV.minorV

private fun isANewVersion(): Boolean {
    val s1 = localVersion.split("-")[0].split(".")
    val s2 = version.split("-")[0].split(".")

    if (s1.size != s2.size) return false
    var i = 0

    do {
        if (s1[i] < s2[i]) return true
        i++
    } while (i < s1.size && i < s2.size)

    return false
}

第三步:整合起来

现在是时候将isANewVersion方法以及两个新字段移到Component中了。您可以使用PluginManager获得插件的版本以及插件的ID,您还可以在plugin.xml文件中找到(和更改)插件的ID。我还建议此时将插件名称更改为更有意义的名称,例如My awesome plugin

<idea-plugin>
    <id>myplugin.myplugin</id>
    <name>My awesome plugin</name>
    ...

Component的整个实现非常简单:我们从PluginManager获取版本,检查是否发生了版本更新,如果有,就同步更新本地版本号,以便下次启动Android Studio时不会再触发整个过程。最后,我们向用户显示一个简单的通知,整合完毕后,看起来像这样。

class MyComponent: ApplicationComponent {

    private var localVersion: String = "0.0"
    private lateinit var version: String

    override fun initComponent() {
        super.initComponent()

        version = PluginManager.getPlugin(
            PluginId.getId("myplugin.myplugin")
        )!!.version

        if (isANewVersion()) {
            updateLocalVersion()
            val noti = NotificationGroup("myplugin",
                                         NotificationDisplayType.BALLOON,
                                         true)

            noti.createNotification("Plugin updated",
                                    "Welcome to the new version",
                                   NotificationType.INFORMATION,
                                   null)
                .notify(null)
        }
    }

    private fun isANewVersion(): Boolean {
        val s1 = localVersion.split("-")[0].split(".")
        val s2 = version.split("-")[0].split(".")

        if (s1.size != s2.size) return false
        var i = 0

        do {
            val l1 = s1[i]
            val l2 = s2[i]
            if (l1 < l2) return true
            i++
        } while (i < s1.size && i < s2.size)

        return false
    }

    private fun updateLocalVersion() {
        localVersion = version
    }

}

第四步:持久化 Component 的状态

现在,我们可以尝试测试我们的插件,由于两个原因,它无法正常工作。

首先是因为我们仍然必须注册Component,其次是因为我们还没有真正保留Component的状态。每次Android Studio初始化时,localVersion的值仍然是0.0

那么,针对如何保存Component状态的问题,我们该怎么做呢?为此,我们必须实现PersistentStateComponent。这意味着我们必须重写两个新方法,即getStateloadState。我们还需要了解PersistentStateComponent的工作方式, 它将公共字段,带注释的私有字段和bean属性存储为XML格式。要从序列化中删除公共字段,可以使用@Transient注释该字段。

在我们的例子中,我将使用@Attribute注释localVersion,以便在存储它的同时将其保持私有状态,将组件本身返回到getState并使用XmlSerializerUtil加载状态,我们还必须使用@State注释组件,并指定xml的位置。

@State(
        name = "MyConfiguration",
        storages = [Storage(value = "myConfiguration.xml")])
class MyComponent: ApplicationComponent,
        PersistentStateComponent<MyComponent> {

    @Attribute
    private var localVersion: String = "0.0"
    private lateinit var version: String

    override fun initComponent() {
        super.initComponent()

        version = PluginManager.getPlugin(
                PluginId.getId("myplugin.myplugin")
        )!!.version

        if (isANewVersion()) {
            updateLocalVersion()
            val noti = NotificationGroup("myplugin",
                                         NotificationDisplayType.BALLOON,
                                         true)
            noti.createNotification("Plugin updated",
                                    "Welcome to the new version",
                                    NotificationType.INFORMATION,
                                    null)
                .notify(null)
        }
    }

    override fun getState(): MyComponent? = this

    override fun loadState(state: MyComponent) = XmlSerializerUtil.copyBean(state, this)

    private fun isANewVersion(): Boolean {
        val s1 = localVersion.split("-")[0].split(".")
        val s2 = version.split("-")[0].split(".")

        if (s1.size != s2.size) return false
        var i = 0

        do {
            if (s1[i] < s2[i]) return true
            i++
        } while (i < s1.size && i < s2.size)

        return false
    }

    private fun updateLocalVersion() {
        localVersion = version
    }

}

像往常一样,您可以按需定制,例如定义存储位置或自定义xml格式,更多信息请参考这里

第五步:注册 Component

最后一步是在plugin.xml文件中注册Component。为此,我们只需要在文件的application-component部分内创建一个新Component,然后指定我们刚刚创建的Component类即可。

<application-components>
    <component>
        <implementation-class>
            components.MyComponent
        </implementation-class>
    </component>
</application-components>

大功告成!现在,您可以运行buildPlugin命令,使用Android Studio中生成的jar文件从磁盘安装插件,下次打开Android Studio时,您会看到此通知。之后,仅当您提高插件版本时,通知也会再次出现。

第2部分到此为止,在第3部分中,我们将学习如何为插件创建设置页面,当然在此之前我们还将了解如何为插件创建UI

如果您有任何疑问,请访问Twitter或发表评论。


《编写AndroidStudio插件》译文系列

关于译者

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub

如果您觉得文章还差了那么点东西,也请通过 关注 督促我写出更好的文章——万一哪天我进步了呢?