【Android】AndroidStudio編寫外掛超詳細教程(一)
AndroidStudio外掛超詳細教程(一)
本教程從0開始,邊探索邊講解思路,保證詳細~~~寫的時候發現有點長,準備分2-3次寫完吧。
最近專案試了一下Android元件化架構,感覺坑還是蠻多的,首先ButterKnife就用不了了,各種R和R2檔案的切換就煩死,剛開始看了下ButterKnife Zelezny
外掛的原始碼,增加了R檔案的選擇,感覺在元件化中還是不太好用,最後還是用回了痛苦的findViewById,正好也看了看android studio編寫外掛的相關知識,今天就和大家一起擼一個findViewById外掛!
- 環境配置
- 專案目錄結構
- 配置外掛資訊
- 獲取資原始檔名
環境配置
Android Studio是基於IntelliJ專門為Android定製的IDE,是沒有辦法編寫IDE的外掛的,所以我們首先要下載一個開發Java用的IntelliJ IDEA。具體下載過程就不贅述了,網上教程一大堆,咱們也不是專門開發Java,隨便下載一個就好。
下載好開啟後,我們看到了一個熟悉的頁面,和android studio差不多,選擇新建一個專案。左邊選擇IntelliJ Platform Plugin
,右上方Project SDK第一次進入應該是沒有配置的。
我們選擇New,選擇一個SDK。這裡系統一般Idea的根目錄,我們直接確定即可。接下來系統會讓你選擇一個JDK,也就是java環境,同樣也會定位到相應位置,如果沒有定位到,我們使用開發Android時JDK的路徑就可以了。檢視Android Studio中的JDK路徑:
配置好環境後,我們就可以愉快的編寫外掛啦!
專案目錄結構
新建好的專案目錄結構比較簡單,沒有什麼多餘的檔案。大概長這樣。
其中com.xxx.xxx剛建立好時是沒有的,需要自己建包。
.idea: idea的一些配置資訊。
out: 編譯生成的一些.class檔案,有點類似於android的build資料夾。
resources/META-INF/plugin.xml: 外掛的一些描述資訊,和我們接下來要寫的外掛操作“Action”的配置。類似android中的Manifest檔案。
src: 這裡就是我們要寫程式碼的地方啦。
.iml: 專案的一些配置資訊,一般不用去管,和android的.iml一樣。
External Libraries: 這個也和android一樣,時引用的第三方庫。
整體看下來,編寫外掛程式碼和我們平時寫android程式碼的時候非常類似。還是非常容易理解的。使用的語言也就是java語言,學習成本很低,但是可以開發出一些非常好玩的外掛。
配置外掛資訊
好,各個檔案的作用我們已經大概瞭解了,接下來,我們先來配置一下我們的外掛資訊,也就是配置我們的resources/META-INF/plugin.xml檔案。配置檔案裡有很詳細的英文描述。這裡只簡單的說一下。
id: 外掛唯一的id。
name: 外掛顯示的名字。
version: 外掛版本。
vendor: 裡面分別是你的郵箱,公司網站或個人網站,公司名。
description: 外掛的描述。
change-notes: 更新文件。
extensions defaultExtensionNs: 預設依賴的庫。
actions: “註冊”一會編寫的動作Action類。
具體填寫的東西展示出來是什麼樣子,大家可以去android studio的外掛倉庫中看看,對應填寫相應的內容就好。如ButterKnife Zelezny填寫的配置資訊長這樣。
獲取資原始檔名
好,接下來是大家最喜歡的敲程式碼了!其實android studio中,每個按鈕都相當於一個系統寫好的外掛,點選這些按鈕執行的動作,都是在對應的Action中寫好的。我們要做的,就是給IDE新增一個我們自己的按鈕,並且寫一個做我們想要操作的Action。
怎麼做呢?首先,我們在我們建立好的包中new一個Action。
點選後出現如下彈窗,讓我們配置Action的一些資訊。
其中,Action Id,Class Name就不多說了,Name為顯示給使用者的動作名稱,Description為操作的描述。
Groups是比較重要的,他代表了我們按鈕展示的位置。比如選擇GenerateGroup,就是在Generate中顯示(Windows中快捷鍵alt+insert,Mac快捷鍵control+enter)。還有build、code(顯示在選單欄上build、code按鈕中)等等一系列Groups的位置,大家根據需要自己選擇。不知道意思的網上查一下就好。
右邊Actions是選擇按鈕位置的,First和Last分別為選單最上方和最下方,點選Actions中的按鈕,可以選擇在該按鈕的下方和上方。我這裡模仿了ButterKnife Zelezny選擇了GenerateGroup,並且放在了最下方。執行時的效果是這樣的:
後面的Keyboard Shortcuts中的First和Second就是我們自定義的快捷鍵了,這裡注意快捷鍵不要和其他系統的快捷鍵衝突。
配置好後,我們點選ok,就能看到我們新建好的類了。
public class FindViewsAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
}
}
同時,我們的plugin.xml中也自動幫我們註冊好了Action。在action標籤中,我們還可以給action增加一個icon欄位來設定按鈕前面的圖示。
一步一步來,Acton已經建立好了,接下來就是寫我們的方法了,我們先看一下自動繼承的這個AnAction類有什麼我們可以用的方法。
看完我就更懵逼了。除了一個自動重寫的actionPerformed
大概能看出來是按鈕被點選的操作外,似乎沒有用的上的方法啊。AnActionEvent裡也就是有個getProject
方法感覺對我們有點用。
到這裡,我是徹底不知道咋弄了。慢慢來,我們先來捋一捋需求。我們要做的是一鍵findViewById,首先要獲取到游標所在的layout檔案,然後讀取出layout.xml檔案裡的所有vieiw的id,最後把再程式碼中生成全域性的變數名,並且繫結findViewById找到的控制元件。
那第一步就是找到游標所在的layout.xml檔案。那肯定要用到游標了。根據需求找方法,我發現anActionEvent
中有一個getData
方法,這個方法的引數中正好有一個DataKeys.EDITOR
,這個似乎是我們想要的啊,得到之後,果然有一個游標的單詞caret
。
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
Editor editor = anActionEvent.getData(DataKeys.EDITOR);
if (editor != null) {
//得到編輯器的游標類
CaretModel caret = editor.getCaretModel();
}
}
得到游標之後,我們應該就可以找到我們需要的資原始檔了。但是,看了半天方法。。也沒找到得到游標所在檔案的方法。。沒辦法,看一下ButterKnifeZelezny
的原始碼吧。
在原始碼裡,我發現了PsiUtilBase.getPsiFileInEditor()
這個方法。並且很多檔案操作都用到了PsiFile,這個是幹什麼的呢?還是看一下官網咖。本人英語捉急,不過文件也比較簡單,大概還是能看出點東西的。附上官網地址:IDEA插開發工具SDK文件
進入官網後,我們可以左上角搜尋一下psi,然後找到psi files,看一下英文全稱我們概可以瞭解到,這是一個表示檔案結構的介面,PsiFile是一個基類,裡面還有PsiJavaFile
和XmlFile
。那我們獲取xml檔案中的id,要拿到的肯定是XMLFile這個類。
我們再往下翻,其中有兩個標題比較重要。分別是,我們怎麼得到這個類,還有我們能用這個類做什麼。
我們看到三個比較重要的方法。
psiElement.getContainingFile(): Element我們都知道是元素的意思,通過這個方法,我們大概瞭解到,用游標獲取檔案中選中的詞,大概率需要用到元素psiElement
。
FilenameIndex.getFilesByName(project, name,scope): 通過檔名獲取檔案,這個我們一會肯定也會用到。
psiFile.accept(new PsiRecursiveElementWalkingVisitor()…): 遞迴遞迴元素,我們獲取id的時候肯定要遞迴xml檔案的,這裡IDEA已經幫我們寫好了遞迴的方法。
正好搜尋欄下面有一個PSI Elements的介紹,不需要多看,我們只看文件標出來的兩個方法。
一個是anActionEvent.getData(LangDataKeys.PSI_ELEMENT)
,一個是psiPfile.findElementAt()
。
講道理這裡我們應該用第一個方法拿到實體類的,但是第一個方法打印出來的是xml檔案的id,所以這裡我們只能用第二個方法,根據游標位置找到元素,然後用檔名找到對應的xml檔案實體。
PsiFile psiFile = anActionEvent.getData(DataKeys.PSI_FILE);
Editor editor = anActionEvent.getData(DataKeys.EDITOR);
CaretModel caret = editor.getCaretModel();
PsiElement psiElementA = file.findElementAt(offset);
//(R.layout.activity_main)由於游標在‘n’和‘)’中間的時候會打印出')'
//所以這裡必須獲取兩個,然後進行判斷。
PsiElement psiElementB = file.findElementAt(offset - 1);
//System.out.println(psiElementA.getText());
//列印一下發現確實打印出了檔名。
接下來我們判斷一下這兩個element哪個是正確的檔名
//getParent()可以得到元素包括'.'在內的字串。
//getFirstChil()則可以得到整個字串開頭的字元
String firstChild=psiElementA.getParent().getFirstChild().getText();
if ("R.layout".equals(firstChild)) {
//psiElementA正確就用A,psiElementB正確就用B。
//這裡只寫虛擬碼了,全部程式碼之後給出下載。
}
至此,我們得到了xml檔案的名字psiElement.getText
,把名字末尾拼接上字尾名,就能得到完整的檔名了。
String name = String.format("%s.xml", psiElement.getText());