为了提高自己开发Android的效率,用过几款别人写的插件,或许每个人编程习惯不一样,别人插件虽然好用,不免要改这改那。so打算自己开发一款符合自己的插件(其实很简单,也就是findViewById,相信你也也是又爱又恨),顺便学习一下AS的插件开发。

环境搭建

Android Studio 是基于 IntelliJ IDEA 开发的,但是并不支持插件开发,我们需要下载 IntelliJ IDEA 来开发,官网下载地址:https://www.jetbrains.com/idea/,下载Community版本就行!

安装也简单,不做详解!!

创建项目

androidstudio 插件网络 androidstudio插件开发_android-studio

目录结构

androidstudio 插件网络 androidstudio插件开发_AS插件_02

这里简单说一下项目自动生成的插件配置文件(plugin.xml):

<idea-plugin>
  <!-- 插件的ID,保证插件上传仓库后的唯一性 -->
  <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="145.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>

</idea-plugin>

添加Action

右击包名(没有创建包名,右击src),创建Action

androidstudio 插件网络 androidstudio插件开发_android-studio_03

androidstudio 插件网络 androidstudio插件开发_Android_04

OK之后会出来一个继承AnAction的实现类,里面会有个方法:

public class InitViewAction extends AnAction {
    @Override
    public void actionPerformed(AnActionEvent event) {
    }
}

当我操作该Action快捷键或者选择该Action菜单,actionPerformed方法就会执行。

同时还会在插件配置文件(plugin.xml)添加action:

<actions>
  <!-- Add your actions here -->
  <action id="InitView.ID" class="com.johan.initview.InitViewAction" text="InitView" description="create init view method">
    <add-to-group group-id="CodeMenu" anchor="first"/>
    <keyboard-shortcut keymap="$default" first-keystroke="alt V"/>
  </action>
</actions>

这些都是我们刚才设置的内容,都是可以改的,比如快捷键冲突了,改一下first-keystroke就行了!

实现功能

我们的功能实现都要写在Action的actionPerformed方法内,我要的是初始化View的小功能,分4步:

1.获取选中内容
2.获取布局文件
3.解析布局文件
4.生成初始化View的代码
获取选中内容
// 获取project
Project project = event.getProject();
if (project == null) {
    return;
}
// 获取选中内容
final Editor editor = event.getData(PlatformDataKeys.EDITOR);
if (editor == null) {
    return;
}
String selectedText = editor.getSelectionModel().getSelectedText();
// 如果没有选中内容,提示选择布局
if (StringUtils.isEmpty(selectedText) || !StringUtils.startsWith(selectedText,"R.layout.")) {
    // 类似于Android的toast
    ViewUtils.showPopupBalloon(editor, "请选择布局id");
    return;
}

全都是Java代码,可以看出,其实 IntelliJ IDEA 也是Java写的(我猜的)。但是其API相对陌生,我们没必要对每个API都要了解,用到时再去查就行了。

