动手试试Android Studio插件开发

Posted -dragon-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动手试试Android Studio插件开发相关的知识,希望对你有一定的参考价值。

原博客:http://blog.csdn.net/zhangke3016/article/details/53245530

由于业务关系,经常需要写一些表单页面,基本也就是简单的增删改查然后上传,做过几个页面之后就有点想偷懒了,这么低水平重复性的体力劳动,能不能用什么办法自动生成呢,查阅相关资料,发现Android studio插件正好可以满足需求,在Github上搜了一下,找到BorePlugin这个帮助自动生成布局代码的插件挺不错的,在此基础上修改为符合自己需求的插件,整体效果还不错。 
发现了android studio插件的魅力,自己也总结一下,也给小伙伴们提供一点参考,今天就以实现自动生成findviewbyid代码插件的方式来个简单的总结。这里就不写行文思路了,一切从0开始,一步一步搭建起这个插件项目吧。效果如下:

一、搭建环境

由于android studio是基于Intellij IDEA开发的,但Android Studio自身不具备开发插件的功能,所以插件开发需要在IntelliJ IDEA上开发。 
好了,说了这么多,开始去官网下载吧,下载地址:https://www.jetbrains.com/idea/ 
安装运行后我们就可以开始开发了。 
创建项目

创建成功之后的文件夹是这个样子的:

我们重点关注plugin.xml和src,plugin.xml是我们这个插件项目的配置说明,类似于android开发中的AndroidManifest.xml文件,用于配置信息的注册和声明。

<idea-plugin version="2">
  <id>com.your.company.unique.plugin.id</id>
  <name>Plugin display name here</name>
  <version>1.0</version>
  <vendor email="support@yourcompany.com" 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="141.0"/>

  <!-- 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>

  <actions>
    <!-- Add your actions here -->
  </actions>

</idea-plugin>
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

来简单介绍下这个XML配置文件: 
id:插件的ID,保证插件的唯一性,如果上传仓库的话。 
name:插件名称。 
version:版本号。 
description:插件的简介。 
change-notes:版本更新信息。 
extensions:扩展组件注册 。 
actions:Action注册,比如在某个菜单下增加一个按钮就要在这注册。

二、开始编码

1、编写菜单选项,用于触发我们的插件。

好了,现在我们要用到很关键的一个类:AnAction,选择new->Action就可以创建:

ActionID:代表该Action的唯一的ID 
ClassName:类名 
Name:插件在菜单上的名称 
Description:对这个Action的描述信息 
Groups:定义这个菜单选项出现的位置,比如图中设置当点击菜单栏Edit时,第一项会出现GenerateCode的选项,右边的Anchor是选择该选项出现的位置,默认First即最顶部。 
之后会出现我们创建的GenerateCodeAction类:

public class GenerateCodeAction extends AnAction 


@Overridepublic void actionPerformed(AnActionEvent e) 

 // TODO: insert action logic here

  



 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

plugin.xml中也多了一段代码:

<action id="HelloWorld.TestGenerateCodeAction" class="com.example.helloworld.GenerateCodeAction" text="GenerateCode"
description="generate findviewbyid code ">
<add-to-group group-id="CodeMenu" anchor="first"/>
<keyboard-shortcut keymap="$default" first-keystroke="meta I"/>
</action>
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这样,一个菜单选项就完成了,接下来就该实现当用户点击GenerateCode菜单或者按快捷键Command+ M后的功能代码了。

2、实现功能逻辑代码

在实现功能逻辑之前,我们要先理清需求,首先我们是想在选中布局文件的时候,自动解析布局文件并生成findviewbyid代码。那我们主要关注三个点就可以了。

1、如何获取布局文件 
2、如何解析布局文件 
3、如何根据将代码写入文件

1、如何获取布局文件 
为简单起见,我们这里通过让用户自己输入布局文件的方式通过FilenameIndex.getFilesByName方法来查找布局文件。 
查找文件我们要用到PsiFile类,官方文档给我们的提供了几种方式:

From an action: 
    e.getData(LangDataKeys.PSI_FILE).
From a VirtualFile: 
    PsiManager.getInstance(project).findFile()
