之前写过一篇java项目的自动化删除lint到的无用资源,由于使用起来每次都得运行java项目,虽然也不麻烦,但是android studio支持插件开发,于是就想通过插件开发达到一键永逸,使用起来也很方便;正好同事也感兴趣所以一起研究插件开发,这也是我们android开发人员进阶的一项重要技能。毕竟有很多重复性工作,可以使用插件这玩意代替人工,效率也会大大的提高。。。比如前边有几篇用java写的都可以使用插件去开发。毕竟这个插件是支持java编程的所以移植过来很方便。这里挑了一个比较简单的之前用Java写的自动化删除lint到的无用资源改用插件开发。随后会附上demo。
由于插件是用java开发的,所以支持所有java的api,插件的UI用java中的swing,如果不是特别了解swing的可以去看看,我相信做android的了解swing的组件也不难。这里写的比较简单没用到UI,但是同事有一个demo插件写的还不错该有的基本都会有了,随后也附上一起学习学习。
在学习之前需要安装好intelliJ IEDA:这里推荐自己收藏的资料,按照如下基本成功的安装好IDEA以及如何创建一个插件项目和android studio所需要的插件安装包
intelli idea插件开发 下载 community免费版
安装jdk及配置环境变量
安装ide及配置Sdk(Java、Android、IntelliJ IDEA)
http://idea.lanyus.com/ ide 注册码获取
安装的ide本身就是sdk
4、文档
http://www.doc88.com/p-7758859318883.html 中文文档
http://www.jetbrains.org/intellij/sdk/docs/basics/architectural_overview/virtual_file.html 官方开发以及API文档
注意:这里的SDK不是android中的SDK,其实不用关心,我们只需要配置JDK1.8以上的就行,不用配置什么SDK,因为ide本身就是sdk
注意:根据自己的操作系统选择下载community这款就行
环境搭建好了:接下来推荐一个博客写的和创建一个插件项目、如何安装插件、以及一些demo
http://blog.csdn.net/lmj623565791/article/details/51548272
http://www.open-open.com/lib/view/open1477035480914.html
http://blog.csdn.net/liuloua/article/details/51917362
相信通过上边的详细阅读基本了解如何开发一个插件了,接下来就是对API的了解以及使用,主要是那几个关键类表示的是什么以及如何使用,这里建议通过阅读官方的开发文档
http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html
不过官方的开发文档并没有他们的用法,也很不全,看的是云里雾里的,只是讲解了一下API,所以这里上传了自己总结的关键API以及同事写的比较全的Demo以及文档
http://download.csdn.net/detail/zhongwn/9688623
http://download.csdn.net/detail/zhongwn/9688626
那我们一起来看看代码的实现吧:这里同样假设你已经拿到了lint-results.xml:获取的网址如下:
http://blog.csdn.net/imesong/article/details/49187695
这里创建一个action
/** * Created by zhongwr on 2016/11/19. */ public class DeleteMainAction extends AnAction { /** * 代表当前项目 */ Project project; @Override public void actionPerformed(AnActionEvent e) { project = e.getData(PlatformDataKeys.PROJECT); //获取当前项目的根路径 Constant.PROJECT_ROOT_DIR = project.getBasePath(); Log.pln("PROJECT_ROOT_DIR = " + Constant.PROJECT_ROOT_DIR); // e.getData(PlatformDataKeys.EDITOR).getDocument();//读取的是当前文档 //解析指定目录的lint文件 List<String> resList = XmlPraser.parseUnusedResXml(Constant.LINT_RESULT_XML_PATH); Log.pln("List = " + resList.toString()); //获取指定文件的document Document lintResultXmlDoc = ConfigUtils.getConfigDocument(Constant.LINT_RESULT_XML_PATH); PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(lintResultXmlDoc); // 获取当前编辑器中的文件 // Project project = e.getData(PlatformDataKeys.PROJECT); // Editor editor = e.getData(PlatformDataKeys.EDITOR); // PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); // 获取当前类 // PsiClass targetClass = getTargetClass(editor, file); //获取元素操作的工厂类,通过这个工厂可根据字符串创建方法、属性、类 element指的就是方法、属性、类等 // PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); // new DeleteUnuseResAction(project,psiFile).execute(); new DeleteUnuseResAction(project, psiFile).executeAction(); }
package util; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserFactory; import java.io.*; import java.util.ArrayList; import java.util.List; /** * Created by zhongwr on 2016/11/19. */ public class XmlPraser { /** * 例子: * <student id="5" group="5"> * <name>小明</name> * <sex>男</sex> * <age>18</age> * <email>xiaoming@163.com</email> * <birthday>1987-06-08</birthday> * <memo>好学生</memo> * </student> * <p> * switch (eventType) { * //文档开始 * case XmlPullParser.START_DOCUMENT: * list=new ArrayList<Student>(); * break; * //开始节点 * case XmlPullParser.START_TAG: * //判断如果其实节点为student * if("student".equals(nodeName)){ * //实例化student对象 * student=new Student(); * //设置Id属性 * student.setId(Integer.parseInt(xmlPullParser.getAttributeValue(0))); * //设置Group属性 * student.setGroup(Integer.parseInt(xmlPullParser.getAttributeValue(1))); * }else if("name".equals(nodeName)){ * //设置name * student.setName(xmlPullParser.nextText()); * <p> * } * break; * //结束节点 * case XmlPullParser.END_TAG: * if("student".equals(nodeName)){ * list.add(student); * student=null; * } * break; * default: * break; * } * eventType=xmlPullParser.next(); * } * <p> * <p> * pull解析lint的xml--获取要删除资源的路径 如:d:/results.xml * * @return * @author zhongwr */ public static List<String> parseUnusedResXml(String filePath) { List<String> pathLists = new ArrayList<String>(); FileInputStream fis = null; InputStream is = null; File file = new File(Constant.PROJECT_ROOT_DIR + filePath); if(!file.exists()){ Log.pln("请确保lint-result.xml文件存在"); return pathLists; } try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); // 获取XmlPullParser实例 XmlPullParser pullParser = factory.newPullParser(); File resFile = new File(Constant.PROJECT_ROOT_DIR + filePath); if (!resFile.isFile()) { System.out.println("file is not exist"); return pathLists; } Log.pln("parseUnusedResXml = " + Constant.PROJECT_ROOT_DIR + filePath); fis = new FileInputStream(file); is = new BufferedInputStream(fis); pullParser.setInput(is, "UTF-8"); // 开始 int eventType = pullParser.getEventType(); boolean isUnusedPath = false; while (eventType != XmlPullParser.END_DOCUMENT) {// 文档没读取完 String nodeName = pullParser.getName(); switch (eventType) { // 文档开始 case XmlPullParser.START_DOCUMENT: pathLists = new ArrayList<String>(); break; // 开始节点 case XmlPullParser.START_TAG: // System.out.println( "START_TAG Node = " + nodeName); // 过滤要删除的未使用的资源 if ("issue".equals(nodeName) && "UnusedResources".equals(pullParser.getAttributeValue(0))) { isUnusedPath = true; System.out.println("UnusedResources Node = " + nodeName); } else if ("location".equals(nodeName) && isUnusedPath) { System.out.println("location Node = " + nodeName); String path = pullParser.getAttributeValue(0); System.out.println("path = " + path); // 删除指定目录下的资源,防止删除lib下的资源 以及 过滤掉不需要删除的目录 // String rootDir = Constant.PROJECT_ROOT_DIR.replace("\\","\\\\"); //测试使用: String rootDir = "D:\\soft\\studio_project\\DeleteUnusedRes"; Log.pln("rootDir " + rootDir); Log.pln("path tru ? " + (path.startsWith( rootDir+ "\\app"))); if (path.startsWith(rootDir + "\\app") && !path.contains("res\\values")) { pathLists.add(path); } } break; // 结束节点 case XmlPullParser.END_TAG: if ("issue".equals(nodeName)) { isUnusedPath = false; } // System.out.println( "END_TAG Node = " + nodeName); break; default: break; } // 手动的触发下一个事件 eventType = pullParser.next(); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } System.out.println("pathLists = " + pathLists.size()); return pathLists; } }
通过project获取当前项目,可以获取到当前项目的根目录,得到根目录之后就可以随意操作目录的文件了,得到根目录之后,配置lint-results.xml的路径就可以解析lint的文件从而得到了无用资源(drawable、layout)的绝对路径,然后执行DeleteUnuseResAction删除这些资源
/** * 删除无用资源管理 * Created by zhongwr on 2016/6/17. */ public class DeleteUnuseResAction extends WriteCommandAction.Simple { public DeleteUnuseResAction(Project project, PsiFile... files) { super(project, files); } @Override protected void run() throws Throwable { Log.pln("===run===run=="); } /** * 删除无用资源 */ public void executeAction() { final List<String> pathList = XmlPraser.parseUnusedResXml(Constant.LINT_RESULT_XML_PATH); int size = pathList.size(); Log.pln("size = "+size); String filePath = null; for (int i = 0; i < size; i++) { filePath = pathList.get(i); try { FileUtil.copyFile(filePath, Constant.BACKUP_PATH + filePath.substring(filePath.lastIndexOf("\\") + 1, filePath.length())); } catch (Exception e) { e.printStackTrace(); } finally { FileUtil.deletFile(filePath); } } } }这个action是继承自
WriteCommandAction.Simple
这个类会实现一个run()方法,它是一个工作线程,所有耗时的操作都可以放在这里执行,感兴趣的可以看看源码实现,其实它也是封装了子线程的调用,可以查看我上传的API文档说明,那里有源码的子线程调用;
但是这里我尝试了删除文件没放在run()方法中执行也是可以的,但是最好放置在子线程中删除吧;
这里删除资源之前我们先将其备份,默认备份在d:/dele_res_backup/目录下,可以在配置文件自行修改;备份完后就可以安安心心将资源文件删除了。
package util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; /** * Created by Administrator on 2016/11/19. */ public class FileUtil { /** * 删除 未使用的冗余资源(图片 xml布局) * <p> * false 显示资源列表 * <p> * true 显示资源列表 并删除资源 * * @throws Exception */ public static void deletFile(String filePath) { File file = new File(filePath); System.out.println("file.isFile() = " + file.isFile()); if (file.isFile() && file.exists()) { // 判断文件是否存在 try { file.delete(); System.out.println("删除成功"); } catch (Exception e) { } } } /** * 复制单个文件:将删除的文件备份 * * @param oldPath * String 原文件路径 如:c:/fqf.txt * @param newPath * String 复制后路径 如:f:/fqf.txt * @return boolean * @author zhongwr */ public static void copyFile(String oldPath, String newPath) { try { // System.out.println("oldPath = " + oldPath + " \\n newPath = " + newPath); int byteread = 0; File newfile = new File(newPath); if (new File(newPath).exists()) { System.out.println("文件已存在 : " + newPath); return; } if (!newfile.getParentFile().exists()) { boolean isOk = newfile.getParentFile().mkdirs(); System.out.println("isOk = " + isOk); } File oldfile = new File(oldPath); if (oldfile.exists()) { // 文件存在时 InputStream inStream = new FileInputStream(oldPath); // 读入原文件 FileOutputStream fs = new FileOutputStream(newPath); byte[] buffer = new byte[inStream.available()]; while ((byteread = inStream.read(buffer)) != -1) { fs.write(buffer, 0, byteread); } inStream.close(); System.out.println("复制文件成功 = " + oldPath); } else { System.out.println("文件不存在 "); } } catch (Exception e) { System.out.println("复制单个文件操作出错"); e.printStackTrace(); } } }有些注释是我故意写上去备注的,方便理解,也可作为参考使用吧。
通过这个例子会发现所有的代码感觉都好熟悉有木有,其实都是java代码,就连解析xml都可以是xpull解析,所以我们做安卓的很容易上手,难点就是理解Project、Document、PSiFile等等新概念这些建议边用边差查文档吧,毕竟我们不是专门开发插件的,随用随查也是一项重要的开发技能咯。到这基本完事了。例子可直接使用也可作为参考,demo地址如下:
http://download.csdn.net/detail/zhongwn/9688658