IntelliJ IDEA編寫插件入門(1):自動創(chuàng)建代碼

當項目引入mvp框架的話乖菱,雖然代碼結(jié)構(gòu)邏輯簡單了助币,但是創(chuàng)建類的過程太繁瑣了并且都是千篇一律的静陈,所以我們有沒有這樣的工具代替呢桑李,答案是有的!

在寫這份文章之前窿给,我是通過http://lib.csdn.net/article/android/63052該文章學(xué)習(xí)的贵白,然后下面的內(nèi)容是我在創(chuàng)建的過程記錄的。如果下面文章依然看不懂的話崩泡,可以在這個鏈接下載源碼更仔細能看到禁荒。但是不知道這個源碼是不是舊版的原因,反正我這邊是運行不了的角撞。

1. 開發(fā)工具下載

下載IntelliJ IDEA呛伴,用過studio都知道它是在IntelliJ IDEA基礎(chǔ)上開發(fā)的。下載地址:https://www.jetbrains.com/idea/
我下載的是2017.3.1的版本谒所。

2. 創(chuàng)建項目

image.png
(1)點擊Next热康,創(chuàng)建名稱起個叫:MvpAutomaticCreation

項目結(jié)構(gòu):
image.png
(2)點擊src,在這里選擇你的sdk地址(如果沒有配置sdk的話,是不能使用一些別的功能的)
image.png
(3)一切配置完后劣领,然后就可以開始創(chuàng)建類了姐军,點擊src,在這里創(chuàng)建Action
image.png
(4)填寫Action信息
image.png
屬性 描述
Action ID 這個Action的唯一標示
Class Name 類名稱
Name 這個插件在菜單上的名稱
Description 關(guān)于這個插件的描述信息
Groups 代表這個插件會出現(xiàn)的位置。比如想讓這個插件出現(xiàn)在Code菜單下的第一次選項,就如圖中一樣選擇CodeMenu(Code)奕锌,右邊Anchor選擇First
Keyboard Shortcuts 快捷鍵設(shè)置著觉。圖中設(shè)置Alt+T。

點擊ok后惊暴,就創(chuàng)建完畢了饼丘,然后我們想要編輯的話,如圖中的配置里面的actions,比如修改快捷鍵
image.png
(5)編寫代碼

在編寫代碼之前辽话,我們肯定已經(jīng)知道我們想要生成什么樣的代碼了肄鸽,每個人寫的框架都不一樣,那么假設(shè)我們現(xiàn)在寫的一個mvp是這么一個架構(gòu)油啤,圖中的Base可以忽略典徘,因為這是兩個項目同時引用的這一個類。
image.png

注意:我這邊編寫的生成類村砂,因為考慮到實際使用,我是不寫基類的生成的

(5.1)首先是基于Activity的mvp有3個類屹逛,讓我們先創(chuàng)建3個:
image.png

后綴名當然是txt了础废,創(chuàng)建如下:
image.png

打開其中一個文件:
image.png

代碼里面的$packagename、$basepackagename罕模、$author评腺、$description、$date淑掌、$name這些字符都是可以動態(tài)替換的蒿讥。

(5.2)開始創(chuàng)建插件ui了
image.png

可視化編輯:
image.png

最終效果:
image.png

(5.3)接下來解析對應(yīng)view的控制類,請看注釋

import javax.swing.*;
import java.awt.event.*;

public class MvpAutomaticCreation extends JDialog {
    private JPanel contentPane;
    private JButton buttonOK;
    private JButton buttonCancel;
    private JTextField textField1;
    private JTextField textField2;

    private DialogCallBack mCallBack;

    /**
     * 在自動創(chuàng)建該類的時候,添加一個回調(diào)函數(shù)DialogCallBack抛腕,并且改變了onOK這個方法
     * @param callBack 回調(diào)函數(shù)
     */
    public MvpAutomaticCreation(DialogCallBack callBack) {
        this.mCallBack = callBack;
        setTitle("MvpAutomaticCreation");
        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(buttonOK);
        buttonOK.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });
        buttonCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });
        // call onCancel() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });
        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    private void onOK() {
        // add your code here
        if (null != mCallBack){
            mCallBack.ok(textField1.getText().trim(), textField2.getText().trim());
        }
        dispose();
    }

    private void onCancel() {
        // add your code here if necessary
        dispose();
    }

    // 這個作廢芋绸,去掉,無用