From a Document:     
    PsiDocumentManager.getInstance(project).getPsiFile()
From an element inside the file:
     psiElement.getContainingFile()
To find files with a specific name anywhere in the project, use :
    FilenameIndex.getFilesByName(project, name, scope)

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里使用最后一种方式来获取图片,获取用户选中的布局文件,如果用户没有选中内容,通过在状态栏弹窗提示:

 public static void showNotification(Project project, MessageType type, String text) 
        StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);

        JBPopupFactory.getInstance()
                .createHtmlTextBalloonBuilder(text, type, null)
                .setFadeoutTime(7500)
                .createBalloon()
                .show(RelativePoint.getCenterOf(statusBar.getComponent()), Balloon.Position.atRight);
    
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

获取用户选中内容:

@Override
    public void actionPerformed(AnActionEvent e) 

        Project project = e.getProject();
        Editor editor = e.getData(PlatformDataKeys.EDITOR);
        if (null == editor) 
            return;
        

        SelectionModel model = editor.getSelectionModel();
        //获取选中内容
        final String selectedText = model.getSelectedText();
        if (TextUtils.isEmpty(selectedText)) 
            Utils.showNotification(project,MessageType.ERROR,"请选中生成内容");
            return;
        
    
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

获取XML文件:

PsiFile[] mPsiFiles = FilenameIndex.getFilesByName(project, selectedText+".xml", GlobalSearchScope.allScope(project));
        if (mPsiFiles.length<=0)
            Utils.showNotification(project,MessageType.INFO,"所输入的布局文件没有找到!");
            return;
        
        XmlFile xmlFile =  (XmlFile) mPsiFiles[0];
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至此,布局文件获取到了,我们开始下一步,解析布局文件啦。 
2、如何解析布局文件 
关于文件操作,官方文档是这样写的:

Most interesting modification operations are performed on the level of individual PSI elements, not files as a whole. 
To iterate over the elements in a file, use 
psiFile.accept(new PsiRecursiveElementWalkingVisitor()…);

我们这里通过file.accept(new XmlRecursiveElementVisitor())方法对XML文件进行解析:

public static ArrayList<Element> getIDsFromLayout(final PsiFile file, final ArrayList<Element> elements) 
        file.accept(new XmlRecursiveElementVisitor() 

            @Override
            public void visitElement(final PsiElement element) 
                super.visitElement(element);
                //解析XML标签
                if (element instanceof XmlTag) 
                    XmlTag tag = (XmlTag) element;
                  //解析include标签
                    if (tag.getName().equalsIgnoreCase("include")) 
                        XmlAttribute layout = tag.getAttribute("layout", null);

                        if (layout != null) 
                            Project project = file.getProject();
//                            PsiFile include = findLayoutResource(file, project, getLayoutName(layout.getValue()));
                            PsiFile include = null;
                            PsiFile[] mPsiFiles = FilenameIndex.getFilesByName(project, getLayoutName(layout.getValue())+".xml", GlobalSearchScope.allScope(project));
                            if (mPsiFiles.length>0)
                                include = mPsiFiles[0];
                            

                            if (include != null) 
                                getIDsFromLayout(include, elements);

                                return;
                            
                        
                    

                    // get element ID
                    XmlAttribute id = tag.getAttribute("android:id", null);
                    if (id == null) 
                        return; // missing android:id attribute
                    
                    String value = id.getValue();
                    if (value == null) 
                        return; // empty value
                    

                    // check if there is defined custom class
                    String name = tag.getName();
                    XmlAttribute clazz = tag.getAttribute("class", null);
                    if (clazz != null) 
                        name = clazz.getValue();
                    

                    try 
                        Element e = new Element(name, value, tag);
                        elements.add(e);
                     catch (IllegalArgumentException e) 
                        // TODO log
                    
                
            
        );


        return elements;
    

    public static String getLayoutName(String layout) 
        if (layout == null || !layout.startsWith("@") || !layout.contains("/")) 
            return null; // it's not layout identifier
        

        String[] parts = layout.split("/");
        if (parts.length != 2) 
            return null; // not enough parts
        

        return parts[1];
    
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73