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程式碼實現地址:
處理流程大致就是檔案操作:
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 & 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();
}
}
}
}
}