//    public static void main(String[] args) {
//        MvpAutomaticCreation dialog = new MvpAutomaticCreation();
//        dialog.pack();
//        dialog.setVisible(true);
//        System.exit(0);
//    }

    public interface DialogCallBack{
        void ok(String author, String moduleName);
    }

}

(5.4)然后接著看執(zhí)行類

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 執(zhí)行類
 * (1)會獲取包名担敌,然后讀取模板文件摔敛,替換模板文件中動態(tài)字符,在Dialog輸入的作者和模塊名稱也會替換模板中字符全封,
 * (2)最后通過包名路徑生成類文件
 *
 *  后面我會根據(jù)實際工作需求马昙,會想辦法改進選擇生成fragment還是activity
 *  而作者名稱也應(yīng)該能設(shè)置是默認的
 *
 */
public class MvpAutomaticCreationAction extends AnAction {

    private Project project;
    private String packageName = "";//包名
    private String mAuthor;//作者
    private String mModuleName;//模塊名稱

    /**
     * 創(chuàng)建類型枚舉
     */
    private enum  CodeType {
        Activity, Fragment, Contract, Presenter, BaseView, BasePresenter
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        project = e.getData(PlatformDataKeys.PROJECT);
        packageName = getPackageName();
        refreshProject(e);
        init();
    }

    /**
     * 刷新項目
     * @param e
     */
    private void refreshProject(AnActionEvent e) {
        e.getProject().getBaseDir().refresh(false, true);
    }

    /**
     * 初始化Dialog
     */
    private void init(){
        MvpAutomaticCreation myDialog = new MvpAutomaticCreation(new MvpAutomaticCreation.DialogCallBack() {
            @Override
            public void ok(String author, String moduleName) {
                // 實例化ok事件
                mAuthor = author;
                mModuleName = moduleName;
                createClassFiles();
                Messages.showInfoMessage(project,"create mvp code success","title");
            }
        });
        myDialog.setVisible(true);

    }

    /**
     * 生成類文件
     */
    private void createClassFiles() {
        createClassFile(CodeType.Activity);
        createClassFile(CodeType.Fragment);
        createClassFile(CodeType.Contract);
        createClassFile(CodeType.Presenter);
//        createBaseClassFile(CodeType.BaseView); // 暫時作廢
//        createBaseClassFile(CodeType.BasePresenter); // 暫時作廢
    }

    /**
     * 生成mvp框架代碼
     * @param codeType 類型
     */
    private void createClassFile(CodeType codeType) {
        String fileName = "";
        String content = "";
        String appPath = getAppPath();
        switch (codeType){
            case Activity:
                fileName = "TemplateActivity.txt";
                content = ReadTemplateFile(fileName);
                content = dealTemplateContent(content);
                writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Activity.java");
                break;
            case Fragment:
                fileName = "TemplateFragment.txt";
                content = ReadTemplateFile(fileName);
                content = dealTemplateContent(content);
                writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Fragment.java");
                break;
            case Contract:
                fileName = "TemplateContract.txt";
                content = ReadTemplateFile(fileName);
                content = dealTemplateContent(content);
                writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Contract.java");
                break;
            case Presenter:
                fileName = "TemplatePresenter.txt";
                content = ReadTemplateFile(fileName);
                content = dealTemplateContent(content);
                writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Presenter.java");
                break;
        }
    }

