天天看點

Android Studio IDE 插件開發概述背景介紹如何開發一個IDE插件?IDE插件常用功能介紹插件打包與安裝總結關于位元組終端技術團隊

Android Studio IDE 插件開發概述背景介紹如何開發一個IDE插件?IDE插件常用功能介紹插件打包與安裝總結關于位元組終端技術團隊

作者:位元組跳動終端技術——周宸韬

這篇文章旨在向讀者介紹intellij ide插件的開發流程以及常用的一些通用功能,任何基于intellij開發的ide都可以通過該方式制作插件,例如android studio(as),本篇也将基于android studio進行展開介紹,讀者将從0到1學習到 android studio插件開發。

ide插件是将一些功能內建到了ide界面當中,當我們使用ide進行開發工作時能很友善的通過ui界面使用這些功能,例如大家熟悉的project工程目錄,gradle工具欄,ide底部的run、terminal、build界面等,都是通過ide插件來實作的,可以說大部分需要通過指令行執行、或使用者手動的一些操作都可以通過插件實作,并以ui的形式呈現。

如下圖:左圖為android studio ide界面右側gradle工具欄,包含了很多gradle任務,點選ui的效果等同于使用者在指令行中輸入gradle指令。右圖為ide頂部菜單欄版本控制部分,其中對于版本的送出、拉取等按鈕等價于指令行輸入對應指令。

<center class="half">

<img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin1.png" width="300"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin2.png" width="300"/> </center>

筆者作為中台部門開發者,經常涉及到一些通用的功能的開發,并以工具或元件等形式交由外部使用。是以如何降低使用者學習成本、提高工作效率是我的目标,而這些優化方向都離不開巧妙的使用工具。例如本次要介紹的ide插件的開發背景就是以此為目标:将原本需要使用指令行完成的工作、或者學習成本較高的操作通過ui進行包裝,并且依附在原生的as界面中,通過ui的互動大幅降低使用者學習成本,同時提升使用體驗。

舉例對比一下,下面兩幅圖檔是某個工程自動搭建功能的截圖,左右兩圖分别為使用指令行和使用as插件的體驗對比,可以看到左側在使用cli指令行進行工程搭建時界面資訊不夠簡潔明了,且使用者互動體驗較差,使用者必須在使用前閱讀文檔,并且沒有容錯機制,輸錯就得從頭開始。而相同的功能使用右側as插件的體驗則好很多,不僅各條資訊清楚明了,還能拓展更多細節功能,如動态檢驗使用者輸入,輸入無誤才可進行下一步等等,使用者完全可以在零知識背景的情況下使用該插件并輕松完成所有功能操作,而且接近原生的界面更美觀。

<img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin3.png" width="400"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin4.png" width="400"/> </center>

在開發第一個插件前,我們要下載下傳正确的開發工具,在jetbrain官網中下載下傳<code>intellij idea</code>,下載下傳連結。

這裡使用的開發工具是intellij idea而不是android studio,因為as是基于intellij為模版開發的,ide插件必須通過intellij開發、釋出,再安裝到android studio中才能使用。

我們需要确認我們使用的android studio是基于哪個intellij版本。這很重要,和你目前使用的android studio版本相同能讓你在調試時很友善,而新版的intellij包含的features在你使用的as上可能并沒有,導緻插件無法安裝,或提示相容性報錯。(圖中的報錯也會出現在未開啟向高版本相容時發生,開發時按需開啟) 。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin5.png" width="400"/&gt;&lt;/center&gt;

下載下傳時請跟随這個步驟:

打開你的android studio,檢視版本号(the build number),這就是我們需要的intellij版本号。

在下載下傳頁面,點選other versions,找到對應的intellij版本,下載下傳安裝即可。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin6.png" width="300"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin7.png" width="500"/&gt; &lt;/center&gt;

這部分也可參照官網:https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html

建立新工程包含兩個向導頁面,【選擇工程模版架構 + 填寫插件工程資訊】,按圖中配置即可。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin8.png" width="350"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin9.png" width="350"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin10.png" width="100" hight="300"/&gt; &lt;/center&gt;