获取布局文件
String layoutFileName = selectedText.substring(9) + ".xml";
// 通过文件名查找布局文件
PsiFile[] getFiles = FilenameIndex.getFilesByName(project, layoutFileName, GlobalSearchScope.allScope(project));
// 如果没有找到,提示没有找到布局文件
if (getFiles == null || getFiles.length == 0) {
    // 类似于Android的toast
    ViewUtils.showPopupBalloon(editor, "没有找到布局文件:" + layoutFileName);
    return;
}
解析布局文件
// 解析XML
XmlFile layoutFile = (XmlFile) getFiles[0];
List<Element> elementList = new ArrayList<>();
Utils.parseXmlLayout(layoutFile, elementList);
if (elementList.size() == 0) {
    ViewUtils.showPopupBalloon(editor, "没有找到任何id");
    return;
}
// ViewUtils类
public static void parseXmlLayout(final PsiFile xmlLayoutFile, final List<Element> elementList) {
    xmlLayoutFile.accept(new XmlRecursiveElementVisitor(){
        @Override
        public void visitElement(PsiElement element) {
            super.visitElement(element);
            if (element instanceof XmlTag) {
                XmlTag tag = (XmlTag) element;
                // 解析XmlTag,保存到elementList中
            }
    });
}
生成初始化View的代码
// 写入文件,不允许在主线程中进行实时的文件写入
PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
PsiClass psiClass = Utils.getTargetClass(editor, psiFile);
InitViewCreator creator = new InitViewCreator(project, psiFile, psiClass, elementList);
creator.execute();

因为不允许在主线程中进行实时的文件写入,所以要使用Simple类:

public class InitViewCreator extends WriteCommandAction.Simple {
    @Override
    protected void run() throws Throwable {
        // 生成Java代码
    }
}

我们可以利用PsiElementFactory帮我们生成PsiClass或者PsiMethod所需要的类,PsiElementFactory可以把我们拼凑的方法(字符串)或者字段(字符串)包装成PsiElement。

// 获取PsiElementFactory 
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
// 生成字段(插入类中)
String field = "private TextView textView;"
factory.createFieldFromText(field, psiClass)
// 生成方法(插入类中)
String method = "public void onClick(View view){\n\n}"
factory.createMethodFromText(method, psiClass)
// 生成语句(插入方法中或者某一块代码块内)
String code = "textView = (TextView) findViewById(R.id.text_view);"
factory.createStatementFromText(code, psiClass);

上面PsiElementFactory生成的PsiElement就可以加入到class或method或代码块中了:

// class插入字段
psiClass.add(factory.createFieldFromText(field, psiClass));
// class插入方法
String method = "public void onClick(View view){\n\n}"
psiClass.add(factory.createMethodFromText(method, psiClass));
// 方法插入语句
String code = "textView = (TextView) findViewById(R.id.text_view);"
psiMethod.getBody().add(factory.createStatementFromText(code, psiClass));

其实还有很多api,如获取类所有字段和所有方法等,你一看,应该就可以理解的,慢慢体会。遇到不懂的,可以查阅文档:http://www.jetbrains.org/intellij/sdk/docs/tutorials.html

生成所需要的代码后,最后就重写class

public class InitViewCreator extends WriteCommandAction.Simple {
    @Override
    protected void run() throws Throwable {
        // 生成Java代码
        // 重写class
        JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
        styleManager.optimizeImports(psiFile);
        styleManager.shortenClassReferences(psiClass);
        new ReformatCodeProcessor(project, psiClass.getContainingFile(), null, false).runWithoutProgress();
    }
}

测试

码完代码后,就可以测试功能了,点击启动:

androidstudio 插件网络 androidstudio插件开发_androidstudio 插件网络_05

会打开一个新的窗口,点击【code】菜单,发现多了一个我们自定义的菜单【InitView】

androidstudio 插件网络 androidstudio插件开发_AS插件_06

然后随便新建一个java项目,测试是否会自动生成代码即可,这里不做演示。

生成插件

测试完后,没什么问题,就可以发布了,很简单,点击【build】菜单下的【Prepare Plugin Module xx Fro Deployment】

androidstudio 插件网络 androidstudio插件开发_Android_07

项目目录会多一个jar包:

androidstudio 插件网络 androidstudio插件开发_插件开发_08

这就是你开发的插件了!!开心吧!!

在Android Studio使用插件

打开 Android Studio,点击【File】->【Setting】,选择【Plugins】,点击【install plugin from disk …】

androidstudio 插件网络 androidstudio插件开发_android-studio_09

然后选择刚才build出来插件(就是刚才生成的jar包),然后重启 Android Studio

androidstudio 插件网络 androidstudio插件开发_androidstudio 插件网络_10

Android Studio 启动后,插件便会生效

androidstudio 插件网络 androidstudio插件开发_Android_11

发布

有兴趣的话,可以把开发的插件发布到仓库,支持在plugin中搜索安装,参考:

就是注册账号,提交jar,填写信息,等着审核就可以了。

项目地址

我把 InitView 代码上传到我的github库中,地址:https://github.com/JohanMan/InitViewPlugin,有兴趣的可以参考一下。

参考资料

学会编写Android Studio插件 别停留在用的程度了
自己编写Android Studio插件