此工具在我的github上。地址:https://github.com/NashLegend/AndroidResourceCleaner


很多人都知道androidunusedresources.jar这个工具,它可以把Android项目中无用的资源列出来。然而它所做的也就止于此了,在列出所有的无用资源以后,开发者们还得手动删除这些文件,这实在是一个没技术含量却又烦人的体力活,作为程序员,自然是有解决办法的,我们为什么不写一个程序,让程序来实现这个功能呢?有些人给出了部分解决方案,就是清除部分无用资源,比如layout和drawable,因为这种资源是和文件一一对应的,只要删除对应文件就可以实现资源的清理,但是对于其他没用到的如string、style等资源却没有处理。而本文的工具则弥补了这个缺点,可以清理所有的资源文件。


这个功能要实现的功能应该是这样的:

1、读取androidunusedresources.jar导出的无用资源列表。

2、清理无用的资源,包括删除无用文件以及修改包含无用资源的文件。

对于drawable和layout等资源,当然是直接删掉文件就行了,因为一个文件就对应着一个资源,而对于其他的资源就不是这么一回事了,一个文件里面可能有很多资源,所以只能是删除文件里面的一部分,而保留另一部分。


程序大体如下:

首先是main

publicstaticvoidmain(String[]args){if(args.length>0){unusedCleaner(args[0]);}}

调用了unusedCleaner方法,我们就用这个方法清理无用资源,传入的参数是androidunusedresources.jar导出的无用资源文件列表。

然后看unusedCleaner方法。

//currents是在找到下一个文件前列出的所有资源id。因为一个文件可能对应着多个id,有可能是一个多对多的关系。staticArrayList<TypeSource>currents=newArrayList<>();staticbooleanLastIsPath=false;publicstaticvoidunusedCleaner(StringfilePath){ArrayList<TypeSource>files=newArrayList<>();//待清理资源列表try{Filefile=newFile(filePath);//androidunusedresources导出的无用资源文件列表if(file.isFile()&&file.exists()){InputStreamReaderread=newInputStreamReader(newFileInputStream(file),"utf-8");BufferedReaderbufferedReader=newBufferedReader(read);StringlineTxt=null;while((lineTxt=bufferedReader.readLine())!=null){if(!parseType(lineTxt)){//逐行读取数据,并提取数据。parseType方法可以判断是否此行代表发现了资源名。对应的格式如drawable:bg_list//如果不是的话,那么这一行就有可能是资源所对应的文件。Stringtrim=lineTxt.trim();if(newFile(trim).exists()){//简单的用文件是否存在判断此行到底是不是一个文件名,如果是的话,说明currents列表中所有对应的资源名肯定包含在这个文件里//就把它添加到一个待清理的资源列表中,将这些资源和文件对应起来,并添加到待清理列表中for(Iterator<TypeSource>iterator=currents.iterator();iterator.hasNext();){TypeSourcetypeSource=(TypeSource)iterator.next().clone();typeSource.path=trim;typeSource.xmlTag=typeSource.getXmlTag();files.add(typeSource);}LastIsPath=true;}else{continue;}}}read.close();}else{System.out.println("noFile");}}catch(Exceptione){System.out.println("Failed");e.printStackTrace();}//全部找出来后,将这些资源一一清理就是了,清理文件是TypeSource的方法,TypeSource类代表一个资源。for(Iterator<TypeSource>iterator=files.iterator();iterator.hasNext();){TypeSourcetypeSource=(TypeSource)iterator.next();System.out.println(typeSource);typeSource.cleanSelf();}System.out.println("done");}publicstaticbooleanparseType(StringlineTxt){//判断当前行是不是一个资源名。如果是的话,加入到currents中,如果上一个资源是文件,那么清空currents,因为之前currents中的已经加入了待清理列表Stringreg="((drawable)|(layout)|(dimen)|(string)|(attr)|(style)|(styleable)|(color)|(id)|(anim))\\s*:\\s*(\\S+)";Matchermatcher=Pattern.compile(reg).matcher(lineTxt);//使用正则匹配当前行if(matcher.find()){if(LastIsPath){currents.clear();}LastIsPath=false;TypeSourcetypeSource=newTypeSource();typeSource.type=matcher.group(1);typeSource.name=matcher.group(12);currents.add(typeSource);returntrue;}else{returnfalse;}}staticclassTypeSource{Stringtype="";//类型Stringname="";//xml中的name属性StringxmlTag="";//xml的tag名Stringpath="";//属于哪个文件publicStringgetXmlTag(){if("styleable".equals(type)){return"declare-styleable";}else{returntype;}}@OverridepublicStringtoString(){returntype+"|"+name+"|"+xmlTag+"|"+path;}/***一个一个的单独删,反正很快,就不考虑效率了。如果把一个文件对应的所有资源列出来统一删除应该很快,但是这里偷懒了*/publicvoidcleanSelf(){try{if(type==null){return;}if(type.equals("drawable")||type.equals("layout")||type.equals("anim")){newFile(path).delete();}elseif(type.equals("id")||type.equals("")){//跳过了id资源,如果要删除的话,跟deleteNodeByName方法差不多。}else{//deleteNodeByName方法删除xml中单个资源项。deleteNodeByName(path,xmlTag,name);}}catch(Exceptione){}}publicTypeSourceclone(){TypeSourcets=newTypeSource();ts.type=type;ts.name=name;ts.xmlTag=xmlTag;ts.path=path;returnts;}}@SuppressWarnings("unchecked")publicstaticvoiddeleteNodeByName(Stringpath,Stringtag,Stringname){//删除xml文件中的某个资源项try{SAXReaderreader=newSAXReader();Documentdocument=reader.read(newFile(path));Elementelement=document.getRootElement();List<Element>list=element.elements(tag);for(inti=0;i<list.size();i++){Elementele=list.get(i);StringtName=ele.attributeValue("name");if(tName!=null&&tName.length()>0){if(name.equals(ele.attributeValue("name"))){element.remove(ele);break;}}}OutputFormatformat=newOutputFormat("",false);//XMLWriterxmlWriter=newXMLWriter(newFileWriter(path),format);xmlWriter.write(document);xmlWriter.flush();}catch(Exceptione1){e1.printStackTrace();}}


然后导出成jar,使用方法如下

java-jarAndroidUnusedResources.jar>del.txtjava-jarcleaner.jardel.txt

好了,原来几小时搞定的事现在只要几秒钟了。

由于被无用资源引用的资源不会被视为无用资源,所以要多执行几遍上面的命令才能真正清除掉


注意由于androidunusedresources导出的结果并不十分准确,有可能出错,所以会导致清理有可能不准确。可能会漏掉某些资源删除不了。这时候就得动手了。


最后,记得的清理资源前先把代码提交一下,你懂的