基于IntelliJ IDEA实现AndroidStudio自定义插件:创建模板工程(包含:文件IO处理、弹框定制等技术点)

需求

AndroidStudio 有很多插件,可供开发者集成、使用。
像Flutter、Cordova、mPaas等众多插件,都拥有一个共同的功能,就是创建“模板工程”。也就是使用这些插件创建的Android工程已集成好了相关的依赖、配置,开发者们也不需要再从零开始集成,直接开发就可以了,非常方便。

我们这边也需要对外提供SDK,也想参考这种方式,提供给调用方使用。
在网上,自定义插件的资料倒是有一些,不过使用自定义插件创建工程的资料却是 零 !!!

没办法,只能通过AndroidStudio插件反推吧:
我这边是在AndroidStudio -> Setting-> Plugins ->Marketplace找了几款会创建工程的插件,去其官网,下载插件,然后反编译得到源码的(下了很多插件,反编译源码,没有任何混淆的,哈哈,省时间了~)

通过分析其源码,实现插件创建模板工程的方式主要有两种:

1、在插件中放入模板工程文件,创建工程时,直接执行IO操作,使用模板文件创建工程;(稍简单一些)
2、在插件代码中通过IO创建工程文件,执行代码逻辑向文件中写内容;(逻辑复杂一些)

我这边选择使用方案1,简单高效,维护也方便。

设计

不啰嗦了,直接上具体实施方案:

1、将纯净版模板工程(删除build的工程)压缩成一个文件,将来作为模板文件放入插件
2、插件安装后,插件菜单显示在AndroidStudio -> File菜单的顶部(new菜单的上面,看着舒服些,哈哈~)
3、用户点击插件菜单,创建模板工程,会弹出提示框,让用户选择目标位置
4、用户选择目标位置后,点击【创建】,将插件中的模板文件(模板工程.zip)拷贝至指定位置
5、解压缩模板工程.zip,得到完整的模板工程(解压后,也可删除压缩包文件)

开搞

1、安装 IntelliJ IDEA

Java编程语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具。
我们的插件也要使用该工具实现,不懂如何使用的同学,可以先去做做功课哈~

下载地址:http://www.jetbrains.com/idea/
我选择的是Community(社区版)
然后下一步......即可

2、创建插件工程

file->new->Intellij Platform Plugin

新建完成的目录,其中 plugin.xml 相当于我们的 AndroidManifest.xml,对一些Actions(类似于我们的Activity)进行注册,逻辑代码同样写在 src 中,资源文件(比如说icon)放在 resources 中

3、插件工程结构

template:存放模板文件的目录

-- readme.txt  让用户读的文本信息,例如插件版本、日期、变更内容、联系人...
-- TestTemplate.zip 模板工程压缩包文件

com.qxc.testplugin:存放插件代码逻辑的目录

-- AddFileActionByTemp  action动作类,监听用户点击菜单动作
-- OutFolderChooser  自定义文件目录选择器类
-- UnZipUtils  解压缩工具类

4、plugin.xml

定义插件信息、action信息(菜单项),源码:


  com.qixingchao.createufp
  CreateUFPProject
  1.0
  qxc
  
    ]]>
  
  
  
  
  
  
    
      
    
  

插件菜单显示在AndroidStudio -> File菜单的顶部

5、AddFileActionByTemp 类

大家可能需要对intellij的API,多做做功课,不然可能看不明白

package com.qxc.testplugin;

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 java.io.*;

/**
 * 根据模板创建文件/工程
 * 齐行超
 * 2020年3月3日
 */
public class AddFileActionByTemp extends AnAction
{
    public Project project;

    /**
     * action
     * @param e 事件
     */
    public void actionPerformed(AnActionEvent e)
    {
        this.project = ((Project)e.getData(PlatformDataKeys.PROJECT));
        init();
        refreshProject(e);
    }

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

    /**
     * 初始化
     */
    private void init()
    {
        OutFolderChooser outFolderChooser = new OutFolderChooser();
        outFolderChooser.InitUI(this);
    }

