1. 程式人生 > >Android 減小安裝包大小(一)Lint

Android 減小安裝包大小(一)Lint

前言

Android Studio提供了一套靜態程式碼分析工具。它可以檢查出:xml檔案中 unused resources沒有使用到的資源等等。
這篇文章的主題是APK Reduce Size。
所以第一步,用Lint來 Check unused resources。
第二步,我們利用第一步的結果,java 實現 batch clean unused resource。

Check unused resources

Android Lint執行

Android studio –>Analyze–>Inspect Code,根據指示確定就好。
這裡寫圖片描述

Lint 結果

Inspection –> Android>Lint>Performance–> Unused resources,這裡列出了Demo中的Unused Resources,可以雙擊跳轉到指定位置刪除,
這裡寫圖片描述
也可以複製到檔案,批量處理。如下圖。
這裡寫圖片描述

批量處理 unused resource

處理之前,我們有必要知道下資原始檔的結構
有的資源有xml File形式drawable,layout,mipmap,menu,anim
有的資源有行key-value形式string,dimen,color
這裡寫圖片描述
我們通過上一步得到一個檔案,下面處理的
clean的java程式碼實現地址:

https://github.com/AlvinScrp/PerformanceOptimizationCase/tree/master/unusedResourcesCleanTool
處理流程大致就是檔案操作:
1.檔案先轉成一個Map,
2.兩層遍歷res 目錄和Map,根據xml File/key-value 刪除resource
PS:在使用之前需要配置你的sourceProjectResDir和lintResultFilePath
你可以把程式碼拿過去打成jar檔案

package com.example;

import java.io.BufferedReader;
import
java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class UnusedResourcesClean { static String Type_drawable = "drawable"; static String Type_layout = "layout"; static String Type_string = "string"; static String Type_color = "color"; static String Type_dimen = "dimen"; static String Type_menu = "menu"; static String Type_anim = "anim"; private static boolean cleanDrawable = true; private static boolean cleanLayout = true; private static boolean cleanAnim = true; private static boolean cleanMenu = true; private static boolean cleanColor = true; private static boolean cleanString = true; private static boolean cleanDimen = true; /** * project res dir */ static String sourceProjectResDir = "/Users/alvin/Documents/workproject/PerformanceOptimizationCase/reduceSizeCase/src/main/res"; /** * lint check result file path */ static String lintResultFilePath = "unusedResourcesCleanTool/unusedLint"; /** * key: resourceType * value: resource file,or resource key name */ static HashMap<String, Set<String>> resMap = new HashMap<>(); public static void main(String[] args) { try { resMap = initUnUsedMap(lintResultFilePath); if (resMap == null || resMap.isEmpty()) { return; } printUnUsedMap(); clean(sourceProjectResDir,resMap); } catch (Exception e) { e.printStackTrace(); } } /** * split The resource 'R.drawable.btn_bg_top_normal' appears to be unused * lint result --> map * * @param resultFilePath */ private static HashMap<String, Set<String>> initUnUsedMap(String resultFilePath) throws Exception { HashMap<String, Set<String>> resMap = new HashMap<>(); try { BufferedReader bf = new BufferedReader(new FileReader(resultFilePath)); String line = ""; while ((line = bf.readLine()) != null) { // if (line.contains("appears to be unused")) { String resKey = line.split("'")[1];//R.drawable.btn_bg_top_normal System.out.println(resKey); String resType = resKey.split("\\.")[1]; String resName = resKey.split("\\.")[2]; System.out.println(resType + " , " + resName); if (resMap.get(resType) == null) { resMap.put(resType, new HashSet<String>()); } Set<String> ids = resMap.get(resType); ids.add(resName); } } } catch (Exception e) { e.printStackTrace(); } return resMap; } private static void printUnUsedMap() { for (Map.Entry<String, Set<String>> entry : resMap.entrySet()) { System.out.println("------------------" + entry.getKey() + "------------------"); for (String s : entry.getValue()) { System.out.println(s); } System.out.println(" "); } } /** * * @param sourceProjectResDir 專案資源目錄 * @param resMap 根據Type分類的unusedResource * 例如 key: string value: {rateuslib_cancel,unnecessary_cancel} */ private static void clean(String sourceProjectResDir,HashMap<String, Set<String>> resMap) { Map<String, Boolean> deletionFileMap = new HashMap<>(); deletionFileMap.put(Type_drawable, cleanDrawable); deletionFileMap.put(Type_layout, cleanLayout); deletionFileMap.put(Type_anim, cleanAnim); deletionFileMap.put(Type_menu, cleanMenu); deletionFileMap.put(Type_color, cleanColor); Map<String, Boolean> deletionLineMap = new HashMap<>(); deletionLineMap.put(Type_string, cleanString); deletionLineMap.put(Type_dimen, cleanDimen); deletionLineMap.put(Type_color, cleanColor); Map<String, String> lineKeyMap = new HashMap<>(); lineKeyMap.put(Type_string, "<string name"); lineKeyMap.put(Type_dimen, "<dimen name="); lineKeyMap.put(Type_color, "<color name="); File dir = new File(sourceProjectResDir); for (File file : dir.listFiles()) { if (!file.isDirectory()) { continue; } if (file.getName().contains("value")) { for (File file1 : file.listFiles()) { for (Map.Entry<String, Boolean> entry : deletionLineMap.entrySet()) { String type = entry.getKey(); boolean clean = entry.getValue(); if (file1.getName().contains(type) && clean) { resetXml(file1, resMap.get(type), lineKeyMap.get(type)); } } } } else { for (Map.Entry<String, Boolean> entry : deletionFileMap.entrySet()) { if (file.getName().contains(entry.getKey()) && entry.getValue()) { Set<String> resSet = resMap.get(entry.getKey()); deleteResFile(resSet, file); } } } } } /** * 從指定資源目錄中,刪除指定型別的檔案 * * @param resourceFiles * @param resDir drawable/ or layout/ ... */ private static void deleteResFile(Set<String> resourceFiles, File resDir) { try { for (File file1 : resDir.listFiles()) { if (resourceFiles.contains(file1.getName().split("\\.")[0])) { file1.delete(); } } } catch (Exception e) { e.printStackTrace(); } } /** * @param sourceFile 需要刪除行的檔案 * @param unusedSourceKeys 需要刪除的行的關鍵詞集合 * @param xmlTypeKey "<string name" or "<dimen name=" or "<color name=" */ public static void resetXml(File sourceFile, Set<String> unusedSourceKeys, String xmlTypeKey) { BufferedWriter bw = null; try { if (unusedSourceKeys==null&&unusedSourceKeys.isEmpty()){ return; } String outFilename = sourceFile.getAbsolutePath() + "_temp"; File outFile = new File(outFilename); BufferedReader bf = new BufferedReader(new FileReader(sourceFile)); bw = new BufferedWriter(new FileWriter(outFilename)); String line = "";// <string name="app_name">App Backup &amp; Restore</string> while ((line = bf.readLine()) != null) { if (line.contains(xmlTypeKey) && unusedSourceKeys.contains(line.split("\"")[1])) { continue; } bw.write(line); bw.newLine(); } sourceFile.delete(); outFile.renameTo(new File(sourceFile.getAbsolutePath())); } catch (Exception e) { } finally { if (bw != null) { try { bw.flush(); bw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }