基于IntelliJ IDEA实现AndroidStudio自定

作者: Android技术分享 | 来源:发表于2020-03-20 11:58 被阅读0次

    需求

    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信息(菜单项),源码:

    <idea-plugin version="2">
      <id>com.qixingchao.createufp</id>
      <name>CreateUFPProject</name>
      <version>1.0</version>
      <vendor email="970188529@qq.com" url="http://baidu.com">qxc</vendor>
      <description><![CDATA[
          Create a ufp project.<br>
        ]]></description>
      <change-notes><![CDATA[
        ]]>
      </change-notes>
      <idea-version since-build="141.0"/>
      <extensions defaultExtensionNs="com.intellij">
      </extensions>
      <actions>
        <action id="CreateTemplateProject" class="com.qxc.testplugin.AddFileActionByTemp" text="CreateTemplateProject"
                description="CreateTemplateProject">
          <add-to-group group-id="FileMenu" anchor="first"/>
        </action>
      </actions>
    </idea-plugin>
    

    插件菜单显示在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自定

        本文链接:https://www.haomeiwen.com/subject/mtphlhtx.html