AndroidStudio插件开发学习笔记

由于工作需要以及个人兴趣, 最近学习了自定义AndroidStudio插件用于生成代码, 在此记录下以备以后查阅, 希望同时也能帮助到正在看这篇博客的你.

编辑器

编写IDEA(AndroidStudio)插件需要用到 IntelliJIDEA , 编辑器可以在官网(官网需要翻墙访问)下载, 激活请访问这个地址, (修改hosts然后粘贴激活码)

#请将hosts添加以下值
0.0.0.0	account.jetbrains.com
0.0.0.0	www.jetbrains.com

插件配置

进入IDEA编辑器, 选择新建工程, 如图所示, 选中 IntelliJ Platform Plugin, Module SDK 的位置如果没有需要点击 New 按钮, 默认会选中 IDEA 所在目录, 直接点确定即可.

在这里插入图片描述

打开工程后可以看到如下图的目录结构, 其中plugin.xml就是插件配置的地方
在这里插入图片描述
按照以下注释配置

<idea-plugin version="2">
  <id>com.your.company.unique.plugin.id</id><!-- 插件ID, 每个插件对应一个插件ID, 不能重复 -->
  <name>Plugin display name here</name><!-- 插件名 -->
  <version>1.0</version><!-- 版本号 -->
  <vendor email="[email protected]" url="http://www.yourcompany.com">YourCompany</vendor><!-- 插件发布后可以在插件商店看到这部分信息, 不填写将无法发布插件到官网-->

  <description><![CDATA[
      Enter short description for your plugin here.<br>
      <em>most HTML tags may be used</em>
    ]]></description><!-- 插件描述 -->

  <change-notes><![CDATA[
      Add change notes here.<br>
      <em>most HTML tags may be used</em>
    ]]>
  </change-notes><!-- 更新日志 -->

  <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
  <idea-version since-build="145.0"/><!-- 最低支持IDEA版本, 保持默认就行 -->

  <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
       on how to target different products -->
  <!-- uncomment to enable plugin in all products
  <depends>com.intellij.modules.lang</depends>
  -->

  <extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
  </extensions>

  <!--此处不需要填写, 创建Action将自动生成-->
  <actions>
    <!-- Add your actions here -->
  </actions>

</idea-plugin>

接下来在 src 中创建一个Action
在这里插入图片描述
如下图, ActionID 之后可以在 plugins.xml中修改, className就是类名, Name是显示给用户的动作名称
在这里插入图片描述
Groups 是指你创建的 Action 出现的位置, 这里我选择了GenerateGroup, 对应如下图 Generate 组, 配置完点击OK, Keyboard Shortcuts可以选择不填

在这里插入图片描述

开始前的准备

  • IntelliJ IDEA 是开源的, 写代码前最好先关联源码 https://github.com/JetBrains/intellij-community 前往github下载源码, 然后点击如下图的按钮, 在打开的窗口中选中 SDKs --> Sourcepath, 点击加号进行添加源码, 将解压后的 java目录和 platform 两个目录添加进去
    在这里插入图片描述
    在这里插入图片描述
  • 插件开发官网文档地址

开始写代码

当我们创建一个 Action 后将生成以下代码, 我们的逻辑都要写到actionPerformed方法中.

public class DemoPlugins extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
    }
}

AnActionEvent 是actionPerformed方法暴露出来的唯一一个参数, 是插件开发的桥梁, 通过 AnActionEvent 可以获取到很多东西, 对此我了解的也不多, 大家可以通过查看注释和官方文档来了解AnActionEvent。

据我所知,AnActionEvent有一个 event.getData(DataKeys.EDITOR) 的方法能获取到很多东西, 比如传入 DataKeys.EDITOR能获取到一个光标对象。比如如下代码可以通过光标类获取到光标所在的类, 返回的类型是PsiClass, PSI是IDEA插件开发文件操作的核心, 稍后详细介绍.

    /**
     * 获取光标所在的类
     */
    public static PsiClass getCurrentClass(AnActionEvent event){
        Editor editor = event.getData(DataKeys.EDITOR);
        PsiElement element = getCurrentFile(event).findElementAt(editor != null ? editor.getCaretModel().getOffset() : 0);
        return PsiTreeUtil.getParentOfType(element, PsiClass.class);
    }

