Android Studio插件开发 MVP框架代码生成插件

原文地址:https://blog.csdn.net/fengdezhudi/article/details/53584497

源码地址:https://github.com/xunzzz/MvpCreatePlugin

一、概述

在使用Android Studio的时候多少都会使用插件来提高开发效率(偷懒),常用的一些:GsonFormat、ButterKnife等等;因为项目中引入mvp这种架构,写各种类是一件很重复繁琐的事,所以当时就写了这个插件,现在正好结合这篇文章拿出来说一下。这篇文章会教你根据自己的项目结构,生成一个mvp框架代码的插件,看一下运行的效果:


Android Studio插件开发 MVP框架代码生成插件_第1张图片

可以看到,只需要输入作者和模块的名称就可以生成一个符合官网版本的mvp框架代码结构,同时会生成base目录。

如果学会了编写插件,这样自己有好创意的时候就可以去实现了,官网有具体的步骤,可以去看一下:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html

废话不多说直接开始。

二、开发环境搭建

开发工具需要IntelliJ IDEA,用过studio都知道它是在IntelliJ IDEA基础上开发的,所以IntelliJ IDEA上手很简单。下载地址:https://www.jetbrains.com/idea/  下载community版本安装即可。开始编写:

1.创建一个plugin项目

File->New->Project 然后选择一个plugin项目,如下图:


Android Studio插件开发 MVP框架代码生成插件_第2张图片

点击Next,输入项目的名称即可,这里取名MvpCreate。

建立工程后看一下项目结构:


Android Studio插件开发 MVP框架代码生成插件_第3张图片

其中plugin.xml为项目的说明文件,会包含一些项目的版权、作者和声明。其中主要标签为,这里会是插件的入口。

src目录下存放主要的代码。

2.创建一个菜单

我们知道Android Studio有很多菜单,例如常用的Build菜单,里面有各种各样的功能,同时还有快捷键支持,如下:

Android Studio插件开发 MVP框架代码生成插件_第4张图片

现在我们开始建一个自己的菜单,选择src->New->Action,如下:


Android Studio插件开发 MVP框架代码生成插件_第5张图片

然后填写这个Action的信息:


Android Studio插件开发 MVP框架代码生成插件_第6张图片

说明一下需要填写的属性:

Action ID:代表这个Action的唯一标示。

Class Name:类名

Name:这个插件在菜单上的名称

Description:关于这个插件的描述信息

Groups:代表这个插件会出现的位置。比如想让这个插件出现在Code菜单下的第一次选项,我在图中选择CodeMenu(Code),右边Anchor选择First

Keyboard Shortcuts:快捷键设置。图中设置Alt+T。

点击OK后会生成一个MvpCreateAction类:


Android Studio插件开发 MVP框架代码生成插件_第7张图片

同时看一下plugin.xml这个文件,会发现标签下多出来了一个标签,里面包含了我们刚刚填写的信息。

这样一个插件就生成了。把这个插件部署到studio已经可以使用,具体如何部署接下来会讲,继续往下看。

三、编写插件代码

先说一下思路:

生成代码我们可以使用一些模板,这个模板可以是一个txt文件,在模板里面添加代码,提取出需要替换的代码,然后通过流读取模板文件,最后生成类文件。基本上就是这样的思路,接下来具体实现:

1.新建模板文件

我们按照官网的mvp风格来编写整体的框架,这里按功能模块分包,包含Activity、Fragment、Presenter、Contract,然后加上base类,BasePresenter、BaseView、BaseActivity、BaseFragment。看一下:


Android Studio插件开发 MVP框架代码生成插件_第8张图片

看一下TemplateContract.txt这个文件的内部细节:


Android Studio插件开发 MVP框架代码生成插件_第9张图片

代码里面的$packagename、$basepackagename、$author、$description、$date、$name这些字符都是可以动态替换的。这些模板文件的具体代码细节在文末的源码中查看,当然也可以根据自己的需要写入自己的代码。