完成兩步向導程式後自動建立工程,我們需要先了解兩個核心檔案:【build.gradle + plugin.xml】,并做一些前置的配置工作。

因為build.gradle和android工程中的建構檔案非常類似,這裡隻解釋android中沒有的配置。

version:intellij閉包建立時隻帶一個屬性 version,該屬性代表用來建構這個插件的intellij 平台ide的版本,如果我們在開發時調用【runide】這個gradle task,一個基于這個版本的intellij ide執行個體就會被建立。

localpath:因為我們希望在as的環境下測試我們的插件,是以我們需要将as作為我們插件的一個依賴,增加一個屬性叫localpath指定本機android studio應用程式contents的安裝目錄,一個基于這個版本的android studio執行個體就會被建立(注意localpath不能和version屬性同時使用,因為我們本地的as路徑中已經有了版本資訊)。

plugins:添加開發需要的依賴插件。可以在這裡添加很多我們想用的插件,比如我們想在插件中執行git指令,我們可以添加 ’git4idea‘ plugin。

在resource檔案夾下可以找到plugin.xml檔案,這個檔案中可以配置我們插件的各項屬性,核心功能是注冊我們插件包含的components和service(功能類實作後還需要在這裡進行注冊才能使用,類似在androidmanifest.xml中聲明activity和service)。

聲明我們的插件需要并且和as相相容:增加android和android studio modules作為依賴。

配置完成後我們可以嘗試運作插件工程,具體位置在gradle工具欄 <code>項目名稱/tasks/intellij/runide</code>路徑。運作<code>runide</code>任務,因為我們配置了android studio為啟動路徑,是以一個android studio模拟ide會打開,所有内容都和我們本地的android studio沒有差别。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin11.png" width="270"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin12.png" width="670"/&gt; &lt;/center&gt;

actions官方介紹: the system of actions allows plugins to add their own items to idea menus and toolbars. an action is a class, derived from the anaction. actions是使用者調用插件功能最常見的方式,如下圖的工具目錄是開發者經常用到的,裡面所有的可選項都是一個action,可以進一步展開的則是action group。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin13.png" width="500"/&gt; &lt;/center&gt;

兩個步驟:

【code implementation - 實作action的具體代碼邏輯】:決定了這個action在哪個context下有效,并且在ui中被選擇後的功能(繼承父類anaction并重寫actionperformed()方法,用于action被執行後的回調)。

【registered - 在配置檔案中注冊】:決定了這個action在ide界面的哪個位置出現(建立新的group或存放進現有的actiongroup,以及在group中的位置)。

兩個條件達成,action就可以從intellij platform中獲得使用者執行動作後的回調,例子: ‘helloworld’。

實作了以上兩步後,運作<code>runide</code> task,頂部的主菜單欄末尾出現了我們添加的actiongroup,展開可看見helloworldaction,點選action,右下角彈出“hello world”提示資訊。我們不僅可以建立group來放置action,還可以将action添加進ide已有的group當中,如下左圖中,我們将helloworld action添加進了ide的cutcopypastegroup,和複制粘貼等action放在了一起。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin14.png" width="800"/&gt; &lt;/center&gt;

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin15.png" width="400"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin16.png" width="400"/&gt; &lt;/center&gt;

plugin.xml檔案中actions的group可以更為複雜,group可以互相包含,并形成工具欄或菜單(如下圖),有興趣的同學可以拉取demo(文章末尾)體驗一下。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin17.png" width="800"/&gt; &lt;/center&gt;

wizard意為向導程式,就是指引使用者完成某個功能的程式,通常為單個或多個指引界面組成。例如下面兩幅圖為android studio中經典的建立新工程視窗,就包含兩個頁面的向導程式。下面将介紹如何制作出和圖中主題完全相同的向導程式。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin18.png" width="480"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin19.png" width="480"/&gt; &lt;/center&gt;

向導程式的基礎類屬于android.jar,由以下幾個核心類構成:

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin20.png" width="300"/&gt; &lt;/center&gt;

向導程式的“主類”,一個modelwizard包含了一個modelwizardstep的隊列(step隊列是一個有序的隊列,并且每個step都包含了它的下一個step的引用),當一個modelwizard結束時,它會周遊所有steps,通路step對應的wizardmodel,并調用wizardmodel#handlefinished()方法。