GUI

Dialog: 在src目录下, 右键 – > New – > Dialog , 创建DIalog。
将生成两个文件:XXXDialog.javaXXXDialog.form, 其中form是图形化界面, 可以拖拖拽拽来摆放控件, 而java文件用于处理控件的各种事件, 类似Android中的 layout布局和Activity的关系。这其中的所有控件都是 java swing 中的控件, 至于控件的用法可以直接百度具体控件(比如百度JTable), 网上有很多非常详细的教程。
form表单
使用如下代码将Dialog显示于界面上:

	XXXDialog dialog = new XXXDialog();
	dialog.setSize(500, 500);//设置Dialog大小
	dialog.setLocationRelativeTo(null);//使Dialog展示在屏幕中央
	dialog.setVisible(true);//使Dialog可见, 调用此方法后Dialog才会展示在屏幕上

数据持久化

IDEA SDK提供PropertiesComponent来持久化数据, 通过PropertiesComponent.getInstance().setValue(key, value); 保存, PropertiesComponent.getInstance().getValue(key);获取, 建议配合Gson使用:

    /**
     * 保存配置至磁盘
     */
    public static  <T> void save(@NotNull T config){
        PropertiesComponent.getInstance().setValue(config.getClass().getName(), new Gson().toJson(config));
    }
    /**
     * 读取配置
     */
    public static @NotNull <T> T load(@NotNull Class<T> clazz, T defaultValue){
        String json = PropertiesComponent.getInstance().getValue(clazz.getName());
        if(json == null){
            return defaultValue;
        }
        return new Gson().fromJson(json, clazz);
    }

在这里插入图片描述
关于如何添加三方jar文件, 需要点击 Project Structure(如上图)图标, 选择Modules – > Dependencies – > 点击加号添加(如下图), 搜索 gson点击OK即可, 当然也可以添加Jar文件。
在这里插入图片描述

PSI文件操作

  • PsiElement 文件操作中的文件, 类, 接口, 方法, 成员变量, 局部变量, 注解, 注释等都是一个 PsiElement 对象
    • **getProject()**返回一个Project对象, 很多操作都需要用到 Project 对象
    • **getParent()**一个重要的方法, 举个例子来理解这个方法:
		//A.java文件中
		public class A{
		 	static class B{
				|//现在光标在此处
			} //B是一个内部类
		}
		
		    /**
		     * 获取光标所在的类
		     */
		    public static PsiClass getCurrentClass(AnActionEvent event){
		        Editor editor = event.getData(DataKeys.EDITOR);
		        PsiElement element = getCurrentFile(event).findElementAt(editor != null ? editor.getCaretModel().getOffset() : 0);
		        return PsiTreeUtil.getParentOfType(element, PsiClass.class);
		    }
		------
		
		{
			PsiClass B = getCurrentClass(event); //B的PsiClass对象
			PsiClass A = ((PsiClass)B.getParent());//得到A的PsiClass对象
			PsiFile AFile = ((PsiFile)A.getParent());//得到A的文件对象
			//也就是说可以通过内部类获取到外部类
		}
  • PsiClass interface class enum都是一个PsiClass对象
  • PsiMethod 代表一个方法的对象
  • PsiField 常量, 成员变量都属于 PsiField
  • PsiComment 一个单行注释对象

PsiElementFactory

可以创建注释 方法 类 接口 注解 变量等, 通过JavaPsiFacade.getElementFactory(project);来获取该对象, project 可以通过 AnActionEvent 或任意一个 PsiElement 来获取
注意: PsiElementFactory.createXXXFromText(“xxx”, PsiElement) 类似这样的方法, 第一个参数不能包含’\t’制表符

