1. 我已经将arc(android resource cleaner)的3.0 beta版本的源代码发布到google code,项目目前还很粗鄙,需要进一步的完善,里面也可能还有不少bug,需要解决,感兴趣的朋友可以联系我,里面有我的qq号码(286505491).
2. 项目所在地址: https://android-resource-cleaner.googlecode.com/svn/trunk/
3.项目里面两个主要的类:
ARC.java 主要负责UI和事件处理
package xiaogang.src; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.List; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.SwingUtilities; class ARC extends JFrame { private static final String VERSION_STR = "3.0 beta"; private static final long serialVersionUID = -1600532762007579488L; private JMenu mFileMenu; private JMenu mActionMenu; private JMenu mHelpMenu; private JMenuItem mOpenFileMenuItem; private JMenuItem mDeleteMenuItem; private JMenuItem mDeleteAllMenuItem; private JMenuItem mExitMenuItem; private JMenuItem mHelpMenuItem; private JButton mJButton; private JMenuBar mJMenuBar1; private JList mUnusedResList; private JSeparator mJSeparator1, mJSeparator2; private ResProbe mCleanRes; private String mSPath; private boolean running = false; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ARC inst = new ARC(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); } public ARC() { super(); setupGUI(); } private void setupGUI() { try { BorderLayout thisLayout = new BorderLayout(); getContentPane().setLayout(thisLayout); mUnusedResList = new JList(); mUnusedResList.setListData(new String[] { "请打开Android工程根目录" }); mUnusedResList.setLayoutOrientation(JList.VERTICAL); JScrollPane listScroller = new JScrollPane(mUnusedResList); getContentPane().add(listScroller, BorderLayout.CENTER); mJButton = new JButton("开始"); mJButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if (running) { mCleanRes.setbCancel(true); mJButton.setText("开始"); running = false; return; } if (mSPath == null && mSPath.length() <= 0) { JOptionPane.showMessageDialog(ARC.this, "选择目录", "Error", JOptionPane.ERROR_MESSAGE); } running = true; mJButton.setText("取消"); new Thread() { @Override public void run() { mCleanRes.run(ARC.this); } }.start(); } }); getContentPane().add(mJButton, BorderLayout.SOUTH); setSize(400, 300); { mJMenuBar1 = new JMenuBar(); setJMenuBar(mJMenuBar1); { mFileMenu = new JMenu(); mJMenuBar1.add(mFileMenu); mFileMenu.setText("文件"); { mOpenFileMenuItem = new JMenuItem(); mFileMenu.add(mOpenFileMenuItem); mOpenFileMenuItem.setText("打开"); mOpenFileMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("打开")) { JFileChooser jFileChooser = new JFileChooser(); jFileChooser .setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int returnVal = jFileChooser.showOpenDialog(ARC.this); if (returnVal == JFileChooser.APPROVE_OPTION) { mSPath = jFileChooser.getSelectedFile().getAbsolutePath(); mCleanRes = new ResProbe(mSPath); mUnusedResList.setListData(new String[] { "工程目录: "+mSPath }); mUnusedResList.invalidate(); } } } }); } { mJSeparator1 = new JSeparator(); mFileMenu.add(mJSeparator1); } { mExitMenuItem = new JMenuItem(); mFileMenu.add(mExitMenuItem); mExitMenuItem.setText("退出"); mExitMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { ARC.this.dispose(); } }); } } mActionMenu = new JMenu(); mJMenuBar1.add(mActionMenu); mActionMenu.setText("操作"); mDeleteMenuItem = new JMenuItem(); mActionMenu.add(mDeleteMenuItem); mDeleteMenuItem.setText("删除"); mDeleteMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { mCleanRes.delete(mUnusedResList.getSelectedIndices()); mUnusedResList.setListData(mCleanRes.getResult()); mUnusedResList.invalidate(); } }); mJSeparator2 = new JSeparator(); mActionMenu.add(mJSeparator2); mDeleteAllMenuItem = new JMenuItem(); mActionMenu.add(mDeleteAllMenuItem); mDeleteAllMenuItem.setText("删除所有"); mDeleteAllMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { mCleanRes.deleteAll(); mUnusedResList.setListData(mCleanRes.getResult()); mUnusedResList.invalidate(); } }); mHelpMenu = new JMenu(); mJMenuBar1.add(mHelpMenu); mHelpMenu.setText("帮助"); mHelpMenuItem = new JMenuItem(); mHelpMenu.add(mHelpMenuItem); mHelpMenuItem.setText("帮助"); mHelpMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { JOptionPane.showMessageDialog(ARC.this, "Version v" + VERSION_STR + "\n赵小刚, QQ: 286505491", "ARC", JOptionPane.INFORMATION_MESSAGE); } }); } this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); } catch (Exception e) { e.printStackTrace(); } } public void setResult(List<ResSet> list) { running = false; mJButton.setText("开始"); String[] fileList = new String[list.size()]; for (int i = 0; i < list.size(); i++) { fileList[i] = list.get(i).toString(); } if (fileList.length == 0) { fileList = new String[1]; fileList[0] = "No result"; } mUnusedResList.setListData(fileList); mUnusedResList.invalidate(); } public void setProgress(File file) { String[] pathList = new String[3]; pathList[0] = "正在处理... : " + mSPath; pathList[1] = file.getParent().replace(mSPath, ""); pathList[2] = file.getName(); mUnusedResList.setListData(pathList); mUnusedResList.invalidate(); } }
ResProbe.java 负责扫描各种资源文件,源代码,r文件,清单文件,然后经过对比,找出冗余资源的核心类
package xiaogang.src; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ResProbe { private volatile boolean isCanceled = false; private String mPackageName; private ARC mCallback; private File mBaseDirectory; private File mSrcDirectory; private File mResDirectory; private File mGenDirectory; private File mManifestFile; private File mRJavaFile; private List<ResSet> mResList = new ArrayList<ResSet>(); private final Set<ResSet> mResources = new HashSet<ResSet>(); private final Set<ResSet> mUsedResources = new HashSet<ResSet>(); private static final Pattern sResourceTypePattern = Pattern .compile("^\\s*public static final class (\\w+)\\s*\\{$"); private static final Pattern sResourceNamePattern = Pattern .compile("^\\s*public static( final)? int(\\[\\])? (\\w+)\\s*=\\s*(\\{|(0x)?[0-9A-Fa-f]+;)\\s*$"); private static final FileType sJavaFileType = new FileType("java", "R." + FileType.USAGE_TYPE + "." + FileType.USAGE_NAME + "[^\\w_]"); private static final FileType sXmlFileType = new FileType("xml", "[\" >]@" + FileType.USAGE_TYPE + "/" + FileType.USAGE_NAME + "[\" <]"); private static final Map<String, ResType> sResourceTypes = new HashMap<String, ResType>(); static { // anim sResourceTypes.put("anim", new ResType("anim") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals(getType())) { return false; } final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } }); // array sResourceTypes.put("array", new ResType("array") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<([a-z]+\\-)?array.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // attr sResourceTypes.put("attr", new ResType("attr") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<attr.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } @Override public boolean doesFileUseResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (parent != null) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("layout") && !directoryType.equals("values")) { return false; } } final Pattern pattern = Pattern.compile("<.+?:" + resourceName + "\\s*=\\s*\".*?\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } final Pattern itemPattern = Pattern.compile("<item.+?name\\s*=\\s*\"" + resourceName + "\".*?>"); final Matcher itemMatcher = itemPattern.matcher(fileContents); if (itemMatcher.find()) { return true; } return false; } }); // bool sResourceTypes.put("bool", new ResType("bool") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<bool.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // color sResourceTypes.put("color", new ResType("color") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<color.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // dimen sResourceTypes.put("dimen", new ResType("dimen") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<dimen.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // drawable sResourceTypes.put("drawable", new ResType("drawable") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (directoryType.equals(getType())) { final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } if (directoryType.equals("values")) { final Pattern pattern = Pattern.compile("<drawable.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } } return false; } }); // id sResourceTypes.put("id", new ResType("id") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values") && !directoryType.equals("layout")) { return false; } final Pattern valuesPattern0 = Pattern .compile("<item.*?type\\s*=\\s*\"id\".*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Pattern valuesPattern1 = Pattern.compile("<item.*?name\\s*=\\s*\"" + resourceName + "\".*?type\\s*=\\s*\"id\".*?/?>"); final Pattern layoutPattern = Pattern.compile(":id\\s*=\\s*\"@\\+id/" + resourceName + "\""); Matcher matcher = valuesPattern0.matcher(fileContents); if (matcher.find()) { return true; } matcher = valuesPattern1.matcher(fileContents); if (matcher.find()) { return true; } matcher = layoutPattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // integer sResourceTypes.put("integer", new ResType("integer") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<integer.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // layout sResourceTypes.put("layout", new ResType("layout") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals(getType())) { return false; } final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } }); // menu sResourceTypes.put("menu", new ResType("menu") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals(getType())) { return false; } final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } }); // plurals sResourceTypes.put("plurals", new ResType("plurals") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<plurals.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // raw sResourceTypes.put("raw", new ResType("raw") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals(getType())) { return false; } final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } }); // string sResourceTypes.put("string", new ResType("string") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<string.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } }); // style sResourceTypes.put("style", new ResType("style") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final Pattern pattern = Pattern.compile("<style.*?name\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } @Override public boolean doesFileUseResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (parent != null) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } } // (name="Parent.Child") final Pattern pattern = Pattern.compile("<style.*?name\\s*=\\s*\"" + resourceName + "\\.\\w+\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } // (parent="Parent") final Pattern pattern1 = Pattern.compile("<style.*?parent\\s*=\\s*\"" + resourceName + "\".*?/?>"); final Matcher matcher1 = pattern1.matcher(fileContents); if (matcher1.find()) { return true; } return false; } }); // styleable sResourceTypes.put("styleable", new ResType("styleable") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals("values")) { return false; } final String[] styleableAttr = resourceName.split("\\[_\\\\.\\]"); if (styleableAttr.length == 1) { final Pattern pattern = Pattern.compile("<declare-styleable.*?name\\s*=\\s*\"" + styleableAttr[0] + "\".*?/?>"); final Matcher matcher = pattern.matcher(fileContents); if (matcher.find()) { return true; } return false; } final Pattern blockPattern = Pattern.compile("<declare-styleable.*?name\\s*=\\s*\"" + styleableAttr[0] + "\".*?>(.*?)</declare-styleable\\s*>"); final Matcher blockMatcher = blockPattern.matcher(fileContents); if (blockMatcher.find()) { final String styleableAttributes = blockMatcher.group(1); final Pattern attributePattern = Pattern.compile("<attr.*?name\\s*=\\s*\"" + styleableAttr[1] + "\".*?/?>"); final Matcher attributeMatcher = attributePattern.matcher(styleableAttributes); if (attributeMatcher.find()) { return true; } return false; } return false; } }); // xml sResourceTypes.put("xml", new ResType("xml") { @Override public boolean doesFileDeclareResource(final File parent, final String fileName, final String fileContents, final String resourceName) { if (!parent.isDirectory()) { return false; } final String directoryType = parent.getName().split("-")[0]; if (!directoryType.equals(getType())) { return false; } final String name = fileName.split("\\.")[0]; final Pattern pattern = Pattern.compile("^" + resourceName + "$"); return pattern.matcher(name).find(); } }); } public ResProbe() { super(); final String baseDirectory = System.getProperty("user.dir"); mBaseDirectory = new File(baseDirectory); } protected ResProbe(final String baseDirectory) { super(); mBaseDirectory = new File(baseDirectory); } public void delete(int[] list) { LinkedList<ResSet> remove = new LinkedList<ResSet>(); for (int i : list) { ResSet result = mResList.get(i); final String type = result.getType(); System.out.println("type: "+type); if (type.equals("anim") || type.equals("drawable") || type.equals("layout")) { final String path = result.getPath(); System.out.println("path: "+path); if (!isEmpty(path)) { File file = new File(path); file.delete(); remove.add(result); } } } mResList.removeAll(remove); } public String[] getResult() { String[] result = new String[mResList.size()]; for (int i = 0; i < mResList.size(); i++) { result[i] = mResList.get(i).toString(); } return result; } public void deleteAll() { LinkedList<ResSet> remove = new LinkedList<ResSet>(); for (ResSet resource : mResList) { final String type = resource.getType(); if (type.equals("anim") || type.equals("drawable") || type.equals("layout") ||type.equals("menu")) { final String path = resource.getPath(); if (!isEmpty(path)) { File file = new File(path); file.delete(); remove.add(resource); } } } mResList.removeAll(remove); } private boolean isEmpty(CharSequence str) { if (str == null || str.length() == 0) return true; else return false; } public void showResult() { if (mCallback != null) { mCallback.setResult(mResList); } } @Override protected void finalize() throws Throwable { if (mResList != null) { mResList.clear(); } super.finalize(); } public void run(ARC callback) { isCanceled = false; if (callback != null) { this.mCallback = callback; } System.out.println("Running in: " + mBaseDirectory.getAbsolutePath()); findPaths(); if (mSrcDirectory == null || mResDirectory == null || mManifestFile == null) { System.err.println("The current directory is not a valid Android project root."); return; } mPackageName = findPackageName(mManifestFile); if (mPackageName == null || mPackageName.trim().length() == 0) { return; } if (mGenDirectory == null) { System.err.println("You must first build your project to generate R.java"); return; } mRJavaFile = findRJavaFile(mGenDirectory, mPackageName); if (mRJavaFile == null) { System.err.println("You must first build your project to generate R.java"); return; } mResources.clear(); try { mResources.addAll(getResourceList(mRJavaFile));//查找R文件 } catch (final IOException e) { System.err.println("The R.java found could not be opened."); e.printStackTrace(); } if (isCanceled) { return; } System.out.println(mResources.size() + " resources found"); System.out.println(); mUsedResources.clear(); searchFiles(null, mSrcDirectory, sJavaFileType); if (isCanceled) { return; } searchFiles(null, mResDirectory, sXmlFileType); if (isCanceled) { return; } searchFiles(null, mManifestFile, sXmlFileType); if (isCanceled) { return; } /* * Because attr and styleable are so closely linked, we need to do some * matching now to ensure we don't say an attr is unused if its * corresponding styleable is used. */ final Set<ResSet> extraUsedResources = new HashSet<ResSet>(); for (final ResSet resource : mResources) { if (resource.getType().equals("styleable")) { final String[] styleableAttr = resource.getName().split("_"); if (styleableAttr.length > 1) { final String attrName = styleableAttr[1]; final ResSet attrResourceTest = new ResSet("attr", attrName); if (mUsedResources.contains(attrResourceTest)) { extraUsedResources.add(resource); } } } else if (resource.getType().equals("attr")) { for (final ResSet usedResource : mUsedResources) { if (usedResource.getType().equals("styleable")) { final String[] styleableAttr = usedResource.getName().split("_"); if (styleableAttr.length > 1 && styleableAttr[1].equals(resource.getName())) { extraUsedResources.add(resource); } } } } } for (final ResSet resource : extraUsedResources) { mResources.remove(resource); mUsedResources.add(resource); } final SortedMap<String, SortedMap<String, ResSet>> unusedResources = new TreeMap<String, SortedMap<String, ResSet>>(); for (final ResSet resource : mResources) { final String type = resource.getType(); SortedMap<String, ResSet> typeMap = unusedResources.get(type); if (typeMap == null) { typeMap = new TreeMap<String, ResSet>(); unusedResources.put(type, typeMap); } typeMap.put(resource.getName(), resource); } final Map<String, ResType> unusedResourceTypes = new HashMap<String, ResType>( unusedResources.size()); for (final String type : unusedResources.keySet()) { final ResType resourceType = sResourceTypes.get(type); if (resourceType != null) { unusedResourceTypes.put(type, resourceType); } } findDeclaredPaths(null, mResDirectory, unusedResourceTypes, unusedResources); /* * Find the paths where the used resources are declared. */ final SortedMap<String, SortedMap<String, ResSet>> usedResources = new TreeMap<String, SortedMap<String, ResSet>>(); for (final ResSet resource : mUsedResources) { final String type = resource.getType(); SortedMap<String, ResSet> typeMap = usedResources.get(type); if (typeMap == null) { typeMap = new TreeMap<String, ResSet>(); usedResources.put(type, typeMap); } typeMap.put(resource.getName(), resource); } if (isCanceled) { return; } final Map<String, ResType> usedResourceTypes = new HashMap<String, ResType>( usedResources.size()); for (final String type : usedResources.keySet()) { final ResType resourceType = sResourceTypes.get(type); if (resourceType != null) { usedResourceTypes.put(type, resourceType); } } if (isCanceled) { return; } findDeclaredPaths(null, mResDirectory, usedResourceTypes, usedResources); final Set<ResSet> libraryProjectResources = getLibraryProjectResources(); for (final ResSet libraryResource : libraryProjectResources) { final SortedMap<String, ResSet> typedResources = unusedResources.get(libraryResource .getType()); if (typedResources != null) { final ResSet appResource = typedResources.get(libraryResource.getName()); if (appResource != null && appResource.hasNoDeclaredPaths()) { typedResources.remove(libraryResource.getName()); mResources.remove(appResource); } } } if (isCanceled) { return; } final int unusedResourceCount = mResources.size(); if (unusedResourceCount > 0) { System.out.println(unusedResourceCount + " unused resources were found:"); final SortedSet<ResSet> sortedResources = new TreeSet<ResSet>(mResources); for (final ResSet resource : sortedResources) { System.out.println(resource); } mResList.addAll(sortedResources); } showResult(); } private void findPaths() { final File[] children = mBaseDirectory.listFiles(); if (children == null) { return; } for (final File file : children) { if (file.isDirectory()) { if (file.getName().equals("src")) { mSrcDirectory = file; } else if (file.getName().equals("res")) { mResDirectory = file; } else if (file.getName().equals("gen")) { mGenDirectory = file; } } else if (file.getName().equals("AndroidManifest.xml")) { mManifestFile = file; } } } private static String findPackageName(final File androidManifestFile) { String manifest = ""; try { manifest = FileUtils.getFileContents(androidManifestFile); } catch (final IOException e) { e.printStackTrace(); } final Pattern pattern = Pattern .compile("<manifest\\s+.*?package\\s*=\\s*\"([A-Za-z0-9_\\.]+)\".*?>"); final Matcher matcher = pattern.matcher(manifest); if (matcher.find()) { return matcher.group(1); } return null; } private static File findRJavaFile(final File baseDirectory, final String packageName) { final File rJava = new File(baseDirectory, packageName.replace('.', '/') + "/R.java"); if (rJava.exists()) { return rJava; } return null; } public void setbCancel(boolean bCancel) { this.isCanceled = bCancel; } /** * Removes all resources declared in library projects. */ private Set<ResSet> getLibraryProjectResources() { final Set<ResSet> resources = new HashSet<ResSet>(); final File projectPropertiesFile = new File(mBaseDirectory, "project.properties"); if (!projectPropertiesFile.exists()) { return resources; } List<String> fileLines = new ArrayList<String>(); try { fileLines = FileUtils.getFileLines(projectPropertiesFile); } catch (final IOException e) { e.printStackTrace(); } final Pattern libraryProjectPattern = Pattern.compile( "^android\\.library\\.reference\\.\\d+=(.*)$", Pattern.CASE_INSENSITIVE); final List<String> libraryProjectPaths = new ArrayList<String>(); for (final String line : fileLines) { final Matcher libraryProjectMatcher = libraryProjectPattern.matcher(line); if (libraryProjectMatcher.find()) { libraryProjectPaths.add(libraryProjectMatcher.group(1)); } } for (final String libraryProjectPath : libraryProjectPaths) { final File libraryProjectDirectory = new File(mBaseDirectory, libraryProjectPath); if (libraryProjectDirectory.exists() && libraryProjectDirectory.isDirectory()) { final String libraryProjectPackageName = findPackageName(new File( libraryProjectDirectory, "AndroidManifest.xml")); final File libraryProjectRJavaFile = findRJavaFile(new File( libraryProjectDirectory, "gen"), libraryProjectPackageName); if (libraryProjectRJavaFile != null) { try { resources.addAll(getResourceList(libraryProjectRJavaFile)); } catch (final IOException e) { e.printStackTrace(); } } } } return resources; } private static Set<ResSet> getResourceList(final File rJavaFile) throws IOException { final InputStream inputStream = new FileInputStream(rJavaFile); final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); boolean done = false; final Set<ResSet> resources = new HashSet<ResSet>(); String type = ""; while (!done) { final String line = reader.readLine(); done = (line == null); if (line != null) { final Matcher typeMatcher = sResourceTypePattern.matcher(line); final Matcher nameMatcher = sResourceNamePattern.matcher(line); if (nameMatcher.find()) { resources.add(new ResSet(type, nameMatcher.group(3))); } else if (typeMatcher.find()) { type = typeMatcher.group(1); } } } reader.close(); inputStream.close(); return resources; } private void searchFiles(final File parent, final File file, final FileType fileType) { if (isCanceled) { return; } if (file.isDirectory()) { for (final File child : file.listFiles()) { if (isCanceled) { return; } searchFiles(file, child, fileType); } } else if (file.getName().endsWith(fileType.getExtension())) { try { if (isCanceled) { return; } searchFile(parent, file, fileType); if (mCallback != null) { mCallback.setProgress(file); } } catch (final IOException e) { System.err.println("There was a problem reading " + file.getAbsolutePath()); e.printStackTrace(); } } } private void searchFile(final File parent, final File file, final FileType fileType) throws IOException { final Set<ResSet> foundResources = new HashSet<ResSet>(); final String fileContents = FileUtils.getFileContents(file); for (final ResSet resource : mResources) { if (isCanceled) { return; } final Matcher matcher = fileType.getPattern(resource.getType(), resource.getName().replace("_", "[_\\.]")).matcher(fileContents); if (matcher.find()) { foundResources.add(resource); } else { final ResType type = sResourceTypes.get(resource.getType()); if (type != null && type.doesFileUseResource(parent, file.getName(), fileContents, resource .getName().replace("_", "[_\\.]"))) { foundResources.add(resource); } } } for (final ResSet resource : foundResources) { if (isCanceled) { return; } mUsedResources.add(resource); mResources.remove(resource); } } private void findDeclaredPaths(final File parent, final File file, final Map<String, ResType> resourceTypes, final Map<String, SortedMap<String, ResSet>> resources) { if (file.isDirectory()) { for (final File child : file.listFiles()) { if (isCanceled) { return; } if (!child.isHidden()) { findDeclaredPaths(file, child, resourceTypes, resources); } } } else { if (!file.isHidden()) { final String fileName = file.getName(); String fileContents = ""; try { fileContents = FileUtils.getFileContents(file); } catch (final IOException e) { e.printStackTrace(); } for (final ResType resourceType : resourceTypes.values()) { if (isCanceled) { return; } final Map<String, ResSet> typeMap = resources.get(resourceType.getType()); if (typeMap != null) { for (final ResSet resource : typeMap.values()) { if (isCanceled) { return; } if (resourceType.doesFileDeclareResource(parent, fileName, fileContents, resource.getName().replace("_", "[_\\.]"))) { resource.addDeclaredPath(file.getAbsolutePath()); final String configuration = parent.getName(); resource.addConfiguration(configuration); } } } } } } } }
4.感兴趣的朋友可以联系我,加入到维护和后续版本的开发中去。