1. 程式人生 > >Android apk 分析工具 Analyze APK Compare APK

Android apk 分析工具 Analyze APK Compare APK

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包,既可以在持續整合中使用