    /**
     * 生成
     * @param content 類中的內(nèi)容
     * @param classPath 類文件路徑
     * @param className 類文件名稱
     */
    private void writeToFile(String content, String classPath, String className) {
        try {
            File floder = new File(classPath);
            if (!floder.exists()){
                floder.mkdirs();
            }

            File file = new File(classPath + "/" + className);
            if (!file.exists()) {
                file.createNewFile();
            }

            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(content);
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 替換模板中字符
     * @param content
     * @return
     */
    private String dealTemplateContent(String content) {
        content = content.replace("$name", mModuleName);
        if (content.contains("$packagename")){
            content = content.replace("$packagename", packageName + "." + mModuleName.toLowerCase());
        }
        if (content.contains("$basepackagename")){
            content = content.replace("$basepackagename", packageName + ".base");
        }
        content = content.replace("$author", mAuthor);
        content = content.replace("$date", getDate());
        return content;
    }

    /**
     * 獲取包名文件路徑
     * @return
     */
    private String getAppPath(){
        String packagePath = packageName.replace(".", "/");
        String appPath = project.getBasePath() + "/App/src/main/java/" + packagePath + "/";
        return appPath;
    }

    /**
     * 獲取當前時間
     * @return
     */
    public String getDate() {
        Date currentTime = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
        String dateString = formatter.format(currentTime);
        return dateString;
    }

    /**
     * 從AndroidManifest.xml文件中獲取當前app的包名
     * @return 當前app的包名
     */
    private String getPackageName() {
        String package_name = "";
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(project.getBasePath() + "/App/src/main/AndroidManifest.xml");

            NodeList nodeList = doc.getElementsByTagName("manifest");
            for (int i = 0; i < nodeList.getLength(); i++){
                Node node = nodeList.item(i);
                Element element = (Element) node;
                package_name = element.getAttribute("package");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return package_name;
    }

    /**
     * 讀取模板文件中的字符內(nèi)容
     * @param fileName 模板文件名
     */
    private String ReadTemplateFile(String fileName) {
        InputStream in = null;
        in = this.getClass().getResourceAsStream("/Template/" + fileName);
        String content = "";
        try {
            content = new String(readStream(in));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 讀取數(shù)據(jù)
     * @param inputStream
     */
    private byte[] readStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) != -1){
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            outputStream.close();
            inputStream.close();
        }

        return outputStream.toByteArray();
    }

}


(6)部署插件

(6.1)找到該文件,填一些資料
image.png
image.png

記住這個版本號要改成145刹悴,否則Android Studio導(dǎo)入會報不兼容問題
image.png

(6.2)然后生成


image.png

會創(chuàng)建一個jar包行楞,拿到這個jar包就可以安裝到Android Studio了。


image.png
(7)部署插件

點擊Install plugin from disk...土匀,選擇自己生成的jar子房,就能導(dǎo)入成功了。


image.png

然后重啟Android studio,在菜單這里就能看到了
image.png
(7) 錯誤匯總

當點擊發(fā)現(xiàn)沒任何反應(yīng)的時候,我們查詢bug發(fā)現(xiàn)這么一個提示:

null
java.lang.NullPointerException
at com.intellij.ide.SystemHealthMonitor.getActionName

注意:在創(chuàng)建自定義的XXAction類時池颈,需要保證自己的XXAction類在某個package中尾序,否則會出現(xiàn)如下之類的報錯:

示例如我google中查詢:


image.png

最近找到一個很不錯的插件源碼合集
https://github.com/balsikandar/Android-Studio-Plugins
http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/plugins-develop.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市躯砰,隨后出現(xiàn)的幾起案子每币,更是在濱河造成了極大的恐慌,老刑警劉巖琢歇,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰怠,死亡現(xiàn)場離奇詭異,居然都是意外死亡李茫,警方通過查閱死者的電腦和手機揭保,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魄宏,“玉大人秸侣,你說我怎么就攤上這事〕杌ィ” “怎么了味榛?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長予跌。 經(jīng)常有香客問我搏色,道長,這世上最難降的妖魔是什么券册? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任频轿,我火速辦了婚禮,結(jié)果婚禮上烁焙,老公的妹妹穿的比我還像新娘航邢。我一直安慰自己,他們只是感情好骄蝇,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布翠忠。 她就那樣靜靜地躺著,像睡著了一般乞榨。 火紅的嫁衣襯著肌膚如雪秽之。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天吃既,我揣著相機與錄音考榨,去河邊找鬼。 笑死鹦倚,一個胖子當著我的面吹牛河质,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼掀鹅,長吁一口氣:“原來是場噩夢啊……” “哼散休!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乐尊,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤戚丸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扔嵌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體限府,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年痢缎,在試婚紗的時候發(fā)現(xiàn)自己被綠了胁勺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡独旷,死狀恐怖署穗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嵌洼,我是刑警寧澤案疲,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站咱台,受9級特大地震影響络拌,放射性物質(zhì)發(fā)生泄漏俭驮。R本人自食惡果不足惜回溺,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望混萝。 院中可真熱鬧遗遵,春花似錦、人聲如沸逸嘀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崭倘。三九已至翼岁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間司光,已是汗流浹背琅坡。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留残家,地道東北人榆俺。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茴晋。 傳聞我的和親對象是個殘疾皇子陪捷,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容