├── .gitignore ├── README.md ├── libs └── dom4j-1.6.1.jar └── src └── Main.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | 10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 11 | hs_err_pid* 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidResourceCleaner 2 | 3 | 自动清理Android项目的无用资源。配合AndroidUnusedResources.jar使用,先将代码导出为jar文件,如cleaner.jar,再使用AndroidUnusedResources检测无用资源并导出到文本文件,再使用cleaner.jar解析文本文件,自动删除文本文件中列出的无用资源。 4 | 5 | 用法示例: 6 | ``` 7 | java -jar AndroidUnusedResources.jar >del.txt 8 | java -jar cleaner.jar del.txt 9 | ``` 10 | 11 | 不会清理.id资源 12 | 13 | 14 | ### 仅支持UTF-8编码的工程,非UTF-8的工程没有测试过,如非UTF-8编码的工程,请自行修改源码内的 encoding 15 | 16 | 17 | Android Studio现在原生支持此功能 18 | -------------------------------------------------------------------------------- /libs/dom4j-1.6.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/AndroidResourceCleaner/7a9397da9f78c0a87f01cf50b296853f35b2e0be/libs/dom4j-1.6.1.jar -------------------------------------------------------------------------------- /src/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.BufferedReader; 2 | import java.io.File; 3 | import java.io.FileInputStream; 4 | import java.io.FileWriter; 5 | import java.io.InputStreamReader; 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import org.dom4j.Document; 13 | import org.dom4j.Element; 14 | import org.dom4j.io.OutputFormat; 15 | import org.dom4j.io.SAXReader; 16 | import org.dom4j.io.XMLWriter; 17 | 18 | public class Main { 19 | 20 | public static void main(String[] args) { 21 | if (args.length > 0) { 22 | unusedCleaner(args[0]); 23 | } 24 | } 25 | 26 | static ArrayList currents = new ArrayList<>(); 27 | static final ArrayList AllFiles = new ArrayList<>(); 28 | static String encoding = "utf-8"; 29 | 30 | static boolean LastIsPath = false; 31 | 32 | public static void unusedCleaner(String filePath) { 33 | int crtLine = 0; 34 | ArrayList files = new ArrayList<>(); 35 | try { 36 | File file = new File(filePath); 37 | if (file.isFile() && file.exists()) { 38 | InputStreamReader read = new InputStreamReader( 39 | new FileInputStream(file), encoding); 40 | BufferedReader bufferedReader = new BufferedReader(read); 41 | String lineTxt = null; 42 | while ((lineTxt = bufferedReader.readLine()) != null) { 43 | if (crtLine == 0) { 44 | parsellFiles(lineTxt); 45 | } else { 46 | if (!parseType(lineTxt)) { 47 | String trim = lineTxt.trim(); 48 | if (new File(trim).exists()) { 49 | for (Iterator iterator = currents 50 | .iterator(); iterator.hasNext();) { 51 | TypeSource typeSource = (TypeSource) iterator 52 | .next().clone(); 53 | typeSource.path = trim; 54 | typeSource.xmlTag = typeSource.getXmlTag(); 55 | files.add(typeSource); 56 | } 57 | LastIsPath = true; 58 | } 59 | } 60 | } 61 | crtLine++; 62 | } 63 | read.close(); 64 | } else { 65 | System.out.println("noFile"); 66 | } 67 | } catch (Exception e) { 68 | System.out.println("Failed"); 69 | e.printStackTrace(); 70 | } 71 | 72 | for (Iterator iterator = files.iterator(); iterator 73 | .hasNext();) { 74 | TypeSource typeSource = (TypeSource) iterator.next(); 75 | System.out.println(typeSource); 76 | typeSource.cleanSelf(); 77 | } 78 | System.out.println("done"); 79 | } 80 | 81 | public static void parsellFiles(String line) { 82 | String reg = "Running in:\\s+(\\S+)"; 83 | Matcher matcher = Pattern.compile(reg).matcher(line); 84 | if (matcher.find()) { 85 | String path = matcher.group(1); 86 | File file = new File(path); 87 | scanSourceFiles(file); 88 | } 89 | } 90 | 91 | public static boolean parseType(String lineTxt) { 92 | // drawable/anim/layout/ 93 | String reg = "((drawable)|(anim)|(layout)|(dimen)|(string)|(attr)|(style)|(styleable)|(color)|(id))\\s*:\\s*(\\S+)"; 94 | Matcher matcher = Pattern.compile(reg).matcher(lineTxt); 95 | if (matcher.find()) { 96 | if (LastIsPath) { 97 | currents.clear(); 98 | } 99 | LastIsPath = false; 100 | TypeSource typeSource = new TypeSource(); 101 | typeSource.type = matcher.group(1); 102 | typeSource.name = matcher.group(matcher.groupCount()); 103 | currents.add(typeSource); 104 | return true; 105 | } else { 106 | return false; 107 | } 108 | } 109 | 110 | @SuppressWarnings("unchecked") 111 | public static void deleteNodeByName(String path, String tag, String name) { 112 | try { 113 | SAXReader reader = new SAXReader(); 114 | Document document = reader.read(new File(path)); 115 | Element element = document.getRootElement(); 116 | List list = element.elements("dimen"); 117 | for (int i = 0; i < list.size(); i++) { 118 | Element ele = list.get(i); 119 | String tName = ele.attributeValue("name"); 120 | if (tName != null && tName.length() > 0) { 121 | if (name.equals(ele.attributeValue("name"))) { 122 | element.remove(ele); 123 | break; 124 | } 125 | } 126 | } 127 | OutputFormat format = new OutputFormat("", false);// 128 | XMLWriter xmlWriter = new XMLWriter(new FileWriter(path), format); 129 | xmlWriter.write(document); 130 | xmlWriter.flush(); 131 | } catch (Exception e1) { 132 | e1.printStackTrace(); 133 | } 134 | } 135 | 136 | public static void scanSourceFiles(File parentFile) { 137 | File[] files = parentFile.listFiles(); 138 | if (files != null) { 139 | for (int i = 0; i < files.length; i++) { 140 | File file = files[i]; 141 | if (file.isDirectory()) { 142 | scanSourceFiles(file); 143 | } else { 144 | AllFiles.add(file); 145 | } 146 | } 147 | } 148 | } 149 | 150 | static class TypeSource { 151 | String type = "";// 类型 152 | String name = "";// xml中的name属性 153 | String xmlTag = "";// xml的tag名 154 | String path = "";// 属于哪个文件 155 | 156 | public String getXmlTag() { 157 | if ("styleable".equals(type)) { 158 | return "declare-styleable"; 159 | } else { 160 | return type; 161 | } 162 | } 163 | 164 | @Override 165 | public String toString() { 166 | return type + " | " + name + " | " + xmlTag + " | " + path; 167 | } 168 | 169 | /** 170 | * 一个一个的单独删,要啥效率啊 171 | */ 172 | public void cleanSelf() { 173 | try { 174 | if (type == null) { 175 | return; 176 | } 177 | if (type.equals("drawable") || type.equals("layout") 178 | || type.equals("anim")) { 179 | checkAndDeleteFile(); 180 | } else if (type.equals("color")) { 181 | if (pathMatchesWithType()) { 182 | new File(path).delete(); 183 | } else { 184 | deleteNodeByName(path, xmlTag, name); 185 | } 186 | } else if (type.equals("id") || type.equals("")) { 187 | // do nothing 188 | } else { 189 | deleteNodeByName(path, xmlTag, name); 190 | } 191 | } catch (Exception e) { 192 | 193 | } 194 | } 195 | 196 | public void checkAndDeleteFile() { 197 | if (pathMatchesWithType()) { 198 | new File(path).delete(); 199 | } else { 200 | //不放心的话可以跳过,不执行这一块,这种情况很少见,一般是过期的R文件导致 201 | if (type.equals("drawable")) { 202 | for (int i = 0; i < AllFiles.size(); i++) { 203 | File file = AllFiles.get(i); 204 | if (file.getName().equals(name + ".xml") 205 | && file.getParentFile().getName() 206 | .startsWith("drawable")) { 207 | file.delete(); 208 | } 209 | } 210 | } else if (type.equals("layout")) { 211 | for (int i = 0; i < AllFiles.size(); i++) { 212 | File file = AllFiles.get(i); 213 | if (file.getName().equals(name + ".xml") 214 | && file.getParentFile().getName() 215 | .startsWith("layout")) { 216 | file.delete(); 217 | } 218 | } 219 | } else if (type.equals("anim")) { 220 | for (int i = 0; i < AllFiles.size(); i++) { 221 | File file = AllFiles.get(i); 222 | if (file.getName().equals(name + ".xml") 223 | && file.getParentFile().getName() 224 | .startsWith("anim")) { 225 | file.delete(); 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | public boolean pathMatchesWithType() { 233 | boolean flag = false; 234 | File file = new File(path); 235 | File parentFile = file.getParentFile(); 236 | if (type.equals("drawable")) { 237 | flag = parentFile.getName().startsWith("drawable"); 238 | } else if (type.equals("layout")) { 239 | flag = parentFile.getName().startsWith("layout"); 240 | } else if (type.equals("anim")) { 241 | flag = parentFile.getName().startsWith("anim"); 242 | } else if (type.equals("color")) { 243 | flag = parentFile.getName().startsWith("color"); 244 | } 245 | return flag; 246 | } 247 | 248 | public TypeSource clone() { 249 | TypeSource ts = new TypeSource(); 250 | ts.type = type; 251 | ts.name = name; 252 | ts.xmlTag = xmlTag; 253 | ts.path = path; 254 | return ts; 255 | } 256 | } 257 | } 258 | --------------------------------------------------------------------------------