Android插件开发

相信大家在开发过程中都会用到一些插件,如butterknife,插件的使用大大的节约了我们的开发时间,看了鸿洋的《学会编写Android Studio插件 别停留在用的程度了》
这篇博客后,我就想着自己去写一个插件了;在Android开发中,现在流行的mvp模式,每个界面都会创建对应的mvp框架,写的很浪费时间,虽然网上也有一些插件,但是每个人封装的base基类都有自己的风格,因此我就想写出一个自己的插件,除了鸿洋的这篇博客,我还参考了下 https://blog.csdn.net/fengdezhudi/article/details/53584497。

开发环境搭建

插件开发使用的是IntelliJ IDEA,附上下载地址https://www.jetbrains.com/idea/ ,推荐下载community版本安装,不需要注册

注意:安装时注意选上 groovy,我当时只选择了java,后来发现插件不能打包,重新下载的groovy,勿踩坑。

新建项目

[外链图片转存失败(img-RuvPWma7-1566354796743)(https://i.imgur.com/xOCsmTW.png)]

注意: sdk这里如果没有,就选择自己的sdk即可,不要勾选groovy,下面的libray是我自己下载的,因此自己去目录下选择的。

新建插件

[外链图片转存失败(img-AMkEidCi-1566354796744)(https://i.imgur.com/ACUyPQJ.png)]

注意:不要直接在src下新建action,否则打包的插件不能正常使用,一定要在src下新建一个包。

[外链图片转存失败(img-s1cPKn03-1566354796746)(https://i.imgur.com/3NpIFJs.png)]

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

Class Name:类名

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

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

Groups:代表这个插件会出现的位置。我的是在Code菜单下的第一次选项,左侧选择CodeMenu(Code),右边Anchor选择First

Keyboard Shortcuts:快捷键设置

就这样一个插件项目就新建完成。然后在src下新建我们的模板,
[外链图片转存失败(img-UDsP4xzo-1566354796749)(https://i.imgur.com/WJm8oYy.png)]

注意:这里 n a m e , name, namepackgename等这些字符都是站位用的,生成文件代码时替换。

然后新建一个Dialog,也就是插件使用时的界面,这个和Android就差不多了。

[外链图片转存失败(img-AcfJwoV6-1566354796750)(https://i.imgur.com/JPYrlpd.png)]

这里贴上我的dailog代码

package action;

import org.apache.http.util.TextUtils;

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

public class CreateDialog extends JDialog {
    private JPanel contentPane;
    private JButton buttonOK;
    private JButton buttonCancel;
    private JTextField textField1;
    private JTextField textField2;
    private JLabel Txttip;


    private DialogCallBack mCallBack;

    public static final String FRAGMENT = "1";
    public static final String ACTIVITY = "2";
    public static final String ALL = "3";
    private String type = FRAGMENT;

    public CreateDialog(DialogCallBack callBack) {
        this.mCallBack = callBack;
        setContentPane(contentPane);
        setModal(true);
        setTitle("Mvp Create Helper");
        setSize(300, 150);
        setLocationRelativeTo(null);
        getRootPane().setDefaultButton(buttonOK);
        Txttip.setVisible(false);



        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){
            if (TextUtils.isEmpty(textField1.getText().trim())){
                Txttip.setVisible(true);
                Txttip.setText("No filename is created");
                return;
            }
            if (TextUtils.isEmpty(textField2.getText().trim())) {
                Txttip.setVisible(true);
                Txttip.setText("No create type, fragment or activity or all");
                return;
            }else if ("fragment".equals(textField2.getText().trim().toLowerCase())){
                type = FRAGMENT;
            }else if ("activity".equals(textField2.getText().trim().toLowerCase())){
                type = ACTIVITY;
            }else if ("all".equals(textField2.getText().trim().toLowerCase())){
                type = ALL;
            }else{
                Txttip.setVisible(true);
                Txttip.setText("Type only fragment and activity and all");
                return;
            }

            mCallBack.ok(type,textField1.getText().trim());
        }
        dispose();
    }

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

//    public static void main(String[] args) {
//        CreateDialog dialog = new CreateDialog(new DialogCallBack() {
//            @Override
//            public void ok(String type, String moduleName) {
//
//            }
//        });
//        dialog.pack();
//        dialog.setVisible(true);
//        System.exit(0);
//    }

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

主要就是根据我们的布局在onOK()中写入我们需要的东西。然后在我们新建的action中写入逻辑代码,也就是自动生成mvp框架代码。

package action;

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.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MvpCreateAction extends AnAction {

    private Project project;
    //包名
    private String packageName = "";
    //模块名称
    private String mModuleName;
    //创建的类型
    private String mType = CreateDialog.FRAGMENT;

    private String xmlName = "view";

    private enum CodeType {
        Activity, Fragment, Contract, Presenter, BaseView, BasePresenter, BaseActivity, BaseFragment
    }


    public MvpCreateAction(){

    }

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

    }

    /**
     * 从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
     */
    private void init() {
        CreateDialog myDialog = new CreateDialog((type, moduleName) -> {
            mModuleName = moduleName;
            mType = type;
            createClassFiles();
            Messages.showInfoMessage(project, "create mvp code success", "title");
        });
        myDialog.setVisible(true);
    }

    /**
     * 生成类文件
     */
    private void createClassFiles() {
        if (mType.equals(CreateDialog.ACTIVITY)) {
            createClassFile(CodeType.Activity);
            xmlName = "activity_" + mModuleName.toLowerCase() + ".xml";
            createXmlFile(xmlName);
        }
        if (mType.equals(CreateDialog.FRAGMENT)) {
            createClassFile(CodeType.Fragment);
            xmlName = "fragment_" + mModuleName.toLowerCase() + ".xml";
            createXmlFile(xmlName);
        }
        if (mType.equals(CreateDialog.ALL)) {
            createClassFile(CodeType.Activity);
            createClassFile(CodeType.Fragment);
            createXmlFile("activity_" + mModuleName.toLowerCase() + ".xml");
            createXmlFile("fragment_" + mModuleName.toLowerCase() + ".xml");
        }
        createBaseClassFile(CodeType.BaseView);
        createBaseClassFile(CodeType.BasePresenter);
        createBaseClassFile(CodeType.BaseActivity);
        createBaseClassFile(CodeType.BaseFragment);

        createClassFile(CodeType.Contract);
        createClassFile(CodeType.Presenter);
    }

    /**
     * 生成布局文件
     */
    private void createXmlFile(String fileName) {
        String content = dealTemplateContent(ReadTemplateFile("Templatexml.txt"));
        String appPath = project.getBasePath() + "/App/src/main/res/layout/";
        writeToFile(content, appPath, fileName);
    }

    /**
     * 生成mvp框架代码
     *
     * @param codeType
     */
    private void createClassFile(CodeType codeType) {
        switch (codeType) {
            case Activity:
                createFile("TemplateActivity.txt", toUpperCaseFirstOne(mModuleName) + "Activity.java");
                break;
            case Fragment:
                createFile("TemplateFragment.txt", toUpperCaseFirstOne(mModuleName) + "Fragment.java");
                break;
            case Contract:
                createFile("TemplateContract.txt", toUpperCaseFirstOne(mModuleName) + "Contract.java");
                break;
            case Presenter:
                createFile("TemplatePresenter.txt", toUpperCaseFirstOne(mModuleName) + "Presenter.java");
                break;
        }
    }

    private void createFile(String fileName, String CreateName) {
        String appPath = getAppPath();
        String content = dealTemplateContent(ReadTemplateFile(fileName));
        writeToFile(content, appPath + mModuleName.toLowerCase(), CreateName);
    }


    /**
     * 生成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 = "BaseView.txt";
                    content = ReadTemplateFile(fileName);
                    content = dealTemplateContent(content);
                    writeToFile(content, basePath, "BaseView.java");
                }
                break;
            case BasePresenter:
                if (!new File(basePath + "BasePresenter.java").exists()) {
                    fileName = "BasePresenter.txt";
                    content = ReadTemplateFile(fileName);
                    content = dealTemplateContent(content);
                    writeToFile(content, basePath, "BasePresenter.java");
                }
                break;
            case BaseActivity:
                if (!new File(basePath + "BaseActivity.java").exists()) {
                    fileName = "BaseActivity.txt";
                    content = ReadTemplateFile(fileName);
                    content = dealTemplateContent(content);
                    writeToFile(content, basePath, "BaseActivity.java");
                }
                break;
            case BaseFragment:
                if (!new File(basePath + "BaseFragment.java").exists()) {
                    fileName = "BaseFragment.txt";
                    content = ReadTemplateFile(fileName);
                    content = dealTemplateContent(content);
                    writeToFile(content, basePath, "BaseFragment.java");
                }
                break;
        }
    }


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

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


    /**
     * 替换模板中字符
     *
     * @param content
     * @return
     */
    private String dealTemplateContent(String content) {
        content = content.replace("$name", toUpperCaseFirstOne(mModuleName));
        if (content.contains("$rpackagename")){
            content = content.replace("$packagename", packageName);
        }
        if (content.contains("$packagename")) {
            content = content.replace("$packagename", packageName + "." + mModuleName.toLowerCase());
        }
        if (content.contains("$basepackagename")) {
            content = content.replace("$basepackagename", packageName + ".base");
        }
        if (content.contains("$axmlname")){
            content = content.replace("$axmlname","activity_" + mModuleName.toLowerCase());
        }
        if (content.contains("$fxmlname")){
            content = content.replace("$fxmlname","fragment_" + mModuleName.toLowerCase());
        }
//        content = content.replace("$author", mAuthor);
        if (content.contains("$date")) {
            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;
    }

//    public static void main(String[] args) {
//
//        MvpCreateAction action = new MvpCreateAction();
//        System.out.print(action.ReadTemplateFile("Templatexml.txt"));
//
//
        String appPath = project.getBasePath() + "/App/src/main/res/layout/";
        writeToFile(content, appPath, fileName);
//    }


    private byte[] readStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        byte[] file;
        int len = -1;
        try {
            while ((len = inputStream.read()) != -1) {
                outputStream.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            file = outputStream.toByteArray(); // 取内存中保存的数据
            outputStream.close();
            if (inputStream != null)
                inputStream.close();
        }

        return file;
    }


    /**
     * 生成
     *
     * @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();
            }else{
                Messages.showInfoMessage(project, "The file already exists", "title");
                return;
            }

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

    }

    //首字母转大写
    public static String toUpperCaseFirstOne(String s) {
        if (Character.isUpperCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
    }

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


}

打包插件

[外链图片转存失败(img-W2znCN9h-1566354796752)(https://i.imgur.com/EZhnfgg.png)]

会自动生成一个jar包,然后我们将这个jar包导入Android studio就可以使用了。

[外链图片转存失败(img-XZggqQbj-1566354796753)(https://i.imgur.com/rC8greG.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值