    /**
     * 创建文件
     * @param basePath 路径
     * @return true、false
     */
    public boolean createClassFiles(String basePath)
    {
        try {
            createFile(basePath, "readme.txt");
            createProject(basePath, "TestTemplate.zip");
        }catch (Exception ex){
            ex.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 创建文件(读写字符串)
     * @param basePath 路径
     * @param fileName 文件名
     * @throws Exception 异常
     */
    private void createFile(String basePath, String fileName) throws Exception
    {
        String content = "";
        if (!new File(basePath + fileName).exists())
        {
            content = ReadTemplateFile(fileName);
            writeToFile(content, basePath, fileName);
        }
    }

    /**
     * 创建工程(zip文件,通过io流处理)
     * @param basePath 路径
     * @param fileName 文件名称
     * @throws Exception 异常
     */
    public void createProject(String basePath, String fileName) throws Exception
    {
        InputStream in = null;
        in = getClass().getResourceAsStream("/template/" + fileName);
        FileInputStream fi=null;
        FileOutputStream fo=null;
        try {
            fo=new FileOutputStream(basePath+fileName);
            byte[] b=new byte[1024];
            int len=0;
            while((len=in.read(b))!=-1) {
                fo.write(b, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fi!=null) {
                try {
                    fi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fo!=null) {
                try {
                    fo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        UnZipUtils.unZip(basePath+fileName, basePath);
    }

    /**
     * 读取模板文件
     * @param fileName 文件名称
     * @return 文件内容(字符串)
     */
    private String ReadTemplateFile(String fileName)
    {
        InputStream in = null;
        in = getClass().getResourceAsStream("/template/" + fileName);
        String content = "";
        try
        {
            content = new String(readStream(in));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 读取io流
     * @param inputStream 文件io流
     * @return byte数组
     * @throws IOException io异常
     */
    private byte[] readStream(InputStream inputStream)
            throws IOException
    {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte['?'];
        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();
        }
    }
}

6、OutFolderChooser

package com.qxc.testplugin;

import com.intellij.openapi.ui.Messages;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JTextField;

/**
 * 输出目录选择器
 * 齐行超
 * 2020年3月3日
 */
public class OutFolderChooser extends JFrame {
    private AddFileActionByTemp addFileActionByTemp;
    private JTextField textField;

    private String textBtn1 = "创建工程";
    private String textBtn2 = "选择目录";

    /**
     * 初始化输出目录选择器
     * @param addFileActionByTemp 类实例
     */
    public void InitUI(AddFileActionByTemp addFileActionByTemp)
    {
        this.addFileActionByTemp = addFileActionByTemp;

        this.setTitle("新建模板工程");
        this.setSize(500, 200);
        this.setDefaultCloseOperation(3);
        this.setResizable(false);
        this.setLocationRelativeTo(null);
        this.setLayout(null);//关闭流式布局

        Font f=new Font("宋体",Font.PLAIN,12);//根据指定字体名称、样式和磅值大小,创建一个新 Font。

        JButton button1 = new JButton(textBtn1);
        button1.setBounds(405,55, 80, 50);
        button1.setContentAreaFilled(false);  //消除按钮背景颜色
        button1.setOpaque(false); //除去边框
        button1.setFocusPainted(false);//出去突起
        button1.setFont(f);
        this.add(button1);

        JButton button2 = new JButton(textBtn2);
        button2.setBounds(320,55, 80, 50);
        button2.setContentAreaFilled(false);  //消除按钮背景颜色
        button2.setOpaque(false); //除去边框
        button2.setFocusPainted(false);//出去突起
        button2.setFont(f);
        this.add(button2);

        textField = new JTextField();
        textField.setBounds(10, 55, 300, 50);
        this.add(textField);

        ButtonListener BL = new ButtonListener(textField);
        button1.addActionListener(BL);
        button2.addActionListener(BL);
        this.setVisible(true);//设置窗体可见
    }

    /**
     * 弹框(diaolog事件处理)
     * @param parent
     * @param msgTextArea
     */
    public void showFileSaveDialog(Component parent, JTextField msgTextArea) {
        // 创建一个默认的文件选取器
        JFileChooser fileChooser = new JFileChooser();
        // 设置打开文件选择框后默认输入的文件名
        fileChooser.setSelectedFile(new File("UFPProject"));
        // 打开文件选择框(线程将被阻塞, 直到选择框被关闭)
        int result = fileChooser.showSaveDialog(parent);
        if (result == JFileChooser.APPROVE_OPTION) {
            // 如果点击了"保存", 则获取选择的保存路径
            File file = fileChooser.getSelectedFile();
            msgTextArea.setText(file.getAbsolutePath().trim());
        }
    }

    /**
     * 创建工程(diaolog事件处理)
     */
    public void createProject(){
      boolean result = addFileActionByTemp.createClassFiles(textField.getText().trim());
      if(result){
          Messages.showInfoMessage(addFileActionByTemp.project, "创建成功!", "提示");
          this.setVisible(false);//设置窗体可见
      }else{
          Messages.showInfoMessage(addFileActionByTemp.project, "创建失败,请联系框架组!", "提示");
      }
    }

    class ButtonListener implements ActionListener {
        private JTextField textField;
        public ButtonListener(JTextField textField) {
            super();
            this.textField = textField;
        }
        public void actionPerformed(ActionEvent e) {
            if(e.getActionCommand().equals(textBtn1))
            {
                OutFolderChooser.this.createProject();
            }
            else if(e.getActionCommand().equals(textBtn2))
            {
                OutFolderChooser.this.showFileSaveDialog(OutFolderChooser.this, textField);
            }
        }
    }
}

7、UnZipUtils

package com.qxc.testplugin;

import java.io.*;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 解压缩工具类
 * 齐行超
 * 2020年3月3日
 */
public class UnZipUtils {
    /**
     * 解压缩zip文件
     * @param filePath 文件路径
     * @param outFolder 输出路径
     */
    public static void unZip(String filePath, String outFolder) {
        // 判断文件夹是否存在
        File folder = new File(outFolder);
        if(!folder.exists()){
            folder.mkdir();
        }

        // 创建buffer
        byte[] buffer = new byte[1024];
        ZipInputStream zipls = null;

        try {
            zipls = new ZipInputStream(new FileInputStream(filePath), Charset.forName("GBK"));
            ZipEntry entry = null;
            while ((entry=zipls.getNextEntry())!=null){
                String entryName = entry.getName();
                String outFileName = outFolder + File.separator + entryName;
                System.out.println("create: " + outFileName);
                if(entry.isDirectory()){
                    new File(outFileName).mkdirs();
                }else{
                    FileOutputStream fos = new FileOutputStream(outFileName);
                    int len;
                    while ((len = zipls.read(buffer))>0){
                        fos.write(buffer,0,len);
                    }
                    fos.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                zipls.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

8、生成插件

编码完成,点击Build -> Prepare Plugin Moudle “xxx” Deployment

生成后的插件,是Jar包形式,包含资源信息,反编译可以看到咱们的模板文件等内容。

9、AndroidStudio安装插件

插件后安装成功后,重启AnroidStudio

10、测试插件

搞定,测试了下新创建的模板工程,运行正常,nice~

总结

插件工程源码:https://pan.baidu.com/s/1GBAQC7d_bnvLno1NSIMh4A
提取码:3xh5

IntelliJ IDEA还是非常好用的,除了支持安卓插件的开发,也支持调试。
插件工程中的技术点不难,无非就是IO操作、自定义UI等,相信大家也都能看得懂。
如果有疑问,也欢迎大家留言咨询,就是不一定有时间解答,哈哈~

你可能感兴趣的:(基于IntelliJ IDEA实现AndroidStudio自定义插件:创建模板工程(包含:文件IO处理、弹框定制等技术点))