2.新建dialog

上面的模板已经建好,现在我们需要一个对话框,在对话框里面输入作者和模块的名字。效果看一下:

Android Studio插件开发 MVP框架代码生成插件_第10张图片

很简单的效果图,和创建Action一样,Dialog可以直接在src下右键New->Dialog取名MyDialog,然后会在src目录下生生成一个MyDialog.java和MyDialog.form的文件,点击这个MyDialog.form文件会进入一个可视化界面,通过拖拽就可以实现一个用户界面:


Android Studio插件开发 MVP框架代码生成插件_第11张图片

然后看一下MyDialog.java这个类的代码:

import javax.swing.*;

import java.awt.event.*;

public class MyDialog extends JDialog {

    private JPanel contentPane;

    private JButton buttonOK;

    private JButton buttonCancel;

    private JTextField textField1;

    private JTextField textField2;

    private DialogCallBack mCallBack;

    public MyDialog(DialogCallBack callBack) {

        this.mCallBack = callBack;

        setTitle("Mvp Create Helper");

        setContentPane(contentPane);

        setModal(true);

        getRootPane().setDefaultButton(buttonOK);

        setSize(300, 150);

        setLocationRelativeTo(null);

        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 interface DialogCallBack{

        void ok(String author, String moduleName);

    }

}

这段代码很简单,最主要的是49行的onOK()方法,在这里添加按钮的回调。这个dialog可以直接使用了。

3.生成代码实现

好了现在去看看MvpCreateAction这个类的具体实现:

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;

public class MvpCreateAction extends AnAction {

    private Project project;

    //包名

    private String packageName = "";

    private String mAuthor;//作者

    private String mModuleName;//模块名称

    private enum  CodeType {

        Activity, Fragment, Contract, Presenter, BaseView, BasePresenter, MvpBaseActivity, MvpBaseFragment

    }

    @Override

    public void actionPerformed(AnActionEvent e) {

        project = e.getData(PlatformDataKeys.PROJECT);

        packageName = getPackageName();

        init();

        refreshProject(e);

    }

    /**

    * 刷新项目

    * @param e

    */

    private void refreshProject(AnActionEvent e) {

        e.getProject().getBaseDir().refresh(false, true);

    }

    /**

    * 初始化Dialog

    */

    private void init(){

        MyDialog myDialog = new MyDialog(new MyDialog.DialogCallBack() {

            @Override

            public void ok(String author, String moduleName) {

                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);

        createBaseClassFile(CodeType.MvpBaseActivity);

        createBaseClassFile(CodeType.MvpBaseFragment);

    }

    /**

    * 生成base类

    * @param codeType

    */

    private void createBaseClassFile(CodeType codeType) {

        String fileName = "";

        String content = "";

        String basePath = getAppPath() + "base/";

        switch (codeType){

            case BaseView:

                if (!new File(basePath + "BaseView.java").exists()){

                    fileName = "TemplateBaseView.txt";

                    content = ReadTemplateFile(fileName);

                    content = dealTemplateContent(content);

                    writeToFile(content, basePath, "BaseView.java");

                }

                break;

            case BasePresenter:

                if (!new File(basePath + "BasePresenter.java").exists()){

                    fileName = "TemplateBasePresenter.txt";

                    content = ReadTemplateFile(fileName);

                    content = dealTemplateContent(content);

                    writeToFile(content, basePath, "BasePresenter.java");

                }

                break;

            case MvpBaseActivity:

                if (!new File(basePath + "MvpBaseActivity.java").exists()){

                    fileName = "TemplateMvpBaseActivity.txt";

                    content = ReadTemplateFile(fileName);

                    content = dealTemplateContent(content);

                    writeToFile(content, basePath, "MvpBaseActivity.java");

                }

                break;

            case MvpBaseFragment:

                if (!new File(basePath + "MvpBaseFragment.java").exists()){

                    fileName = "TemplateMvpBaseFragment.txt";

                    content = ReadTemplateFile(fileName);

                    content = dealTemplateContent(content);

                    writeToFile(content, basePath, "MvpBaseFragment.java");

                }

                break;

        }

    }

    /**

    * 生成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;

        }

    }

    /**

    * 获取包名文件路径

    * @return

    */

    private String getAppPath(){

        String packagePath = packageName.replace(".", "/");

        String appPath = project.getBasePath() + "/App/src/main/java/" + packagePath + "/";

        return appPath;

    }

    /**

    * 替换模板中字符

    * @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

    */

    public String getDate() {

        Date currentTime = new Date();

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");

        String dateString = formatter.format(currentTime);

        return dateString;

    }

    /**

    * 读取模板文件中的字符内容

    * @param fileName 模板文件名

    * @return

    */

    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;

    }

    private byte[] readStream(InputStream inputStream) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024];

