Android apk 分析工具 Analyze APK Compare APK
阿新 • • 發佈:2019-01-22
apk檔案比對
新版本釋出時,需要check下新版本Apk包大小,以及具體哪些檔案導致Apk變大,從而針對性的進行優化。Android studio 有工具Analyze APK 做了類似的事情,但是無法進行持續整合,本文參照Alalyze APK 的功能,分析APK各個檔案大小,並給出對應的結果報告
使用方法
java -jar apk.jar App-1.0.apk App-2.0.apk changes
會輸出diffReport.html
diffReport.html 結果
原始碼
Main.java
import java.io.*;
import java.text .NumberFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.pegdown.PegDownProcessor;
public class Main {
static final String NA = "N/A";
static String StyleCSS ="";
static final NumberFormat format = NumberFormat.getInstance();
static final Comparator comparator = new Comparator<DiffItem>() {
@Override
public int compare(DiffItem o1, DiffItem o2) {
return (int) (Math.abs (o2.diffSize) - Math.abs(o1.diffSize));
}
};
public static void main(String[] args) {
if (args.length != 3) {
System.out.println("Usage: ApkCompare oldApk newApk outputFilename");
System.out.println("Example: ApkCompare App-1.0.apk App-2.0.apk changes");
System.out.println("This would output a diff file named changes.md at current directory");
return;
}
Map<String, Long> oldFilesInfo = getFilesInfo(args[0]);
Map<String, Long> newFilesInfo = getFilesInfo(args[1]);
List<DiffItem> outputDiffList = new ArrayList<DiffItem>();
Set<Map.Entry<String, Long>> oldEntries = oldFilesInfo.entrySet();
for (Map.Entry<String, Long> oldEntry : oldEntries) {
DiffItem diffItem = new DiffItem();
String keyOldFilename = oldEntry.getKey();
diffItem.oldFilename = keyOldFilename;
Long oldFileSize = oldEntry.getValue();
Long newFileSize = newFilesInfo.get(keyOldFilename);
if (newFileSize == null) { //新APK中刪除了的檔案
diffItem.newFilename = NA;
diffItem.diffSize = -oldFileSize;
} else {
diffItem.newFilename = diffItem.oldFilename;
diffItem.diffSize = newFileSize - oldFileSize;
}
newFilesInfo.remove(keyOldFilename);
if (diffItem.diffSize != 0L) { //僅統計檔案大小有改變的情況
outputDiffList.add(diffItem);
}
}
//新版本中新增的檔案
Set<Map.Entry<String, Long>> newEntries = newFilesInfo.entrySet();
for (Map.Entry<String, Long> newEntry : newEntries) {
DiffItem diffItem = new DiffItem();
diffItem.oldFilename = NA;
diffItem.newFilename = newEntry.getKey();
diffItem.diffSize = newEntry.getValue();
outputDiffList.add(diffItem);
}
outputMarkdown(args, outputDiffList);
System.out.println("APK compare done!");
System.out.println("output file: " + new File(args[2]).getAbsolutePath() + ".md");
try {
get(new File(args[2]).getAbsolutePath() + ".md");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String readFile(String filepath)throws IOException{
FileReader reader = new FileReader(filepath);//定義一個fileReader物件,用來初始化BufferedReader
BufferedReader bReader = new BufferedReader(reader);//new一個BufferedReader物件,將檔案內容讀取到快取
StringBuilder sb = new StringBuilder();//定義一個字串快取,將字串存放快取中
String s = "";
while ((s =bReader.readLine()) != null) {//逐行讀取檔案內容,不讀取換行符和末尾的空格
sb.append(s + "\n");//將讀取的字串新增換行符後累加存放在快取中
}
bReader.close();
return sb.toString();
}
public static String readJarFile(String filepath)throws IOException{
InputStream inputStream = Main.class.getResourceAsStream(filepath);
BufferedReader bReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String s = "";
while ((s =bReader.readLine()) != null) {
sb.append(s + "\n");
}
bReader.close();
return sb.toString();
}
public static void get(String filepath) throws IOException{
String html = null;
html =readFile(filepath);
PegDownProcessor pdp = new PegDownProcessor(Integer.MAX_VALUE);
html = pdp.markdownToHtml(html);
StyleCSS = readJarFile("style.css");
html=StyleCSS+html;
FileWriter writer;
try {
writer = new FileWriter("diffReport.html");
writer.write(html);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void outputMarkdown(String[] filenames, List<DiffItem> outputDiffList) {
try {
List<DiffItem> increased = new ArrayList<DiffItem>();
List<DiffItem> decreased = new ArrayList<DiffItem>();
List<DiffItem> added = new ArrayList<DiffItem>();
List<DiffItem> removed = new ArrayList<DiffItem>();
int increasedCnt = 0;
int decreasedCnt = 0;
int addedCnt = 0;
int removedCnt = 0;
//檔案數量
int increasedFileCnt = 0;
int decreasedFileCnt = 0;
int addedFileCnt = 0;
int removedFileCnt = 0;
for (DiffItem diffItem : outputDiffList) {
if (NA.equals(diffItem.oldFilename)) { //新版本完全新增檔案
added.add(diffItem);
addedCnt += diffItem.diffSize;
addedFileCnt++;
} else if (NA.equals(diffItem.newFilename)) { //新版本刪除了的檔案
removed.add(diffItem);
removedCnt += diffItem.diffSize;
removedFileCnt++;
} else if (diffItem.diffSize > 0) { //同一檔案有差異,新版本中size增加了
increased.add(diffItem);
increasedCnt += diffItem.diffSize;
increasedFileCnt++;
} else {//同一檔案有差異,新版本中size減小了
decreased.add(diffItem);
decreasedCnt += diffItem.diffSize;
decreasedFileCnt++;
}
}
BufferedWriter writer = new BufferedWriter(new FileWriter(filenames[2] + ".md"));
File oldApkFile = new File(filenames[0]);
// writer.write("### "+oldApkFile.getName());
// writer.write(" VS ");
File newApkFile = new File(filenames[1]);
// writer.write(newApkFile.getName());
// writer.write("\n\n");
writer.write("## 包大小 \n\n");
writer.write("| 線上版本 | 新版本 | Diff |\n");
writer.write("| --------- | --------- | ---------: |\n");
writer.append("| ").append(long2float(oldApkFile.length())+"Mb ").
append("| ").append(long2float(newApkFile.length())+"Mb ").
append("| ").append(long2float(newApkFile.length() - oldApkFile.length())+"Mb ")
.append("| \n");
writer.write("## 包大小詳細檔案比對 \n\n");
writer.write("| 檔案 | 數量 | Diff (byte) |\n");
writer.write("| --------- | --------- | ---------: |\n");
writer.append("| 新版本中增加的檔案 | ").append(format.format(addedFileCnt)+" | ").append(format.format(addedCnt)).append(" | \n");
writer.append("| 新版本中大小增加的檔案 | ").append(format.format(increasedFileCnt)+" | ").append(format.format(increasedCnt)).append(" | \n");
writer.append("| 新版本中大小減小的檔案 | ").append(format.format(decreasedFileCnt)+" | ").append(format.format(decreasedCnt)).append(" | \n");
writer.append("| 新版本中被刪除的檔案 | ").append(format.format(removedFileCnt)+" | ").append(format.format(removedCnt)).append(" | \n");
writer.append("| 總計 | ").append(format.format(addedFileCnt+increasedFileCnt+decreasedFileCnt+removedFileCnt)+" | ").append(format.format(addedCnt + increasedCnt + decreasedCnt + removedCnt)).append(" | \n\n");
outputMarkdownAddedList(writer, added);
outputMarkdownIncreasedList(writer, increased);
outputMarkdownDecreasedList(writer, decreased);
outputMarkdownRemovedList(writer, removed);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void outputMarkdownAddedList(Writer writer, List<DiffItem> outputDiffList) {
if (outputDiffList.size() > 0) {
try {
Collections.sort(outputDiffList, comparator);
writer.append("## ").append("新版本中增加的檔案");
writer.append("<a name=\"added\"></a>\n");
writer.append("| File Name | Size (byte)|\n");
writer.append("| --------- | ---------: |\n");
for (DiffItem diffItem : outputDiffList) {
writer.append("| ").append(diffItem.newFilename).append(" | ")
.append(format.format(diffItem.diffSize))
.append(" |\n");
writer.flush();
}
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void outputMarkdownIncreasedList(Writer writer, List<DiffItem> outputDiffList) {
if (outputDiffList.size() > 0) {
try {
Collections.sort(outputDiffList, comparator);
writer.append("## ").append("新版本中大小增加的檔案");
writer.append("<a name=\"increased\"></a>\n");
writer.append("| File Name | Increased Size (byte)|\n");
writer.append("| --------- | ---------: |\n");
for (DiffItem diffItem : outputDiffList) {
writer.append("| ").append(diffItem.newFilename).append(" | ")
.append(format.format(diffItem.diffSize))
.append(" |\n");
writer.flush();
}
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void outputMarkdownDecreasedList(Writer writer, List<DiffItem> outputDiffList) {
if (outputDiffList.size() > 0) {
try {
Collections.sort(outputDiffList, comparator);
writer.append("## ").append("新版本中大小減小的檔案");
writer.append("<a name=\"decreased\"></a>\n");
writer.append("| File Name | Decreased Size (byte)|\n");
writer.append("| --------- | ---------: |\n");
for (DiffItem diffItem : outputDiffList) {
writer.append("| ").append(diffItem.newFilename).append(" | ")
.append(format.format(diffItem.diffSize))
.append(" |\n");
writer.flush();
}
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void outputMarkdownRemovedList(Writer writer, List<DiffItem> outputDiffList) {
if (outputDiffList.size() > 0) {
try {
Collections.sort(outputDiffList, comparator);
writer.append("## ").append("新版本中被刪除的檔案");
writer.append("<a name=\"removed\"></a>\n");
writer.append("| File Name | Decreased Size (byte)|\n");
writer.append("| --------- | ---------: |\n");
for (DiffItem diffItem : outputDiffList) {
writer.append("| ").append(diffItem.oldFilename).append(" | ")
.append(format.format(diffItem.diffSize))
.append(" |\n");
writer.flush();
}
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Map<String, Long> getFilesInfo(String apkFilePath) {
Map<String, Long> map = new LinkedHashMap<String, Long>();
try {
ZipFile apkFile = new ZipFile(apkFilePath);
Enumeration<? extends ZipEntry> entries = apkFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String filename = entry.getName();
long size = entry.getSize();
map.put(filename, size);
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
private static class DiffItem {
String oldFilename;
String newFilename;
Long diffSize;
}
public static float long2float(long l){
return (float)(Math.round(Float.valueOf(l)/(1024*1024)*100))/100;
}
}
css檔案
<html lang= "zh-CN">
<head>
<style type="text/css">
body {
font: normal 11px auto "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
color: #4f6b72;
background: #E6EAE9;
}
a {
color: #c75f3e;
}
table {
width: 700px;
padding: 0;
margin: 0;
border: 0;
margin: 0;
border-collapse: collapse;
border-spacing: 0;
}
th {
font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
color: #4f6b72;
border-right: 1px solid #C1DAD7;
border-bottom: 1px solid #C1DAD7;
border-top: 1px solid #C1DAD7;
letter-spacing: 2px;
text-transform: uppercase;
text-align: left;
padding: 6px 6px 6px 12px;
background: #CAE8EA no-repeat;
}
td {
border-right: 1px solid #C1DAD7;
border-bottom: 1px solid #C1DAD7;
background: #fff;
font-size: 11px;
padding: 6px 6px 6px 12px;
color: #4f6b72;
}
/*---------for IE 5.x bug*/
html>body td {
font-size: 11px;
}
body,
td,
th {
font-family: 宋體, Arial;
font-size: 12px;
}
</style>
</head>
pom檔案
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.test</groupId>
<artifactId>apk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>apk</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.pegdown</groupId>
<artifactId>pegdown</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.commonjava.googlecode.markdown4j</groupId>
<artifactId>markdown4j</artifactId>
<version>2.2-cj-1.0</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
將其導成jar包,既可以在持續整合中使用