一個step就是wizard向導程式中的一個單獨頁面,它負責建立一個ui界面呈現給使用者,确定頁面上的資訊是否有效,并且将使用者資料儲存在對應的wizardmodel對象中。

可以設定可見性的step,可以通過前一個step來控制跟在其後的step可見性,例如一個step提供了一些選項給使用者,并根據使用者的選擇來決定之後哪些steps可以被展示。

model就是資料的集合,這些資料由wizard中的每個step進行填充。多個step可以共享同一個model,核心的方法是handlefinished(),當使用者在向導程式中點選了“finish”按鈕時,wizard結束,這個方法将會被調用進行最終的邏輯處理。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin21.jpeg" width="900"/&gt; &lt;/center&gt;

同樣在android.jar庫中,和wizard同級的名叫ui的包中提供了一個很友善的類,幫助使用者建立as樣式的modelwizard,隻需将modelwizard對象作為參數放入studiowizarddialogbuilder的構造器中即可。使用as樣式包裝我們的插件ui能讓使用者使用時更有原生的感覺,體驗更好。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin22.png" width="480"/&gt;&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin23.png" width="480"/&gt; &lt;/center&gt;

tool windows是ide的子視窗。這些視窗通常都在ide主視窗的“邊框”上擁有屬于自己的一個tool window button,點選後将在ide主視窗的左、右、下側激活panel來展示資訊。如下圖左一gradle工具欄。建立tool window需要提供一個jpanel,并通過toolwindowfactory來實作。

performs lazy initialization of a tool window registered in {@code plugin.xml}.

使用者必須建立toolwindowfactory的實作類,并實作createtoolwindowcontent()方法,在該方法中初始化tool window的ui,并添加到android studio中。toolwindowfactory提供了懶加載機制,這樣實作的好處是未使用的工具視窗不會增加啟動時間或導緻記憶體使用方面的任何開銷:如果使用者沒有與該工具視窗互動,相關代碼就不會被加載和執行。

在插件中使用tool windows有兩種形式:

declarative setup:可以了解為靜态,在<code>plugin.xml</code>檔案中注冊,始終可見使用者随時都可以使用。

programmatic setup:通過api接口動态注入,可以在一些操作前後出現和隐藏。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin24.png" width="320"/&gt;

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin25.png" width="320"/&gt;

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin26.png" width="320"/&gt; &lt;/center&gt;

wizard向導程式和tool window工具欄都需要ui作為面闆内容的填充,根本上來說,需要的隻是一個内容豐富的jpanel作為content。as 插件中的ui大量的使用了java swing元件,是以對swing比較熟悉的同學上手會很快,這裡介紹幾種在as插件中生成ui的技巧。

new --&gt; swing ui designer --&gt; gui form 填寫資訊後就會生成對可視化的.form檔案以及綁定的java類,在對應的java檔案中增加一個getrootpanel方法擷取root panel就可以将建構好的panel給到向導程式或工具欄中使用。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin27.png" /&gt; &lt;/center&gt;

上面提到的gui form有一個缺點,隻能使用java,并且.fome檔案和.java檔案強綁定,我們也無法單獨使用這個生成的java檔案,并且當我們想編寫純kotlin代碼時,gui form就顯得不好用了。

eclipse是多數同學剛接觸java時使用的經典ide,其中有一個windowbuilder插件同樣可以可視化建立gui界面,但是相比于gui form,windowbuilder生成的是單獨的.java檔案,使用者在gui可視化界面操作的每個步驟都會生成對應的源碼,我們可以直接copy這些代碼到as插件當中,并使用“convert java code to kotlin”功能,将這些代碼一鍵轉為kotlin代碼,非常友善(更重要的是,windowbuilder的使用體驗個人覺得更好)。

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin28.png"/&gt;;

&lt;img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/asplugin29.png"/&gt;;

&lt;/center&gt;

intellij 插件官方提供的一些基于kotlin的領域特定語言,可以在kotlin代碼中寫ui,優點是代碼優美,缺點是累,具體可參考官網的指引https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl.html

