Android

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

2017-12-21  本文已影响145人  zhongjh

当项目引入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

上一篇下一篇

猜你喜欢

热点阅读