原文:Write an Android Studio Plugin Part 2: Persisting data
作者:Marcos Holgado
译者:却把清梅嗅
《编写AndroidStudio插件》系列是 IntelliJ IDEA 官方推荐的学习IDE插件开发的博客专栏,希望对有需要的读者有所帮助。
在本系列的第一部分中,我们了解了如何为Android Studio
创建一个基本的插件,并编写了第一个Action
。本文我们将了解如何在插件中对数据进行持久化。
请记住,您可以在GitHub
上找到本系列的所有代码,还可以在对应的分支上查看每篇文章的相关代码,本文的代码在Part2
分支中。
https://github.com/marcosholgado/plugin-medium
我们今天的目标是在插件中对数据进行持久化。在此过程中,我们将学习什么是component
以及如何使用component
来管理插件的生命周期。接下来我们将开始使用它,以保证Android Studio
启动并且安装了新版本的插件时显示通知。您将来可用此功能向用户显示您插件更新了哪些内容。
在编写任何代码之前,我们需要了解什么是Component
。 Component
是插件集成的基本概念,Component
让我们能够控制插件的生命周期并保持其状态,以便将其自动保存和加载。
一共有三种不同类型的Component
:
IDE(Android Studio)
启动时创建并初始化的Component
;Component
;Compoenent
.首先我们要确定所需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
}
那么有意思的来了。我们将从声明两个新字段开始:localVersion
和version
。第一个将存储我们已安装的最新版本,而第二个将是我们插件的实际安装版本。
我想要比较它们,以检查版本号localVersion
是否在version
的后面,若如此我们就能知道用户是否刚刚安装了新版本的插件,以及是否向用户发送通知。我们还必须将localVersion
更新为与version
相同的值,以便下次用户启动Android Studio
时不再收到欢迎信息。
首先,用户未安装我们的插件,因此我们将localVersion
的值设置为0.0
,因为我们的第一个版本是1.0-SNAPSHOT
。您可以在build.gradle
文件中更改插件的版本,但如果未更改,则应为:
version '1.0-SNAPSHOT'
为了实现isANewVersion
,我们将做一些简单的事情。如果你愿意,可以适当进行修改,但是这里只是针对版本号进行简单的比较,因此我没有对边界条件相关逻辑进行全方位覆盖。
首先,我摆脱了实际上要在版本上维护的-SNAPSHOT
部分。之后,我使用.
作为分隔符对每个版本号进行了分割。因此我们得到一个包含所有主数字和副数字的List
,以便我们可以比较它们并返回true
或false
。这并非最佳算法,但足以满足我们的需求。该算法的假设非常简单,两个版本号必须具有相同的长度,并且必须遵循相同的命名约定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.mypluginid>
<name>My awesome pluginname>
...
该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
的状态。每次Android Studio
初始化时,localVersion
的值仍然是0.0
。
那么,针对如何保存Component
状态的问题,我们该怎么做呢?为此,我们必须实现PersistentStateComponent
。这意味着我们必须重写两个新方法,即getState
和loadState
。我们还需要了解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
格式,更多信息请参考这里。
最后一步是在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或发表评论。
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub。
如果您觉得文章还差了那么点东西,也请通过 关注 督促我写出更好的文章——万一哪天我进步了呢?