自己编写Android Studio插件
2016-11-19 23:00
411 查看
前言
为何会自己写插件呢,原因有两个,一个是之前看到鸿神写了一篇学会编写Android Studio插件 别停留在用的程度了的博客,另一个是有些插件是不能满足自己的需求的,所有就需要自己来写;之前因为赶项目没时间,今天抽空就学习了下。可以看下一篇 Android Studio插件GenerateFindViewById
这篇博客是根据输入或者选中布局文件(如
R.layout.activity_main,只需要选中
activity_main或者输入
activity_main),来自动生成字段,和获取值(
findViewById())。
适用Activity和Fragment
编写插件
环境
Android Studio本身是不支持开发插件的,所以需要下载IntelliJ IDEA来编写,但是Android Studio是基于IntelliJ IDEA的,用IntelliJ IDEA不会感到陌生,官网下载https://www.jetbrains.com/idea/创建项目
目录结构
plugin.xml
plugin.xml是类似Android项目里面的AndroidMenifest文件,用来配置信息的注册和声明。
id:(com.example.plugin.Name)插件的ID,保证插件上传仓库后的唯一性。
name:插件名称。
version:版本号。
description:插件的简介。
change-notes:版本更新信息。
extensions:扩展组件注册 。
开始编写
创建一个Action,是继承AnAction类的
右键src目录->New->Action
Action" title="">
填写内容
填写ActionID,ClassName,Name,Description;选择放在哪个菜单,Anchor选择First或者Last;设置快捷键KeyBoard Shortcuts;
ActionID:代表该Action的唯一的ID,一般的格式为:pluginName.ID
ClassName:类名
Name:就是最终插件在菜单上的名称
Description:对这个Action的描述信息
Groups:定义这个菜单选项出现的位置,这里选中CodeMenu(Code),在Code菜单里面。
可以在plugin.xml
里面修改对应的Action属性
编写Action
点击ok之后会生成相应的Action,在Action里面的
actionPerformed方法会在点击菜单或者快捷键的是否触发。
思路
在获取布局文件内容后自动解析布局文件并生成字段和findViewById代码。
1.获取布局文件
2.解析布局文件,获取属性
3.将代码写入action
获取布局文件
查找文件需要用到PsiFile类,通过
FilenameIndex.getFilesByName(project, name, scope)来查找布局文件。
先获取用户选中内容,如果没选中,则弹出dialog让用户输入内容;
// 获取project Project project = e.getProject(); // 获取选中内容 final Editor mEditor = e.getData(PlatformDataKeys.EDITOR); if (null == mEditor) { return; } SelectionModel model = mEditor.getSelectionModel(); String selectedText = model.getSelectedText(); if (TextUtils.isEmpty(selectedText)) { // 未选中布局内容,显示dialog selectedText = Messages.showInputDialog(project, "layout(不需要输入R.layout.):" , "未选中布局内容,请输入layout文件名", Messages.getInformationIcon()); if (TextUtils.isEmpty(selectedText)) { Utils.showPopupBalloon(mEditor, "未输入layout文件名"); return; } }
然后根据输入的内容查找xml文件;
// 获取布局文件,通过FilenameIndex.getFilesByName获取 // GlobalSearchScope.allScope(project)搜索整个项目 PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, selectedText + ".xml", GlobalSearchScope.allScope(project)); if (psiFiles.length <= 0) { Utils.showPopupBalloon(mEditor, "未找到选中的布局文件"); return; } XmlFile xmlFile = (XmlFile) psiFiles[0];
解析布局文件,获取属性
通过psiFile.accept(new PsiRecursiveElementWalkingVisitor()…);去遍历一个文件的所有元素/** * 获取所有id * * @param file * @param elements * @return */ public static java.util.List<Element> getIDsFromLayout(final PsiFile file, final java.util.List<Element> elements) { // To iterate over the elements in a file // 遍历一个文件的所有元素 file.accept(new XmlRecursiveElementVisitor() { @Override public void visitElement(PsiElement element) { super.visitElement(element); // 解析Xml标签 if (element instanceof XmlTag) { XmlTag tag = (XmlTag) element; // 获取Tag的名字(TextView)或者自定义 String name = tag.getName(); // 如果有include if (name.equalsIgnoreCase("include")) { // 获取布局 XmlAttribute layout = tag.getAttribute("layout", null); // 获取project Project project = file.getProject(); // 布局文件 XmlFile include = null; PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, getLayoutName(layout.getValue()) + ".xml", GlobalSearchScope.allScope(project)); if (psiFiles.length > 0) { include = (XmlFile) psiFiles[0]; } if (include != null) { // 递归 getIDsFromLayout(include, elements); return; } } // 获取id字段属性 XmlAttribute id = tag.getAttribute("android:id", null); if (id == null) { return; } // 获取id的值 String idValue = id.getValue(); if (idValue == null) { return; } XmlAttribute aClass = tag.getAttribute("class", null); if (aClass != null) { name = aClass.getValue(); } // 添加到list try { Element e = new Element(name, idValue, tag); elements.add(e); } catch (IllegalArgumentException e) { } } } }); return elements; } /** * layout.getValue()返回的值为@layout/layout_view * * @param layout * @return */ public static String getLayoutName(String layout) { if (layout == null || !layout.startsWith("@") || !layout.contains("/")) { return null; } // @layout layout_view String[] parts = layout.split("/"); if (parts.length != 2) { return null; } // layout_view return parts[1]; }
对应的实体类Element,里面包含获取id的值,获取类型如(TextView或者com.example.CustomView),根据id设置变量名。
// 判断id正则 private static final Pattern sIdPattern = Pattern.compile("@\\+?(android:)?id/([^$]+)$", Pattern.CASE_INSENSITIVE); // id public String id; // 名字如TextView public String name; // 命名1 aa_bb_cc; 2 aaBbCc 3 mAaBbCc public int fieldNameType = 3; public XmlTag xml; /** * 构造函数 * * @param name View的名字 * @param id android:id属性 * @throws IllegalArgumentException When the arguments are invalid */ public Element(String name, String id, XmlTag xml) { // id final Matcher matcher = sIdPattern.matcher(id); if (matcher.find() && matcher.groupCount() > 1) { this.id = matcher.group(2); } if (this.id == null) { throw new IllegalArgumentException("Invalid format of view id"); } String[] packages = name.split("\\."); if (packages.length > 1) { // com.example.CustomView this.name = packages[packages.length - 1]; } else { this.name = name; } this.xml = xml; } /** * 获取id,R.id.id * * @return */ public String getFullID() { StringBuilder fullID = new StringBuilder(); String rPrefix = "R.id."; fullID.append(rPrefix); fullID.append(id); return fullID.toString(); } /** * 获取变量名 * * @return */ public String getFieldName() { String fieldName = id; String[] names = id.split("_"); if (fieldNameType == 2) { // aaBbCc StringBuilder sb = new StringBuilder(); for (int i = 0; i < names.length; i++) { if (i == 0) { sb.append(names[i]); } else { sb.append(firstToUpperCase(names[i])); } } fieldName = sb.toString(); } else if (fieldNameType == 3) { // mAaBbCc StringBuilder sb = new StringBuilder(); for (int i = 0; i < names.length; i++) { if (i == 0) { sb.append("m"); } sb.append(firstToUpperCase(names[i])); } fieldName = sb.toString(); } return fieldName; } // 第一个字母大写 public static String firstToUpperCase(String key) { return key.substring(0, 1).toUpperCase(Locale.CHINA) + key.substring(1); }
将代码写入action
Intellij Platform不允许在主线程中进行实时的文件写入,需通过异步任务来进行,可以通过继承WriteCommandAction.Simple,然后在run方法里面进行写文件操作。
@Override protected void run() throws Throwable { }
主要用到的方法
/** * 根据当前文件获取对应的class文件 * @param editor * @param file * @return */ protected PsiClass getTargetClass(Editor editor, PsiFile file) { int offset = editor.getCaretModel().getOffset(); PsiElement element = file.findElementAt(offset); if(element == null) { return null; } else { PsiClass target = PsiTreeUtil.getParentOfType(element, PsiClass.class); return target instanceof SyntheticElement ?null:target; } }
mClass.findMethodsByName("onCreate", false)判断类是否包含某方法
JavaPsiFacade.getInstance(mProject).findClass("android.app.Activity", new EverythingGlobalScope(mProject));根据类名查找类
PsiUtilBase.getPsiFileInEditor(mEditor, project);方法为获取当前文件;
psiclass.add(JavaPsiFacade.getElementFactory(mProject).createMethodFromText(sbInitView.toString(), psiclass))方法为类创建方法;
mFactory.mFactory.createMethodFromText(method.toString(), mClass)方法添加字段;
onCreate.getBody().addAfter(mFactory.createStatementFromText("initView();", mClass), setContentViewStatement);方法为方法体添加内容。
具体创建内容
import com.intellij.codeInsight.actions.ReformatCodeProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.command.WriteCommandAction.Simple; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.search.EverythingGlobalScope; import entity.Element; import org.apache.http.util.TextUtils; import java.util.List; public class IdCreator extends Simple { private PsiFile mFile; private Project mProject; private PsiClass mClass; private List<Element> mElements; private PsiElementFactory mFactory; private String mSelectText; public IdCreator(PsiFile psiFile, PsiClass psiClass, String command, List<Element> elements, String selectText) { super(psiClass.getProject(), command); mFile = psiFile; mProject = psiClass.getProject(); mClass = psiClass; mElements = elements; // 获取Factory mFactory = JavaPsiFacade.getElementFactory(mProject); mSelectText = selectText; } @Override protected void run() throws Throwable { generateFields(); generateFindViewById(); // 重写class JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(mProject); styleManager.optimizeImports(mFile); styleManager.shortenClassReferences(mClass); new ReformatCodeProcessor(mProject, mClass.getContainingFile(), null, false).runWithoutProgress(); } /** * 创建变量 */ private void generateFields() { for (Element element : mElements) { // remove duplicate field PsiField[] fields = mClass.getFields(); boolean duplicateField = false; for (PsiField field : fields) { String name = field.getName(); if (name != null && name.equals(element.getFieldName())) { duplicateField = true; break; } } if (duplicateField) { continue; } // 设置变量 String text = element.xml.getAttributeValue("android:text"); // text String fromText = "private " + element.name + " " + element.getFieldName() + ";"; if (!TextUtils.isEmpty(text)) { fromText = "/** " + text + " */\n" + fromText; } // 添加到class mClass.add(mFactory.createFieldFromText(fromText, mClass)); } } /** * 设置变量的值FindViewById,Activity和Fragment */ private void generateFindViewById() { // 根据类名查找类 PsiClass activityClass = JavaPsiFacade.getInstance(mProject).findClass("android.app.Activity", new EverythingGlobalScope(mProject)); PsiClass activityCompatClass = JavaPsiFacade.getInstance(mProject).findClass("android.support.v7.app.AppCompatActivity", new EverythingGlobalScope(mProject)); PsiClass fragmentClass = JavaPsiFacade.getInstance(mProject).findClass("android.app.Fragment", new EverythingGlobalScope(mProject)); PsiClass fragmentV4Class = JavaPsiFacade.getInstance(mProject).findClass("android.support.v4.app.Fragment", new EverythingGlobalScope(mProject)); // 判断mClass是不是继承activityClass或者activityCompatClass if ((activityClass != null && mClass.isInheritor(activityClass, true)) || (activityCompatClass != null && mClass.isInheritor(activityCompatClass, true)) || mClass.getName().contains("Activity")) { // 判断是否有onCreate方法 if (mClass.findMethodsByName("onCreate", false).length == 0) { StringBuilder method = new StringBuilder(); method.append("@Override protected void onCreate(android.os.Bundle savedInstanceState) {\n"); method.append("super.onCreate(savedInstanceState);\n"); method.append("\t// TODO:run FindViewById again To setValue in initView method\n"); method.append("\tsetContentView(R.layout."); method.append(mSelectText); method.append(");\n"); method.append("}"); // 添加 mClass.add(mFactory.createMethodFromText(method.toString(), mClass)); } else { // 获取setContentView PsiStatement setContentViewStatement = null; // onCreate是否存在initView方法 boolean hasInitViewStatement = false; PsiMethod onCreate = mClass.findMethodsByName("onCreate", false)[0]; for (PsiStatement psiStatement : onCreate.getBody().getStatements()) { // 查找setContentView if (psiStatement.getFirstChild() instanceof PsiMethodCallExpression) { PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) psiStatement.getFirstChild()).getMethodExpression(); if (methodExpression.getText().equals("setContentView")) { setContentViewStatement = psiStatement; } else if (methodExpression.getText().equals("initView")) { hasInitViewStatement = true; } } } if (!hasInitViewStatement && setContentViewStatement != null) { // 将initView()写到setContentView()后面 onCreate.getBody().addAfter(mFactory.createStatementFromText("initView();", mClass), setContentViewStatement); } generatorLayoutCode(null); } // 判断mClass是不是继承fragmentClass或者fragmentV4Class } else if ((fragmentClass != null && mClass.isInheritor(fragmentClass, true)) || (fragmentV4Class != null && mClass.isInheritor(fragmentV4Class, true)) || mClass.getName().contains("Fragment")) { // 判断是否有onCreateView方法 if (mClass.findMethodsByName("onCreateView", false).length == 0) { StringBuilder method = new StringBuilder(); method.append("@Override public View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup container, android.os.Bundle savedInstanceState) {\n"); method.append("\t// TODO: run FindViewById again To setValue in initView method\n"); method.append("\tView view = View.inflate(getActivity(), R.layout."); method.append(mSelectText); method.append(", null);"); method.append("return view;"); method.append("}"); // 添加 mClass.add(mFactory.createMethodFromText(method.toString(), mClass)); } else { // 查找onCreateView PsiReturnStatement returnStatement = null; // view String returnValue = null; // onCreateView是否存在initView方法 boolean hasInitViewStatement = false; PsiMethod onCreate = mClass.findMethodsByName("onCreateView", false)[0]; for (PsiStatement psiStatement : onCreate.getBody().getStatements()) { if (psiStatement instanceof PsiReturnStatement) { // 获取view的值 returnStatement = (PsiReturnStatement) psiStatement; returnValue = returnStatement.getReturnValue().getText(); } else if (psiStatement.getFirstChild() instanceof PsiMethodCallExpression) { PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) psiStatement.getFirstChild()).getMethodExpression(); if (methodExpression.getText().equals("initView")) { hasInitViewStatement = true; } } } if (!hasInitViewStatement && returnStatement != null && returnValue != null) { onCreate.getBody().addBefore(mFactory.createStatementFromText("initView(" + returnValue + ");", mClass), returnStatement); } generatorLayoutCode(returnValue); } } } /** * 写initView方法 * * @param findPre Fragment的话要view.findViewById */ private void generatorLayoutCode(String findPre) { // 判断是否已有initView方法 PsiMethod[] initViewMethods = mClass.findMethodsByName("initView", false); if (initViewMethods.length > 0 && initViewMethods[0].getBody() != null) { PsiCodeBlock initViewMethodBody = initViewMethods[0].getBody(); for (Element element : mElements) { String pre = TextUtils.isEmpty(findPre) ? "" : findPre + "."; String s2 = element.getFieldName() + " = (" + element.name + ") " + pre + "findViewById(" + element.getFullID() + ");"; initViewMethodBody.add(mFactory.createStatementFromText(s2, initViewMethods[0])); } } else { StringBuilder initView = new StringBuilder(); if (TextUtils.isEmpty(findPre)) { initView.append("private void initView() {\n"); } else { initView.append("private void initView(View " + findPre + ") {\n"); } for (Element element : mElements) { String pre = TextUtils.isEmpty(findPre) ? "" : findPre + "."; initView.append(element.getFieldName() + " = (" + element.name + ")" + pre + "findViewById(" + element.getFullID() + ");\n"); } initView.append("}\n"); mClass.add(mFactory.createMethodFromText(initView.toString(), mClass)); } } }
使用插件
导出插件Build->Prepare All Plugin Modules For Deployment
Android Studio导入插件,当前是本地,直接通过
Install plugin from disk...导入。
博客的代码不是完整的,更多内容可以到GitHub上下载查看GitHub,此插件以后会继续更新,欢迎Start,Issuse
发布插件
发布到IntelliJ Plugin仓库,支持在plugin中搜索安装,参考:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html
主要的步骤就是注册账号,提交相应的jar文件,然后填写信息,最后等待审核就可以了。
感谢
学会编写Android Studio插件 别停留在用的程度了http://blog.csdn.net/zhangke3016/article/details/53245530
相关文章推荐
- 自己编写Android Studio插件 别停留在用的程度了
- 自己编写Android Studio插件 别停留在用的程度了(转载)
- 自己编写Android Studio插件
- 学会编写Android Studio插件 别停留在用的程度了
- 自己动手实现一个Android Studio插件
- 初步编写IDEA\AndroidStudio翻译插件
- Android Cordova 插件开发之编写自己定义插件
- 自己动手实现一个Android Studio插件
- Idea-Plugin编写Android Studio插件
- 学会编写Android Studio插件 别停留在用的程度了
- 自己动手实现一个Android Studio插件
- 学会编写Android Studio插件 别停留在用的程度了
- 学会自己编写AndroidStduio插件
- 编写Android Studio插件
- 自己动手实现一个Android Studio插件
- 学会编写Android Studio插件 别停留在用的程度了
- 学会编写Android Studio插件 别停留在用的程度了
- 成为大神之路---学会编写Android Studio插件 别停留在用的程度了
- 学会编写Android Studio插件 别停留在用的程度了
- [置顶] 编写自己的JQUERY插件