IntelliJ IDEA编写插件入门(1):自动创建代码

当项目引入mvp框架的话,虽然代码结构逻辑简单了,但是创建类的过程太繁琐了并且都是千篇一律的,所以我们有没有这样的工具代替呢,答案是有的!

在写这份文章之前,我是通过http://lib.csdn.net/article/android/63052该文章学习的,然后下面的内容是我在创建的过程记录的。如果下面文章依然看不懂的话,可以在这个链接下载源码更仔细能看到。但是不知道这个源码是不是旧版的原因,反正我这边是运行不了的。

1. 开发工具下载

下载IntelliJ IDEA,用过studio都知道它是在IntelliJ IDEA基础上开发的。下载地址:https://www.jetbrains.com/idea/
我下载的是2017.3.1的版本。

2. 创建项目

image.png
(1)点击Next,创建名称起个叫:MvpAutomaticCreation

项目结构:
image.png
(2)点击src,在这里选择你的sdk地址(如果没有配置sdk的话,是不能使用一些别的功能的)
image.png
(3)一切配置完后,然后就可以开始创建类了,点击src,在这里创建Action
image.png
(4)填写Action信息
image.png
属性 描述
Action ID 这个Action的唯一标示
Class Name 类名称
Name 这个插件在菜单上的名称
Description 关于这个插件的描述信息
Groups 代表这个插件会出现的位置。比如想让这个插件出现在Code菜单下的第一次选项,就如图中一样选择CodeMenu(Code),右边Anchor选择First
Keyboard Shortcuts 快捷键设置。图中设置Alt+T。

点击ok后,就创建完毕了,然后我们想要编辑的话,如图中的配置里面的actions,比如修改快捷键
image.png
(5)编写代码

在编写代码之前,我们肯定已经知道我们想要生成什么样的代码了,每个人写的框架都不一样,那么假设我们现在写的一个mvp是这么一个架构,图中的Base可以忽略,因为这是两个项目同时引用的这一个类。
image.png

注意:我这边编写的生成类,因为考虑到实际使用,我是不写基类的生成的

(5.1)首先是基于Activity的mvp有3个类,让我们先创建3个:
image.png

后缀名当然是txt了,创建如下:
image.png

打开其中一个文件:
image.png

代码里面的$packagename、$basepackagename、$author、$description、$date、$name这些字符都是可以动态替换的。

(5.2)开始创建插件ui了
image.png

可视化编辑:
image.png

最终效果:
image.png

(5.3)接下来解析对应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;

    /**
     * 在自动创建该类的时候,添加一个回调函数DialogCallBack,并且改变了onOK这个方法
     * @param callBack 回调函数
     */
    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)然后接着看执行类

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;

/**
 * 执行类
 * (1)会获取包名,然后读取模板文件,替换模板文件中动态字符,在Dialog输入的作者和模块名称也会替换模板中字符,
 * (2)最后通过包名路径生成类文件
 *
 *  后面我会根据实际工作需求,会想办法改进选择生成fragment还是activity
 *  而作者名称也应该能设置是默认的
 *
 */
public class MvpAutomaticCreationAction extends AnAction {

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

    /**
     * 创建类型枚举
     */
    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 类中的内容
     * @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;
    }

    /**
     * 读取模板文件中的字符内容
     * @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;
    }

    /**
     * 读取数据
     * @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导入会报不兼容问题
image.png

(6.2)然后生成


image.png

会创建一个jar包,拿到这个jar包就可以安装到Android Studio了。


image.png
(7)部署插件

点击Install plugin from disk...,选择自己生成的jar,就能导入成功了。


image.png

然后重启Android studio,在菜单这里就能看到了
image.png
(7) 错误汇总

当点击发现没任何反应的时候,我们查询bug发现这么一个提示:

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

注意:在创建自定义的XXAction类时,需要保证自己的XXAction类在某个package中,否则会出现如下之类的报错:

示例如我google中查询:


image.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容