1. 程式人生 > >【Android】AndroidStudio編寫外掛超詳細教程(一)

【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路徑:
檢視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是一個基類,裡面還有PsiJavaFileXmlFile。那我們獲取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());