idea插件开发--组件--编程久坐提醒
gitee地址:https://gitee.com/jyq_18792721831/studyplugin.git
idea插件開(kāi)發(fā)入門
idea插件開(kāi)發(fā)–配置
idea插件開(kāi)發(fā)–服務(wù)-翻譯插件
idea插件開(kāi)發(fā)–組件–編程久坐提醒
idea插件開(kāi)發(fā)--組件--編程久坐提醒
- 介紹
- 組件
- 應(yīng)用程序啟動(dòng)
- 項(xiàng)目打開(kāi)
- 模塊打開(kāi)
- 應(yīng)用程序/項(xiàng)目關(guān)閉
- 監(jiān)聽(tīng)程序
- 代碼中注冊(cè)監(jiān)聽(tīng)器
- 聲明注冊(cè)監(jiān)聽(tīng)器
- 項(xiàng)目級(jí)的監(jiān)聽(tīng)器
- 聲明注冊(cè)的其他配置
- 自定義監(jiān)聽(tīng)器接口
- 消息系統(tǒng)
- 設(shè)計(jì)
- 主題
- 消息總線
- 連接
- 廣播
- 嵌套消息
- 組件定義
- 應(yīng)用程序級(jí)別
- 項(xiàng)目級(jí)別
- 監(jiān)聽(tīng)器定義
- Java 計(jì)時(shí)器
- 實(shí)例
- 需求
- 分解
- 項(xiàng)目創(chuàng)建
- 配置界面
- 存儲(chǔ)服務(wù)
- 配置和存儲(chǔ)
- 計(jì)時(shí)器
- 應(yīng)用程序打開(kāi)關(guān)閉監(jiān)聽(tīng)器
- 提醒對(duì)話框
- 額外的技術(shù)點(diǎn)
- 效果
- 總結(jié)
介紹
插件組件是一項(xiàng)遺留功能,支持與為舊版本的 IntelliJ 平臺(tái)創(chuàng)建的插件兼容。使用組件的插件不支持動(dòng)態(tài)加載(在不重新啟動(dòng) IDE 的情況下安裝、更新和卸載插件的功能)。
插件組件在plugin.xml中配置,配置的標(biāo)簽有<application-components>,<project-components>和<module-components>三種。
分別對(duì)應(yīng)idea第一次打開(kāi),打開(kāi)項(xiàng)目,打開(kāi)模塊。
不過(guò)組件目前不支持使用。
官方建議使用服務(wù),訂閱狀態(tài)替換組件的使用,并很有可能在未來(lái)廢棄活刪除組件。
服務(wù)
如果是借助組件進(jìn)行初始化一些對(duì)象,或者準(zhǔn)備一些數(shù)據(jù),或者服務(wù)之類的,而且基本上是所有IDE都相同的,那么可以使用服務(wù)來(lái)替換。
存儲(chǔ)
如果是在組件中存儲(chǔ)一些信息,不管是應(yīng)用程序級(jí)別的或者是項(xiàng)目級(jí)別的,建議使用持久化來(lái)替換。
訂閱事件
如果需要在應(yīng)用程序第一次打開(kāi)觸發(fā),或者項(xiàng)目第一次打開(kāi)觸發(fā),或者模塊第一次打開(kāi)觸發(fā),那么建議訂閱事件來(lái)替換組件。
組件
說(shuō)是組件,可能不好理解,我自己的理解是,組件實(shí)際上是觸發(fā)的事件。
比如<application-components>標(biāo)簽下定義的組件,實(shí)際上就是訂閱了應(yīng)用程序打開(kāi)的事件,當(dāng)應(yīng)用程序打開(kāi)時(shí),會(huì)觸發(fā)這些訂閱了應(yīng)用程序打開(kāi)事件的監(jiān)聽(tīng),從而執(zhí)行一些邏輯。
應(yīng)用程序啟動(dòng)
官方不建議在應(yīng)用程序啟動(dòng)的時(shí)候執(zhí)行代碼,因?yàn)檫@會(huì)減慢啟動(dòng)速度。插件應(yīng)該在打開(kāi)項(xiàng)目活用戶調(diào)用插件的時(shí)候執(zhí)行,如果必須在應(yīng)用程序啟動(dòng)的時(shí)候執(zhí)行,那么現(xiàn)在可以有以下幾種方式實(shí)現(xiàn)。
組件
application-components組件,這些組件,會(huì)在應(yīng)用程序啟動(dòng)的時(shí)候執(zhí)行。但是不建議使用,有組件廢棄的可能。
訂閱
訂閱AppLifecycleListener監(jiān)聽(tīng)器的主題,以便在應(yīng)用程序打開(kāi)時(shí)觸發(fā)。
執(zhí)行一次
如果只是想代碼執(zhí)行一次,那么可以使用RunOnceUtil工具類實(shí)現(xiàn)。
數(shù)據(jù)準(zhǔn)備
如果只是想在應(yīng)用程序啟動(dòng)的時(shí)候,開(kāi)始提前為插件的工作準(zhǔn)備條件,那么可以在應(yīng)用程序啟動(dòng)的時(shí)候,增加后臺(tái)任務(wù),比如預(yù)加載活動(dòng)PreloadingActivity接口
項(xiàng)目打開(kāi)
官方比較建議的是在項(xiàng)目打開(kāi)的時(shí)候,執(zhí)行代碼。
組件
project-components組件,這里的組件會(huì)在項(xiàng)目打開(kāi)的時(shí)候執(zhí)行,也是不建議使用的,有組件廢棄的可能。
擴(kuò)展點(diǎn)
對(duì)于項(xiàng)目打開(kāi)有兩種擴(kuò)展點(diǎn):前臺(tái)執(zhí)行,后臺(tái)執(zhí)行。
com.intellij.postStartupActivity是前臺(tái)執(zhí)行的擴(kuò)展點(diǎn),也是當(dāng)項(xiàng)目打開(kāi)的時(shí)候會(huì)立即執(zhí)行。
com.intellij.backgroundPostStartupActivity是后臺(tái)執(zhí)行的擴(kuò)展點(diǎn),當(dāng)項(xiàng)目打開(kāi)后,會(huì)延遲大約5秒執(zhí)行(2019.3及以后的版本)。
執(zhí)行一次
如果只是想代碼執(zhí)行一次,那么可以使用RunOnceUtil工具類實(shí)現(xiàn)。
模塊打開(kāi)
隨著微服務(wù)的興起,我們一個(gè)項(xiàng)目中存在多個(gè)模塊已經(jīng)是不爭(zhēng)的事實(shí)了,所以官方實(shí)際上是不建議在模塊打開(kāi)的時(shí)候執(zhí)行代碼,因?yàn)檫@意味著當(dāng)一個(gè)項(xiàng)目被打開(kāi),那么可能有多個(gè)模塊被打開(kāi)。
組件
module-components組件,這里的組件會(huì)在模塊打開(kāi)的時(shí)候執(zhí)行,不建議使用。
除了因?yàn)榻M件可能被廢棄,新的解決方案中并不支持在模塊打開(kāi)的時(shí)候執(zhí)行代碼。
應(yīng)用程序/項(xiàng)目關(guān)閉
對(duì)于應(yīng)用程序或者項(xiàng)目關(guān)閉時(shí)執(zhí)行代碼,實(shí)際上并沒(méi)有做單獨(dú)的處理,而是巧妙的借助服務(wù)實(shí)現(xiàn)的。
我們定義服務(wù)是可以指定作用域的,比如應(yīng)用程序范圍內(nèi),或者項(xiàng)目范圍內(nèi)。
而且服務(wù)是可以實(shí)現(xiàn)Dispose接口的。
這樣,當(dāng)我們想要在項(xiàng)目關(guān)閉的時(shí)候執(zhí)行代碼,那么只需要定義一個(gè)項(xiàng)目范圍內(nèi)的服務(wù),然后讓服務(wù)實(shí)現(xiàn)Dispose接口,然后把需要在項(xiàng)目關(guān)閉的時(shí)候執(zhí)行的代碼放在Dispose接口中即可。
如果想要在應(yīng)用程序關(guān)閉的時(shí)候執(zhí)行代碼,那么也是類似,定義一個(gè)應(yīng)用程序范圍內(nèi)的服務(wù),也是實(shí)現(xiàn)Dispose接口,把需要在應(yīng)用程序關(guān)閉的時(shí)候執(zhí)行的代碼放在Dispose接口內(nèi)。
監(jiān)聽(tīng)程序
監(jiān)聽(tīng)器允許插件以聲明的方式訂閱通過(guò)消息總線傳遞的事件,監(jiān)聽(tīng)器必須是無(wú)狀態(tài)的,并且不能實(shí)現(xiàn)生命周期,比如Disposeable。
監(jiān)聽(tīng)器有兩種作用域:應(yīng)用程序級(jí)別和項(xiàng)目級(jí)別。
監(jiān)聽(tīng)器可以訂閱的全部主題列表和應(yīng)該實(shí)現(xiàn)的監(jiān)聽(tīng)接口擴(kuò)展點(diǎn)列表|IntelliJ Platform Plugin SDK (jetbrains.com)
監(jiān)聽(tīng)器的聲明性注冊(cè)擁有比代碼注冊(cè)有更好的性能。因?yàn)槁暶髯?cè)的監(jiān)聽(tīng)器實(shí)例是懶創(chuàng)建的,第一次事件觸發(fā)時(shí)才會(huì)創(chuàng)建監(jiān)聽(tīng)器實(shí)例,而不是在應(yīng)用程序啟動(dòng)或者項(xiàng)目打開(kāi)的期間。
從2019.3版本開(kāi)始,支持在plugin.xml中定義監(jiān)聽(tīng)器。
應(yīng)用程序級(jí)別的監(jiān)聽(tīng)器
<idea-plugin><applicationListeners><listener class="myPlugin.MyListenerClass" topic="BaseListenerInterface"/></applicationListeners> </idea-plugin>這里的class就是監(jiān)聽(tīng)器的具體實(shí)現(xiàn),而TOPIC就是我們關(guān)注的主題,或者說(shuō)訂閱的主題。
除了擴(kuò)展點(diǎn)列表中的主題,我們也可以自己通過(guò)Topic類創(chuàng)建自定義的主題。
你也可以像擴(kuò)展點(diǎn)列表中一樣,要求監(jiān)聽(tīng)器實(shí)現(xiàn)哪些操作,從而定義接口。
代碼中注冊(cè)監(jiān)聽(tīng)器
在代碼中聲明監(jiān)聽(tīng)器,我們首先需要將監(jiān)聽(tīng)器和訂閱的主題,注冊(cè)到消息總線,然后處理觸發(fā)后的操作
比如監(jiān)聽(tīng)有關(guān)虛擬文件系統(tǒng)更改的事件
messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {@Overridepublic void after(@NotNull List<? extends VFileEvent> events) {// handle the events} });聲明注冊(cè)監(jiān)聽(tīng)器
在實(shí)際開(kāi)發(fā)的時(shí)候,當(dāng)實(shí)現(xiàn)了一個(gè)監(jiān)聽(tīng)器接口,我們還需要去擴(kuò)展點(diǎn)列表中找到對(duì)應(yīng)關(guān)系,然后在把主題和監(jiān)聽(tīng)器進(jìn)行注冊(cè),這樣就比較麻煩。
所以在plugin.xml中注冊(cè)監(jiān)聽(tīng)器,允許我們指定監(jiān)聽(tīng)器接口,用監(jiān)聽(tīng)器接口代替訂閱的主題。
這樣就少了一個(gè)環(huán)節(jié),避免在這個(gè)環(huán)節(jié)出錯(cuò)。
<applicationListeners><listener class="myPlugin.MyVfsListener"topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/> </applicationListeners>監(jiān)聽(tīng)器的實(shí)現(xiàn)
public class MyVfsListener implements BulkFileListener {@Overridepublic void after(@NotNull List<? extends VFileEvent> events) {// handle the events} }項(xiàng)目級(jí)的監(jiān)聽(tīng)器
上面講的都是應(yīng)用程序級(jí)別的監(jiān)聽(tīng)器,如果我們需要定義項(xiàng)目級(jí)別的監(jiān)聽(tīng)器,就需要對(duì)項(xiàng)目做區(qū)分。
首先,在plugin.xml中使用projectListeners聲明
<idea-plugin><projectListeners><listener class="MyToolwindowListener"topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener" /></projectListeners> </idea-plugin>然后在監(jiān)聽(tīng)器實(shí)現(xiàn)中,傳入多項(xiàng)目之間的區(qū)分,project對(duì)象。
傳入方式為構(gòu)造器注入,就是寫(xiě)一個(gè)Project參數(shù)的構(gòu)造器,這樣當(dāng)創(chuàng)建監(jiān)聽(tīng)器實(shí)例的時(shí)候,就會(huì)把Project傳入,注意,必須是Project類型。
在idea插件中,構(gòu)造器注入是一種常見(jiàn)的方式,但是需要注意,支持構(gòu)造器注入的,一般也就是Project對(duì)象,有一些還支持Module對(duì)象,使用構(gòu)造器注入應(yīng)該小心。
public class MyToolwindowListener implements ToolWindowManagerListener {private final Project project;public MyToolwindowListener(Project project) {this.project = project;}@Overridepublic void stateChanged(@NotNull ToolWindowManager toolWindowManager) {// handle the state change} }聲明注冊(cè)的其他配置
在plugin.xml中聲明監(jiān)聽(tīng)器,除了上面用到的屬性,還有一些其他的屬性:
- os:允許監(jiān)聽(tīng)器只監(jiān)聽(tīng)給定的操作系統(tǒng),比如os=“windows”,這個(gè)屬性需要在2020.1以及之后的版本中使用。
- activeInTextMode:測(cè)試環(huán)境中禁用或啟用監(jiān)聽(tīng)器
- activeInHeadlessMode:在另一種測(cè)試環(huán)境中禁用監(jiān)聽(tīng)器
這些都比較少用。
自定義監(jiān)聽(tīng)器接口
首先應(yīng)該在接口中指定監(jiān)聽(tīng)器訂閱的主題,接著定義操作
public interface ChangeActionNotifier {Topic<ChangeActionNotifier> CHANGE_ACTION_TOPIC = Topic.create("custom name", ChangeActionNotifier.class)void beforeAction(Context context);void afterAction(Context context); }訂閱操作
代碼注冊(cè)如下
public void init(MessageBus bus) {bus.connect().subscribe(ActionTopics.CHANGE_ACTION_TOPIC, new ChangeActionNotifier() {@Overridepublic void beforeAction(Context context) {// Process 'before action' event.}@Overridepublic void afterAction(Context context) {// Process 'after action' event.}}); }當(dāng)然,我們應(yīng)該盡可能使用聲明注冊(cè)監(jiān)聽(tīng)器
觸發(fā)
觸發(fā)代碼如下
public void doChange(Context context) {ChangeActionNotifier publisher = myBus.syncPublisher(ActionTopics.CHANGE_ACTION_TOPIC);publisher.beforeAction(context);try {// Do action// ...} finally {publisher.afterAction(context)} }- MessageBus實(shí)例可通過(guò)ComponentManager.getMessageBus()獲得 許多標(biāo)準(zhǔn)接口都實(shí)現(xiàn)了消息總線,例如Application和Project。
- IntelliJ平臺(tái)使用許多公共主題,例如AppTopics,ProjectTopics等。 ``因此,可以訂閱它們以接收有關(guān)處理的信息。
消息系統(tǒng)
在實(shí)際開(kāi)發(fā)中,發(fā)布訂閱模式是一個(gè)非常棒的模式。
在idea中,消息的傳遞系統(tǒng)就是一個(gè)發(fā)布訂閱模式。并且在發(fā)布訂閱的基礎(chǔ)上,擴(kuò)展了層級(jí)結(jié)構(gòu)的廣播和特殊嵌套事件的傳遞。
設(shè)計(jì)
消息傳遞的終點(diǎn)是主題,每一個(gè)消息最終都會(huì)傳遞到主題停止,當(dāng)然可能不止一個(gè)主題??蛻舳丝梢杂嗛喯⒖偩€中的主題,并且支持客戶端向消息總線中發(fā)布消息。
主題
主題有兩個(gè)核心的屬性,一個(gè)是可讀性的名字,用于區(qū)分不同的主題,這里的可讀是人類可讀;另一個(gè)屬性是廣播方向。前面說(shuō)了,消息傳遞不僅僅是發(fā)布訂閱,還有層級(jí)結(jié)構(gòu)的廣播,比如向下廣播,向上廣播,兄弟廣播之類的。理解主題的層級(jí)結(jié)構(gòu)為樹(shù)形,我覺(jué)得更容易理解一點(diǎn)。
主題有兩種類型,分別為應(yīng)用程序級(jí)別,和項(xiàng)目級(jí)別。
使用Topic的內(nèi)部枚舉來(lái)區(qū)分AppLevel,ProjectLevel
消息總線
消息總線主要實(shí)現(xiàn)兩個(gè)功能:客戶端發(fā)布消息,監(jiān)聽(tīng)器訂閱主題。
可以認(rèn)為所有的消息都要通過(guò)消息總線,在消息總線中通過(guò)的時(shí)候,就會(huì)分發(fā)給訂閱者。
連接
消息總線與客戶端建立關(guān)系的鏈接,它是實(shí)現(xiàn)訂閱的核心,更準(zhǔn)確的說(shuō),它一方面關(guān)聯(lián)了消息總線,另一方面關(guān)聯(lián)了監(jiān)聽(tīng)器。
當(dāng)有消息投遞的時(shí)候,消息總線就會(huì)首先把消息傳遞給連接,然后連接調(diào)用監(jiān)聽(tīng)器處理。
廣播
消息總線可以組織到層級(jí)結(jié)構(gòu)中
如果topic1將廣播方向定義為*TO_CHILDREN,*我們會(huì)得到以下內(nèi)容:
廣播方式:子廣播(默認(rèn)),不廣播,父廣播。也是通過(guò)Topic類中的內(nèi)部枚舉定義。
嵌套消息
消息系統(tǒng)保證發(fā)送到某個(gè)主題的所有消息的順序都是一定的。
- 消息1已發(fā)送;
- handler1接收message1并將message2發(fā)送到同一主題;
- 處理程序 2接收消息 1;
- 處理程序 2接收消息 2;
- 處理程序 1接收消息 2;
組件定義
應(yīng)用程序級(jí)別
在plugin.xml中聲明
<application-components><component><implementation-class>com.study.plugin.sedentaryreminder.components.MyApplicationComponent</implementation-class></component></application-components>然后新增組件實(shí)現(xiàn)類,實(shí)現(xiàn)類實(shí)現(xiàn)ApplicationComponent接口。
import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull;public class MyApplicationComponent implements ApplicationComponent {@Overridepublic void initComponent() {Messages.showMessageDialog("initComponent", "applicationComponent", Messages.getInformationIcon());}@Overridepublic void disposeComponent() {Messages.showMessageDialog("disposeComponent", "applicationComponent", Messages.getInformationIcon());}@Overridepublic @NotNullString getComponentName() {return "MyApplicationComponent";} }效果
項(xiàng)目級(jí)別
項(xiàng)目級(jí)別的使用project-components
<project-components><component><implementation-class>com.study.plugin.sedentaryreminder.components.MyProjectComponent</implementation-class></component></project-components>實(shí)現(xiàn)類實(shí)現(xiàn)接口ProjectComponent
import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull;public class MyProjectComponent implements ProjectComponent {@Overridepublic void projectOpened() {Messages.showMessageDialog("projectOpen", "projectComponent", Messages.getInformationIcon());System.out.println("projectOpened");}@Overridepublic void projectClosed() {Messages.showMessageDialog("projectClosed", "projectComponent", Messages.getInformationIcon());System.out.println("projectClosed");}@Overridepublic void initComponent() {}@Overridepublic void disposeComponent() {}@Overridepublic @NotNullString getComponentName() {return "MyProjectComponent";} }效果
監(jiān)聽(tīng)器定義
在plugin.xml中聲明定義
<applicationListeners><listener class="com.study.plugin.sedentaryreminder.listeners.MyApplicationOpenListener" topic="com.intellij.ide.AppLifecycleListener"/></applicationListeners>這里標(biāo)簽加入后,會(huì)變紅,檢測(cè)不通過(guò),是因?yàn)閜lugin.xml中idea-version配置的不支持監(jiān)聽(tīng)器的版本,要使用監(jiān)聽(tīng)器,那么idea的版本必須是2019.3及之后的版本,修改原來(lái)的173.0版本為193.0,就不會(huì)報(bào)紅了
然后業(yè)務(wù)實(shí)現(xiàn)Topic的接口即可
import com.intellij.ide.AppLifecycleListener; import com.study.plugin.sedentaryreminder.utils.NotificationUtil;public class MyApplicationOpenListener implements AppLifecycleListener {@Overridepublic void appStarted() {NotificationUtil.error("appStarted");}@Overridepublic void appClosing() {NotificationUtil.error("appClosing");} }查看接口,發(fā)現(xiàn)區(qū)分的比組件更詳細(xì)。
效果
Java 計(jì)時(shí)器
在Java中要實(shí)現(xiàn)定時(shí)執(zhí)行某項(xiàng)任務(wù)就需要用到Timer類和TimerTask類。其中,Timer類可以實(shí)現(xiàn)在某一刻時(shí)間或某一段時(shí)間后安排某一個(gè)任務(wù)執(zhí)行一次或定期重復(fù)執(zhí)行,該功能需要與TimerTask類配合使用。TimerTask類表示由Timer類安排的一次或多次重復(fù)執(zhí)行的那個(gè)任務(wù)。
| void cancel() | 終止此計(jì)時(shí)器,丟棄所有當(dāng)前已安排的任務(wù),對(duì)當(dāng)前正在執(zhí)行的任務(wù)沒(méi)有影響 |
| int purge() | 從此計(jì)時(shí)器的任務(wù)隊(duì)列中移除所有已取消的任務(wù),一般用來(lái)釋放內(nèi)存空間 |
| void schedule(TimerTask task, Date time) | 安排在指定的時(shí)間執(zhí)行指定的任務(wù) |
| void schedule(TimerTask task, Date firstTime, long period) | 安排指定的任務(wù)在指定的時(shí)間開(kāi)始進(jìn)行重復(fù)的固定延遲執(zhí)行 |
| void schedule(TimerTask task, long delay) | 安排在指定延遲后執(zhí)行指定的任務(wù) |
| void schedule(TimerTask task, long delay, long period) | 安排指定的任務(wù)從指定的延遲后開(kāi)始進(jìn)行重復(fù)的固定延遲執(zhí)行 |
| void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 安排指定的任務(wù)在指定的時(shí)間開(kāi)始進(jìn)行重復(fù)的固定速率執(zhí)行 |
| void scheduleAtFixedRate(TimerTask task, long delay, long period) | 安排指定的任務(wù)在指定的延遲后開(kāi)始進(jìn)行重復(fù)的固定速率執(zhí)行 |
時(shí)間都是毫秒為單位
schedule()和scheduleAtFixedRate()方法的區(qū)別
schedule()方法的執(zhí)行時(shí)間間隔永遠(yuǎn)的是固定的,如果之前出現(xiàn)了延遲情況,那么之后也會(huì)繼續(xù)按照設(shè)定好的時(shí)間間隔來(lái)執(zhí)行
scheduleAtFixedRate()方法在出現(xiàn)延遲情況時(shí),則將快讀連續(xù)地出現(xiàn)兩次或更多的執(zhí)行,從而使后續(xù)執(zhí)行能夠追趕上來(lái)。從長(zhǎng)遠(yuǎn)來(lái)看,執(zhí)行的頻率將正好是指定的周期。
實(shí)例
我們接下來(lái)用一個(gè)小例子來(lái)應(yīng)用所學(xué)。
開(kāi)發(fā)一個(gè)編程久坐提醒。
需求
隨著開(kāi)發(fā)任務(wù)越來(lái)越重,經(jīng)濟(jì)下行,每個(gè)人在電腦前編程的時(shí)間越來(lái)越長(zhǎng),而久坐會(huì)導(dǎo)致許多疾病的發(fā)生,比如腹部肥胖,腰間盤(pán)突出等,所以在編程一段時(shí)間后,ide能提醒開(kāi)發(fā)者,你應(yīng)該休息一下,活動(dòng)一下。
分解
首先需要有配置,每個(gè)人身體狀況不同,所以可以自定義每隔多長(zhǎng)時(shí)間提醒一次,然后每次休息多長(zhǎng)時(shí)間。
有的人自制力好點(diǎn),到了時(shí)間就休息,但是有的人卻是工作狂,工作不完成,誓不休息;所以應(yīng)該可以配置是否可豁免。
當(dāng)然,有些時(shí)候是需要暫時(shí)關(guān)閉提醒功能的,所以可以配置,今日是否提醒。
從每天第一次打開(kāi)ide開(kāi)始計(jì)時(shí),中間關(guān)閉ide時(shí)候停止計(jì)時(shí),然后計(jì)算累計(jì)時(shí)間,防止有人不講武德,每次快到時(shí)間了,重啟ide,跳過(guò)提醒。
分解的需求如下:
項(xiàng)目創(chuàng)建
首先創(chuàng)建一個(gè)項(xiàng)目,名字就是sedentaryreminder,然后創(chuàng)建目錄結(jié)構(gòu)
配置界面
配置界面長(zhǎng)這個(gè)樣子
別忘記增加一個(gè)監(jiān)聽(tīng)器,如果輸入的時(shí)間不在1小時(shí)內(nèi),給出提示
效果
存儲(chǔ)服務(wù)
存儲(chǔ)服務(wù)將配置存儲(chǔ),防止用戶重新打開(kāi)后配置的信息丟失。
存儲(chǔ)服務(wù)非常簡(jiǎn)單,主要是鞏固之前的輕量級(jí)服務(wù)idea插件開(kāi)發(fā)–服務(wù)-翻譯插件_a18792721831的博客-CSDN博客
import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.components.Service;@Service public final class SedentaryReminderConfigService {private final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();public void save(String key, String value) {propertiesComponent.setValue(key, value);}public void save(String key, Integer value) {propertiesComponent.setValue(key, value, 0);}public void save(String key, Boolean value) {propertiesComponent.setValue(key, value);}public void clear(String key) {propertiesComponent.unsetValue(key);}public String get(String key, String defValue) {return propertiesComponent.getValue(key, defValue);}public int get(String key, int defValue) {return propertiesComponent.getInt(key, defValue);}public boolean get(String key, boolean defValue) {return propertiesComponent.getBoolean(key, defValue);}}配置和存儲(chǔ)
配置界面也是非常的簡(jiǎn)單,實(shí)現(xiàn)基本要求即可idea插件開(kāi)發(fā)–配置_a18792721831的博客-CSDN博客
配置setting中繪制界面的時(shí)候,需要先從存儲(chǔ)服務(wù)中獲取已存儲(chǔ)的值,然后設(shè)置為配置界面的值,當(dāng)發(fā)生修改的時(shí)候,存儲(chǔ)起來(lái)即可。
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.util.NlsContexts; import com.study.plugin.sedentaryreminder.service.SedentaryReminderConfigService; import com.study.plugin.sedentaryreminder.ui.SedentaryReminderConfigUI; import com.study.plugin.sedentaryreminder.utils.PluginAppKeys; import java.util.Objects; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;import javax.swing.JComponent;public class SedentaryReminderConfig implements SearchableConfigurable, PluginAppKeys {private SedentaryReminderConfigUI ui = new SedentaryReminderConfigUI();private SedentaryReminderConfigService configService = ApplicationManager.getApplication().getService(SedentaryReminderConfigService.class);@Overridepublic @NotNull@NonNlsString getId() {return PLUGIN_CONFIG_ID;}@Overridepublic @NlsContexts.ConfigurableName String getDisplayName() {return PLUGIN_CONFIG_NAME;}@Overridepublic @NullableJComponent createComponent() {ui.setIntervalTime(configService.get(PLUGIN_INTERVAL_TIME, DEFAULT_INTERVAL_TIME));ui.setRestTime(configService.get(PLUGIN_REST_TIME, DEFAULT_REST_TIME));ui.setCompulsionRest(configService.get(PLUGIN_COMPULSION_REST, DEFAULT_COMPULSION_REST));ui.setTodaySkipReminder(configService.get(PLUGIN_TODAY_SKIP_REMINDER, DEFAULT_TODAY_SKIP_REMINDER));return ui.getRootPanel();}@Overridepublic boolean isModified() {return configService.get(PLUGIN_INTERVAL_TIME, DEFAULT_INTERVAL_TIME) != ui.getIntevalTime() ||configService.get(PLUGIN_REST_TIME, DEFAULT_REST_TIME) != ui.getRestTime() ||configService.get(PLUGIN_COMPULSION_REST, DEFAULT_COMPULSION_REST) != ui.getCompulsionRest() ||configService.get(PLUGIN_TODAY_SKIP_REMINDER, DEFAULT_TODAY_SKIP_REMINDER) != ui.getTodaySkipReminder();}@Overridepublic void apply() throws ConfigurationException {Integer intevalTime = ui.getIntevalTime();if (Objects.nonNull(intevalTime)) {configService.save(PLUGIN_INTERVAL_TIME, intevalTime);}Integer restTime = ui.getRestTime();if (Objects.nonNull(restTime)) {configService.save(PLUGIN_REST_TIME, restTime);}configService.save(PLUGIN_COMPULSION_REST, ui.getCompulsionRest());configService.save(PLUGIN_TODAY_SKIP_REMINDER, ui.getTodaySkipReminder());} }計(jì)時(shí)器
當(dāng)計(jì)時(shí)器觸發(fā)的時(shí)候,需要記錄下本次提醒時(shí)間,以及清空已經(jīng)編程時(shí)間,然后展示提醒對(duì)話框
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.study.plugin.sedentaryreminder.service.SedentaryReminderConfigService; import com.study.plugin.sedentaryreminder.ui.ReminderDialog; import com.study.plugin.sedentaryreminder.utils.PluginAppKeys; import java.time.LocalDateTime; import java.util.TimerTask;public class ReminderTask extends TimerTask implements PluginAppKeys {private SedentaryReminderConfigService configService = ApplicationManager.getApplication().getService(SedentaryReminderConfigService.class);private static final Logger log = Logger.getInstance(ReminderTask.class);@Overridepublic void run() {log.info("reminder timer task is run");// 記錄時(shí)間提醒時(shí)間configService.save(SEDENTARY_REMINDER_LAST_REMINDER_DATE, LocalDateTime.now());// 清空已用時(shí)間configService.clear(SEDENTARY_REMINDER_LAST_USE_DATE);log.info("last reminder date is save : " + configService.get(SEDENTARY_REMINDER_LAST_REMINDER_DATE, LocalDateTime.now()) +", last use date is clear : " +configService.get(SEDENTARY_REMINDER_LAST_USE_DATE, 0L));// 彈出提醒對(duì)話框new ReminderDialog().show();log.info("reminder dialog is show");} }應(yīng)用程序打開(kāi)關(guān)閉監(jiān)聽(tīng)器
當(dāng)應(yīng)用程序打開(kāi)的時(shí)候,需要讀取上次提醒時(shí)間以及編程已用時(shí)間,然后獲取當(dāng)前時(shí)間,判斷上次提醒時(shí)間是否是當(dāng)天,如果是同一天,那么繼續(xù)上次編程時(shí)間計(jì)時(shí),如果不是同一天那么清空上次編程時(shí)間。
也就是每天需要獨(dú)立計(jì)時(shí)。
接著需要判斷是否今日跳過(guò)提醒,如果需要今日跳過(guò)提醒,那么結(jié)束,否則繼續(xù)后續(xù)操作。
如果今日不可跳過(guò),那么獲取最大編程時(shí)間和休息時(shí)間,然后啟動(dòng)計(jì)時(shí)器。
如果是同一天,需要繼續(xù)上次編程已用時(shí)間繼續(xù)計(jì)時(shí),否則從0開(kāi)始計(jì)時(shí)
當(dāng)應(yīng)用程序關(guān)閉的時(shí)候,需要終止計(jì)時(shí)器,并放棄所有的任務(wù),同時(shí)釋放計(jì)時(shí)器內(nèi)存。
如果今日可跳過(guò),那么結(jié)束。
如果今日不可跳過(guò),那么獲取上次提醒時(shí)間,獲取休息時(shí)間,獲取允許的最大編程時(shí)間和當(dāng)前時(shí)間,計(jì)算編程已用時(shí)間
編程已用時(shí)間 = 當(dāng)前時(shí)間 - 上次提醒時(shí)間 - 休息時(shí)間
如果編程已用時(shí)間大于最大允許的編程時(shí)間,那么是原來(lái)今日跳過(guò)提醒修改為今日提醒,此時(shí)設(shè)置編程已用時(shí)間為0,然后記錄編程已用時(shí)間。
別忘記在plugin.xml中注冊(cè)監(jiān)聽(tīng)器。
代碼如下
import com.intellij.ide.AppLifecycleListener; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.study.plugin.sedentaryreminder.service.SedentaryReminderConfigService; import com.study.plugin.sedentaryreminder.task.ReminderTask; import com.study.plugin.sedentaryreminder.utils.PluginAppKeys; import java.time.LocalDateTime; import java.util.Timer;public class SedentaryReminderApplicationListener implements AppLifecycleListener, PluginAppKeys {private SedentaryReminderConfigService configService = ApplicationManager.getApplication().getService(SedentaryReminderConfigService.class);// 創(chuàng)建計(jì)時(shí)器private Timer timer = new Timer();private static final Logger log = Logger.getInstance(SedentaryReminderApplicationListener.class);@Overridepublic void appStarted() {// 獲取上次提醒時(shí)間LocalDateTime lastReminderDate = configService.get(SEDENTARY_REMINDER_LAST_REMINDER_DATE, LocalDateTime.now());log.info("app start last reminder date : " + lastReminderDate);// 獲取上次編程時(shí)間(單位:秒)long lastUseDateSeconds = configService.get(SEDENTARY_REMINDER_LAST_USE_DATE, 0L);log.info("app start last use date : " + lastUseDateSeconds);// 獲取當(dāng)前時(shí)間LocalDateTime now = LocalDateTime.now();if (now.getDayOfMonth() != lastReminderDate.getDayOfMonth()) {// 如果本次打開(kāi)時(shí)間與上次提醒時(shí)間不在一天,則重置今日跳過(guò)配置configService.clear(PLUGIN_TODAY_SKIP_REMINDER);log.info("app start last reminder not today, clear today skip reminder");}// 獲取今日是否跳過(guò)boolean todaySkipReminder = configService.get(PLUGIN_TODAY_SKIP_REMINDER, false);if (todaySkipReminder) {log.info("app start todaySkipReminder is true");return;}// 獲取編程時(shí)間int intervalTime = configService.get(PLUGIN_INTERVAL_TIME, DEFAULT_INTERVAL_TIME);// 獲取休息時(shí)間int restTime = configService.get(PLUGIN_REST_TIME, DEFAULT_REST_TIME);// 如果上次提醒時(shí)間和現(xiàn)在不是一天,那么清空編程時(shí)間,然后創(chuàng)建計(jì)時(shí)器任務(wù)if (now.getDayOfMonth() != lastReminderDate.getDayOfMonth()) {log.info("app start last reminder date not today");configService.save(SEDENTARY_REMINDER_LAST_USE_DATE, 0L);log.info("app start save last use date 0");timer.schedule(new ReminderTask(), intervalTime * 60 * 1000, (intervalTime + restTime) * 60 * 1000);log.info("app start first reminder in " + intervalTime + " min");log.info("app start reminder interval is " + (intervalTime + restTime));}// 如果上次提醒時(shí)間和現(xiàn)在是同一天,那么接著上次的時(shí)間繼續(xù)計(jì)時(shí)else {log.info("app start last reminder date is today");timer.schedule(new ReminderTask(), (intervalTime * 60 - lastUseDateSeconds) * 1000, (intervalTime + restTime) * 60 * 1000);log.info("app start first reminder in " + (intervalTime * 60 - lastUseDateSeconds) + " sec");log.info("app start reminder interval is " + (intervalTime + restTime));}}@Overridepublic void appWillBeClosed(boolean isRestart) {// 終止計(jì)時(shí)器,放棄全部任務(wù)timer.cancel();// 釋放內(nèi)存timer.purge();log.info("app colsed timer is stop");// 獲取今日是否跳過(guò)// 放在計(jì)時(shí)器關(guān)閉之后是防止修改配置導(dǎo)致內(nèi)存泄漏boolean todaySkipReminder = configService.get(PLUGIN_TODAY_SKIP_REMINDER, false);if (todaySkipReminder) {log.info("app closed todaySkipReminder is true");return;}// 記錄編程時(shí)間// 獲取上次提醒時(shí)間LocalDateTime lastReminderTime = configService.get(SEDENTARY_REMINDER_LAST_REMINDER_DATE, LocalDateTime.now());// 獲取休息時(shí)間int restTime = configService.get(PLUGIN_REST_TIME, DEFAULT_REST_TIME);// 獲取編程時(shí)間int intervalTime = configService.get(PLUGIN_INTERVAL_TIME, DEFAULT_INTERVAL_TIME);LocalDateTime now = LocalDateTime.now();long lastUseTime = now.toEpochSecond(currentZoneOffset) - lastReminderTime.toEpochSecond(currentZoneOffset) - restTime * 60;// 避免用戶以修改是否跳過(guò)為方式跳過(guò)休息// 如果他反復(fù)修改配置,期望跳過(guò)休息,那么會(huì)盡快的實(shí)現(xiàn)一次休息lastUseTime = lastUseTime > intervalTime ? 0 : lastUseTime;configService.save(SEDENTARY_REMINDER_LAST_USE_DATE,lastUseTime);log.info("app closed last use date is save : " + configService.get(SEDENTARY_REMINDER_LAST_USE_DATE, 0L));} }提醒對(duì)話框
提醒對(duì)話框繼承DialogWrapper類,DiaWrapper類是idea平臺(tái)封裝的對(duì)話框的基類。
提醒對(duì)話框首先需要一個(gè)JPanel用于存放其他控件,也就是rootJPanel。
然后使用方位布局,在中間放一個(gè)進(jìn)度條,在上面放一個(gè)倒計(jì)時(shí)的JLabel,用于顯示倒計(jì)時(shí)。
同時(shí)需要一個(gè)適配swing的計(jì)時(shí)器,用于更新進(jìn)度條。
特別需要注意的是,swing的更新操作全部需要放在EDT線程中,詳見(jiàn)Java多線程開(kāi)發(fā)系列之番外篇:事件派發(fā)線程—EventDispatchThread - 王若伊_恩賜解脫 - 博客園 (cnblogs.com)
而DialogWrapper類的很多操作都會(huì)檢測(cè)線程是否是EDT線程,如果不是EDT線程,那么就會(huì)阻止用戶更新界面,所以我們需要重寫(xiě)這些會(huì)檢查線程的操作,如果當(dāng)前線程不是EDT線程,需要提交事件到EDT事件隊(duì)列中。
在初始化界面的時(shí)候,需要給計(jì)時(shí)器綁定更新操作,更新操作主要是更新進(jìn)度條和倒計(jì)時(shí)。
然后給進(jìn)度條增加監(jiān)聽(tīng),當(dāng)進(jìn)度條滿的時(shí)候,使用EDT關(guān)閉對(duì)話框
更別忘記設(shè)置取消不可用。
在idea創(chuàng)建對(duì)話框面板的時(shí)候,需要根據(jù)配置設(shè)置進(jìn)度條的初始值,最大值和最小值,并啟動(dòng)計(jì)時(shí)器。
然后重寫(xiě)對(duì)話框下面的按鈕,隱藏確定,取消按鈕
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.ui.DialogWrapper; import com.study.plugin.sedentaryreminder.service.SedentaryReminderConfigService; import com.study.plugin.sedentaryreminder.utils.PluginAppKeys; import java.awt.BorderLayout; import lombok.SneakyThrows; import org.jetbrains.annotations.Nullable;import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; import javax.swing.Timer;public class ReminderDialog extends DialogWrapper implements PluginAppKeys {private static final Logger log = Logger.getInstance(ReminderDialog.class);private JPanel rootJPanel = new JPanel();private JProgressBar progressBar = new JProgressBar();// 創(chuàng)建計(jì)時(shí)器,主要是用于提醒對(duì)話框的進(jìn)度條更新private Timer timer;// 為了更加直觀,增加倒計(jì)時(shí)展示private JLabel timeLabel;private SedentaryReminderConfigService configService = ApplicationManager.getApplication().getService(SedentaryReminderConfigService.class);public ReminderDialog() {super(true);// 設(shè)置是否是模式對(duì)話框,即是否強(qiáng)制休息setModal(configService.get(PLUGIN_COMPULSION_REST, DEFAULT_COMPULSION_REST));setTitle("休息中~");initJPanel();init();}@SneakyThrows@Overrideprotected void init() {if (SwingUtilities.isEventDispatchThread()) {super.init();} else {SwingUtilities.invokeAndWait(() -> super.init());}}private void initJPanel() {rootJPanel.setLayout(new BorderLayout());timeLabel = new JLabel();rootJPanel.add(timeLabel, BorderLayout.NORTH);// 居中展示rootJPanel.add(progressBar, BorderLayout.CENTER);// 進(jìn)度條展示邊框progressBar.setBorderPainted(true);// 每過(guò)1秒,進(jìn)度條更新一次timer = new Timer(1000, e -> {progressBar.setValue(progressBar.getValue() + 1);timeLabel.setText(String.valueOf(progressBar.getMaximum() - progressBar.getValue()));});// 增加進(jìn)度條監(jiān)聽(tīng),如果進(jìn)度條滿了,關(guān)閉對(duì)話框progressBar.addChangeListener(e -> {Object source = e.getSource();if (source instanceof JProgressBar) {JProgressBar bar = (JProgressBar) source;if (bar.getValue() == bar.getMaximum()) {// 計(jì)時(shí)器關(guān)閉timer.stop();// 發(fā)送窗口關(guān)閉事件SwingUtilities.invokeLater(() -> close(CLOSE_EXIT_CODE));log.info("reminder dialog will be closed");}}});// 設(shè)置關(guān)閉對(duì)話框不可用getCancelAction().setEnabled(false);}@Overrideprotected @NullableJComponent createCenterPanel() {// 設(shè)置進(jìn)度條最大值,也就是休息時(shí)間int restTime = configService.get(PLUGIN_REST_TIME, DEFAULT_REST_TIME) * 60;progressBar.setMaximum(restTime);timeLabel.setText(String.valueOf(restTime));// 設(shè)置進(jìn)度條開(kāi)始progressBar.setMinimum(0);// 進(jìn)度條初始化為0progressBar.setValue(0);// 計(jì)時(shí)器啟動(dòng)timer.start();return rootJPanel;}@Overrideprotected JComponent createSouthPanel() {// 隱藏 ok 取消按鈕return null;}@SneakyThrows@Overridepublic void show() {if (SwingUtilities.isEventDispatchThread()) {super.show();} else {SwingUtilities.invokeAndWait(() -> super.show());}} }額外的技術(shù)點(diǎn)
休息倒計(jì)時(shí)是使用swing適配的計(jì)時(shí)器完成,是一個(gè)可復(fù)用的計(jì)時(shí)器,基本原理和java計(jì)時(shí)器相同,相關(guān)的使用方式見(jiàn)Java Swing Timer:計(jì)時(shí)器組件 (biancheng.net)
進(jìn)度條控件也是swing封裝的一個(gè)組件,使用起來(lái)需要用戶自己更新進(jìn)度條的值,一般是配合swing適配的計(jì)時(shí)器使用,相關(guān)資料見(jiàn)Java Swing JProgressBar:進(jìn)度條組件 (biancheng.net)
還有就是我們存儲(chǔ)時(shí)間時(shí)候,存儲(chǔ)的是時(shí)間戳,獲取時(shí)間的時(shí)間戳,然后把時(shí)間戳作為字符串存儲(chǔ)。
時(shí)間使用LocalDateTime,而LocalDataTime和時(shí)間戳的互轉(zhuǎn),
LocalDateTime -> 時(shí)間戳
使用LocalDateTime.toEpochSecond方法,參數(shù)是時(shí)區(qū)。
時(shí)間戳 -> LocalDateTime
使用LocalDateTime.ofEpochSecond方法,參數(shù)是時(shí)間戳的秒,納秒我們?cè)O(shè)置為0,然后在傳入時(shí)區(qū)即可。
操作系統(tǒng)的時(shí)區(qū)獲取
使用OffsetDateTime.now().getOffset()獲取操作系統(tǒng)默認(rèn)的時(shí)區(qū)。
日志
idea插件打印日志需要使用idea平臺(tái)的日志類,創(chuàng)建日志對(duì)象。
com.intellij.openapi.diagnostic.Logger.getInstance(ReminderTask.class)
效果
強(qiáng)制休息時(shí),會(huì)展示如下模式對(duì)話框,此時(shí)你是無(wú)法操作的,同時(shí)會(huì)自動(dòng)將鼠標(biāo)焦點(diǎn)聚焦到模式對(duì)話框上。
你點(diǎn)擊叉叉是無(wú)法取消對(duì)話框的,而且你也無(wú)法操作其他的。
只能等待倒計(jì)時(shí)結(jié)束,自動(dòng)關(guān)閉對(duì)話框。
而且當(dāng)你重啟后,還會(huì)接著上次編程已用時(shí)間繼續(xù)倒計(jì)時(shí)。
默認(rèn)是每編程25分鐘,休息5分鐘。
你可以自己配置編程時(shí)間,編程時(shí)間不能大于1小時(shí)。
你可以在未觸發(fā)提醒對(duì)話框的時(shí)候配置今日跳過(guò),并重啟idea后生效。
當(dāng)然你也可以配置非模式對(duì)話框,只是提醒,而不強(qiáng)制。
總結(jié)
這個(gè)小插件的靈感來(lái)源于運(yùn)動(dòng)手環(huán),運(yùn)動(dòng)手環(huán)有久坐提醒,每當(dāng)我們久坐1小時(shí),手環(huán)就會(huì)震動(dòng),提醒我們活動(dòng)一下,但是很多時(shí)候,我們并不會(huì)按照提醒進(jìn)行休息。
開(kāi)發(fā)編程久坐提醒一方面是強(qiáng)制休息,另一方面是提醒休息。
總的來(lái)說(shuō)這個(gè)插件還是有一定挑戰(zhàn)性的,開(kāi)發(fā)過(guò)程中的一些技術(shù)點(diǎn),是之前并不了解的,所以這個(gè)插件的開(kāi)發(fā)難度一度出乎了我的預(yù)期,好在網(wǎng)上有許多大神的總結(jié),一步一步的攻克,完成了這個(gè)插件。
通過(guò)這個(gè)插件,首先是了解了idea插件的組件,包括組件的定義,使用以及idea自己對(duì)組件的演變。
接著了解了組件的替代者,有監(jiān)聽(tīng)器,有工具類等,idea提供了多種方式實(shí)現(xiàn)原本組件的功能。
同時(shí)也是進(jìn)一步體會(huì)到了技術(shù)的發(fā)展對(duì)開(kāi)發(fā)工具的影響,比如隨著微服務(wù)的興起,項(xiàng)目?jī)?nèi)模塊的數(shù)量迅速增加,此前提供的模塊級(jí)別的組件,此時(shí)就不太適合了,那么idea就拋棄了組件這種功能,轉(zhuǎn)為其他方式實(shí)現(xiàn)。
然后是了解了idea中的消息系統(tǒng),以及idea是如何實(shí)現(xiàn)的消息系統(tǒng),idea中各個(gè)控件如何相互配合,多個(gè)線程之間的狀態(tài)如何進(jìn)行數(shù)據(jù)的傳遞,以及Idea對(duì)消息系統(tǒng)中發(fā)布訂閱模型的客戶化修改。
當(dāng)然,還有最重要的監(jiān)聽(tīng)器,可以說(shuō),監(jiān)聽(tīng)器可以關(guān)注訂閱idea中任何狀態(tài),事件和操作,都允許插件開(kāi)發(fā)者對(duì)這些信息做自己關(guān)注的處理。
除此之外,對(duì)jdk中提供的計(jì)時(shí)器有了一定的了解,計(jì)時(shí)器的使用,原理和計(jì)算方式。
接著是如何使用swing中的進(jìn)度條的控件,包括進(jìn)度條的創(chuàng)建,使用和更新,以及進(jìn)度條值得監(jiān)控。
swing對(duì)計(jì)時(shí)器的適配,使得使用計(jì)時(shí)器更新進(jìn)度條更加簡(jiǎn)便。
在后則是idea中提供的對(duì)話框的封裝,以及如何使用重寫(xiě)機(jī)制,來(lái)修改父類中對(duì)話框的繪制,以及如何創(chuàng)建對(duì)話框,展示對(duì)話框和關(guān)閉對(duì)話框。
在對(duì)話框中了解到了swing中對(duì)于多個(gè)線程對(duì)相同數(shù)據(jù)的競(jìng)爭(zhēng)是如何解決的,以及EDT線程是什么,如何避免EDT線程檢測(cè),如何正確的在EDT線程之外操作swing的界面。
其實(shí)時(shí)間的存儲(chǔ)中,開(kāi)發(fā)的時(shí)候也遇到了一定的困難,比如時(shí)間和時(shí)間戳的相互轉(zhuǎn)化,時(shí)區(qū)的獲取。
也逐漸讓我明白了,打印日志是多么的重要,特別是這種多線程的開(kāi)發(fā)的時(shí)候,不打印日志,即使有斷點(diǎn)調(diào)試,梳理多個(gè)線程之間的互相調(diào)用,也是比較難的。好的日志可以讓問(wèn)題一目了然。
總的來(lái)說(shuō),收獲良多。
總結(jié)
以上是生活随笔為你收集整理的idea插件开发--组件--编程久坐提醒的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [NLP]——BPE、WordPiece
- 下一篇: Leaflet地图初始化地图(谷歌+天地