有時我們希望能儲存使用者在插件中的操作或一些配置,避免重複的工作以及必要資料的讀取,或避免使用者重複多次輸入。intellij platform提供了一些友善的api來做資料持久化。

這是intellij插件開發中的基礎能力,分為三種不同的類型,當我們想要在ide插件的不同生命周期進行一些狀态和邏輯上的處理,就可以使用這三種服務,例如:持久化狀态、訂閱事件、application啟動/關閉時、project被打開/關閉時。

service 接口類型

作用描述

application level

idea啟動時會初始化,idea生命周期中僅存在一個執行個體

project level

idea 會為每一個 project 執行個體建立一個 project 級别的執行個體

module level

idea 會為每一個 project 的加載過的module執行個體module級别的執行個體,在多子產品項目中容易導緻記憶體洩露

這塊代碼是模拟在ide啟動時,自動檢驗目前是否存在新版本的功能,若有新版本則進行更新操作,就是使用了持久化存儲來實作的。

這是一個簡單的key-value資料結構,可以當作map使用,用于儲存application 和 project 級别的資料。

複雜類型的資料結構使用persistentstatecomponent,可以指定持久化的存儲位置。

建立一個persistentstatecomponent的實作類,t表示需要持久化的資料結構類型,可以是任意類,甚至是實作類本身,然後重寫getstate和loadstate方法。

若要指定存儲的位置,需要在顯現類上增加@state注解。

若不希望其中的某個字段被持久化,可以在該字段上增加@transient 注解。

persistentstatecomponent的實作類需要在plugin.xml中注冊為 service後使用。

打包:在gradle工具欄中運作assemble任務,即可在/build/distribution/{插件名稱}-{插件版本}.zip路徑下找到打包好的插件zip包。

本地安裝:還沒将插件釋出到插件市場前我們可以選擇安裝本地插件,打開as菜單欄/android studio/preference/plugins/install plugin from disk... 安裝後即可使用。

釋出插件市場:

通路https://hub.jetbrains.com/,建立賬号。

使用賬号登陸jetbrains marketplace https://plugins.jetbrains.com/,釋出插件(需官方稽核2個工作日)。

插件的第一個版本都需要在網站手動上傳,之後的版本可以使用hub賬号中的token自動更新。

回顧開發過程:ide插件的核心步驟:安裝正确版本intellij --&gt; 配置工程 --&gt; 建立action --&gt; 将複雜流程注入wizard向導程式或toolwindow工具欄(同時建立ui) --&gt; 使用資料持久化儲存必要資料 --&gt; 打包&amp;安裝&amp;釋出。

筆者覺得ide插件開發的難點主要是摸索的過程,ide插件較冷門,網上介紹的文章很少,官網介紹了一些功能群組件後也沒有詳細的api指引,令人有點無從下手。最終,通過反編譯檢視一些官方插件(firebase、flutter等)的源碼,以及收集google、youtube和各大部落格的資訊,終于将as插件的一期雛形打造完畢,也将學到的一些常用的通用能力在本文中進行整理,希望能幫到之後想要接觸as插件開發的同學。

https://github.com/chentaozhou/chentaodemo

位元組跳動終端技術團隊(client infrastructure)是大前端基礎技術的全球化研發團隊(分别在北京、上海、杭州、深圳、廣州、新加坡和美國山景城設有研發團隊),負責整個位元組跳動的大前端基礎設施建設,提升公司全産品線的性能、穩定性和工程效率;支援的産品包括但不限于抖音、今日頭條、西瓜視訊、飛書、懂車帝等,在移動端、web、desktop等各終端都有深入研究。

就是現在!用戶端/前端/服務端/端智能算法/測試開發 面向全球範圍招聘!一起來用技術改變世界,感興趣請聯系 [email protected],郵件主題 履歷-姓名-求職意向-期望城市-電話。

請添加連結描述是位元組跳動終端技術團隊過去九年在抖音、今日頭條、西瓜視訊、飛書、懂車帝等 app 的研發實踐成果,面向移動研發、前端開發、qa、 運維、産品經理、項目經理以及營運角色,提供一站式整體研發解決方案,助力企業研發模式更新,降低企業研發綜合成本。