使用 PsiElementFactory 来创建一个常量:

	public static PsiElementFactory getElementFactory(PsiElement element){
        return JavaPsiFacade.getElementFactory(element.getProject());
    }

	/**
     * 创建带注释的String常量
     * @param constName 变量名
     * @param constValue 变量的值
     * @param javaDoc 变量的注释
     */
    public static void createPrivateStringConstFailed(PsiClass psiClass, String constName, String constValue, String javaDoc){
        PsiType psiType = PsiType.getTypeByName("java.lang.String", psiClass.getProject()
                , GlobalSearchScope.EMPTY_SCOPE);//创建一个数据类型
        // 根据变量名查找类种是否包含此变量, 第二个参数含义是是否查找父类
        PsiField psiField = psiClass.findFieldByName(constName, false);
        if(psiField == null) {//避免生成重复变量
            //使用PsiElementFactory创建一个成员变量
            psiField = getElementFactory(psiClass).createField(constName, psiType);
            //PsiUtil是SDK提供的一个工具类
            //给成员变量添加修饰符, 参数2为true代表添加, false代表移除
            PsiUtil.setModifierProperty(psiField, PsiModifier.STATIC, true);
            PsiUtil.setModifierProperty(psiField, PsiModifier.FINAL, true);
            PsiUtil.setModifierProperty(psiField, PsiModifier.PRIVATE, true);

            //设置变量初始化表达式
            PsiExpression psiInitializer = getElementFactory(psiClass).createExpressionFromText("\"" + constValue + "\"", psiField);
            psiField.setInitializer(psiInitializer);
            //在变量后写入单行注释
            writeComment(psiClass, javaDoc, psiField);
            //将变量添加至类中
            psiClass.add(psiField);
        }else {
            PsiExpression psiInitializer = getElementFactory(psiClass).createExpressionFromText("\"" + constValue + "\"", psiField);
            psiField.setInitializer(psiInitializer);
            writeComment(psiClass, javaDoc, psiField);
        }
    }

    private static void writeComment(PsiClass psiClass, String javaDoc, PsiField psiField) {
        if(javaDoc != null) {
            //获取变量最后一个元素
            PsiElement lastChild = psiField.getLastChild();
            if(!(lastChild instanceof PsiComment)) {//避免重复写入注释
                //创建一个单行注释
                PsiComment comment = PsiUtils.getElementFactory(psiClass).createCommentFromText("//" + javaDoc, psiField);
                //将单行注释添加至最后一个元素后
                psiField.addAfter(comment, lastChild);
            }
        }
    }

创建一个接口:

    /**
     * 创建一个接口
     */
    public static PsiClass createInterface(PsiClass clazz, String interfaceCame){
        PsiClass innerInterface = getElementFactory(clazz).createInterface(interfaceCame);
        PsiModifierList modifierList = innerInterface.getModifierList();
        modifierList.setModifierProperty(PsiModifier.PUBLIC, true);
        return innerInterface;
    }

为变量 方法 或接口 类添加注解: PsiElement.getModifierList().addAnnotation("NotNull()");不需要加@符号
创建一个方法:

	PsiClass clazz;//目标类
	StringBuilder sb = new StringBuilder();
	sb.append("/**\n");
    sb.append(" * ").append("注释内容").append("\n");
    sb.append("* @param value woshicnahsumiaoshu").append("\n");
    sb.append(" */\n");
    sb.append("@Click(").append(id).append(")\n");
    sb.append("void onClick(View view) {\n\n}");
	String text = sb.toString();
	JavaPsiFacade.getElementFactory(clazz.getProject()).createMethodFromText(text, clazz);

总结

本来想着重写Psi 文件操作相关的东西的, 结果写的时候发下无从下笔, 还是对IDEA插件开发了解的不够多, 写的有点乱, Psi相关大家可以通过关联源码后看注释来了解每个函数的作用, 一般情况下使用插件生成代码都可以使用PsiElementFactory来创建, 使用PsiElement.add(PsiElement)来添加, 感兴趣的可以通过写Demo尝试

猜你喜欢

转载自blog.csdn.net/qq_27070117/article/details/93973951