        int len = -1;

        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();

    }

    /**

    * 生成

    * @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();

        }

    }

    /**

    * 从AndroidManifest.xml文件中获取当前app的包名

    * @return

    */

    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;

    }

}

说明一下,首先会获取包名,然后读取模板文件,替换模板文件中动态字符,在Dialog输入的作者和模块名称也会替换模板中字符,最后通过包名路径生成类文件,这是上面代码的一个简述,具体看代码吧。这样一个mvp框架代码生成插件已经完成,接下来看一下如何部署。

四、部署插件

1.填写插件信息

找到plugin.xml文件,填入一些基本信息,如下图:


Android Studio插件开发 MVP框架代码生成插件_第12张图片

填写一些插件的信息,像name、version、description...等等,填写的这些信息会在安装插件时,看到它的简介。

然后点击Build->Prepare Plugin Module ...... 如图:


Android Studio插件开发 MVP框架代码生成插件_第13张图片

会在项目的根目录生成一个jar包:

Android Studio插件开发 MVP框架代码生成插件_第14张图片

拿到这个jar包就可以安装到Android Studio了。

2.安装插件

打开Android Studio,选择File->Setting->Plugins->Install plugin from disk,然后选择刚刚生成的jar包,如图:

Android Studio插件开发 MVP框架代码生成插件_第15张图片

安装好了以后重启Studio即可使用。

重启后可以看到在Code菜单下多了MvpHelper这个选项,点击或者快捷键Alt+T调用。


Android Studio插件开发 MVP框架代码生成插件_第16张图片

现在调用这个插件去生成代码吧!

3.上传到Plugins仓库

如果你有个好的插件想要分享给你的小伙伴,但每次都要打成jar包发送给需要的人,然后对方需要Setting->Plugins->Install plugin from disk找到本地jar去安装使用。这样感觉太麻烦了,如果直接通过浏览Plugins仓库安装多好。按照下面步骤就可以实现:

官方Plugins仓库地址:https://plugins.jetbrains.com/

找到plugin.xml文件把下面这段代码打开:

  com.intellij.modules.lang

打开后代表在所有的插件仓库都可以搜到这个插件,即IntelliJ IDEA和Android Studio的仓库均可搜到,要不然只能在IntelliJ IDEA的仓库搜到。

修改完plugin.xml文件后,重新生成jar,然后在仓库官网注册用户,上传插件,填写插件信息,等待审核通过。这样就可以在插件仓库搜索到自己的插件了,如下图:

Android Studio插件开发 MVP框架代码生成插件_第17张图片

五、最后

到此本文结束,编写一个插件可以说就三步:

下载IntelliJ IDEA,创建plugin项目;

代码实现;

生成jar包,部署插件;

步骤很简单,主要的是代码实现上比较麻烦,现在可以动脑去编写一些“偷懒”的插件吧。

你可能感兴趣的:(Android Studio插件开发 MVP框架代码生成插件)