Skip to content

Commit

Permalink
Merge pull request #33 from victorrattis/32-remove-apktoolkit
Browse files Browse the repository at this point in the history
Remove apktool dependency
  • Loading branch information
alexzaitsev authored Jan 10, 2019
2 parents 13d67f4 + acfca21 commit 2a2ef99
Show file tree
Hide file tree
Showing 17 changed files with 356 additions and 124 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ META-INF
build
output
test.*
apk-dependency-graph-scripts-*.zip
apk-dependency-graph-scripts-*.zip

# lib/ directory where the dependencies are downloaded by Ivy Apache.
lib
37 changes: 5 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![version](https://img.shields.io/badge/version-0.1.4-brightgreen.svg)](https://github.com/alexzaitsev/apk-dependency-graph/releases/tag/0.1.4) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-apk--dependency--graph-blue.svg?style=flat)](http://android-arsenal.com/details/1/4411) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Android%20dependency%20visualizer&url=https://github.com/alexzaitsev/apk-dependency-graph&hashtags=android,dependency,coupling,graph,visualize,apktool,developer)
[![version](https://img.shields.io/badge/version-0.1.4-brightgreen.svg)](https://github.com/alexzaitsev/apk-dependency-graph/releases/tag/0.1.4) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-apk--dependency--graph-blue.svg?style=flat)](http://android-arsenal.com/details/1/4411) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Android%20dependency%20visualizer&url=https://github.com/alexzaitsev/apk-dependency-graph&hashtags=android,dependency,coupling,graph,visualize,baksmali,developer)


Android dependency visualizer. It's a tool that helps to visualize current state of your project. It's really easy to see how tight your classes are coupled.
Expand All @@ -10,7 +10,7 @@ Class coupling is one of the significant code metrics which shows how easy is to
## Project structure

This project consists of the several parts:
* lib (apktool)
* lib (baksmali)
* src, build (apk-dependency-graph)
* gui (d3)
* run scripts
Expand All @@ -25,7 +25,7 @@ From terminal just move to the parent folder of the project and run `ant` comman

## Run

You need at least **Java 7** to run `apktool` and `apk-dependency-graph` `jar` files.
You need at least **Java 7** to run `apk-dependency-graph` `jar` file.

## Usage

Expand All @@ -52,33 +52,11 @@ or
```
Wait until the command finishes:
```
I: Using Apktool 2.3.0 on app-release.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: C:\Users\username\AppData\Local\apktool\framework\1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
Baksmaling classes.dex...
Success! Now open index.html in your browser.
```
It will decompile your apk and create `apk-file-name` folder in the same folder where the script is. After this it will analyze the smali code and generate `gui/analyzed.js` file which contains all dependencies.
**Now open `gui/index.html` in your browser and enjoy!**
### Long way
If you don't want to use `run` scripts you can do all the stuff from the command line by yourself.
Firsly, decompile your apk with the `apktool` jar:
```
java -jar apktool_2.3.0.jar d path-to-apk.apk -o path-to-folder-with-decompiled-files -f
```
After this run `apk-dependency-graph`:
```
java -jar build/jar/apk-dependency-graph.jar -i path-to-folder-with-decompiled-files -o analyzed.js -f com.example.test -d true
```
I suggest to use your package name as a filter. If you don't want to filter just pass `nofilter`.
**Now open `gui/index.html` in your browser and enjoy!**

## Examples

Expand All @@ -98,11 +76,6 @@ Watch [demo video](https://www.youtube.com/watch?v=rw501tvT4ko).

Share your awesome architecture using `#apkdependencyvizualizer` hashtag!

## Troubleshooting

Aware! **This tool cannot analyze apks generated with enabled instant run feature**. This is a limitation of `apktool` running under the hood.
If you have troubles look through [troubleshooting](https://github.com/alexzaitsev/apk-dependency-graph/wiki/Troubleshooting) wiki page or create an issue in this repository.

---

## Credits
Expand All @@ -125,4 +98,4 @@ Yes, we really need you man! We always have something to do and have special lab
If you're looking for an Android Studio plugin that allows to display graph of dependency injections - please check out [this repository](https://github.com/kaygisiz/Dependency-Injection-Graph). It's based on current project and available in [Jetbrains repository](https://plugins.jetbrains.com/plugin/10107-dependency-injection-graph).

## Share a link to our repository
[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Android%20dependency%20visualizer&url=https://github.com/alexzaitsev/apk-dependency-graph&hashtags=android,dependency,coupling,graph,visualize,apktool,developer)
[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Android%20dependency%20visualizer&url=https://github.com/alexzaitsev/apk-dependency-graph&hashtags=android,dependency,coupling,graph,visualize,baksmali,developer)
52 changes: 49 additions & 3 deletions build.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
<project name="apk-dependency-graph" basedir="." default="main">
<project xmlns:ivy="antlib:org.apache.ivy.ant"
name="apk-dependency-graph" basedir="." default="main">

<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="jar.dir" value="${build.dir}/jar"/>
<property name="main-class" value="code.Main"/>
<property name="lib.dir" value="lib" />

<target name="clean">
<delete dir="${build.dir}"/>
</target>

<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}"/>
<javac
srcdir="${src.dir}"
destdir="${classes.dir}"
classpathref="external-libraries-classpath"
includeantruntime="false" />
</target>

<target name="jar" depends="compile">
Expand All @@ -21,6 +27,11 @@
<manifest>
<attribute name="Main-Class" value="${main-class}"/>
</manifest>
<zipgroupfileset dir="${basedir}/">
<include name="${lib.dir}/**/*.jar" />
<exclude name="${lib.dir}/**/*sources.jar"/>
<exclude name="${lib.dir}/**/*javadoc.jar"/>
</zipgroupfileset>
</jar>
</target>

Expand All @@ -30,6 +41,41 @@

<target name="clean-build" depends="clean,jar"/>

<target name="main" depends="clean,run"/>
<target name="main" depends="clean,resolve,run"/>

<!-- target: resolve -->
<property name="ivy.install.version" value="2.2.0" />
<condition property="ivy.home" value="${env.IVY_HOME}">
<isset property="env.IVY_HOME" />
</condition>
<property name="ivy.home" value="${user.home}/.ant" />
<property name="ivy.jar.dir" value="${ivy.home}/libs" />
<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />

<target name="download-ivy">
<mkdir dir="${ivy.jar.dir}"/>
<get src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
dest="${ivy.jar.file}" usetimestamp="true"/>
</target>

<target name="init-ivy" depends="download-ivy">
<path id="ivy.lib.path">
<fileset dir="${ivy.jar.dir}" includes="*.jar"/>
</path>
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
</target>

<target name="resolve" description="--> retreive dependencies with ivy" depends="init-ivy">
<ivy:retrieve/>
</target>

<!-- path: external libraries classpath, we don't need sources and javadoc -->
<path id="external-libraries-classpath">
<fileset dir="${basedir}/">
<include name="${lib.dir}/*.jar" />
<exclude name="${lib.dir}/*sources.jar"/>
<exclude name="${lib.dir}/*javadoc.jar"/>
</fileset>
</path>
</project>
7 changes: 7 additions & 0 deletions ivy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<ivy-module version="2.0">
<info organisation="org.apache" module="apk-dependency-graph"/>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.smali/baksmali -->
<dependency org="org.smali" name="baksmali" rev="2.2.5"/>
</dependencies>
</ivy-module>
Binary file removed lib/apktool_2.3.4.jar
Binary file not shown.
3 changes: 1 addition & 2 deletions run.bat
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,4 @@ For %%A in ("%filename%") do (
Set outPath=%~dp0\output\%Name:~0,-4%
Set jsonPath=%~dp0\gui\analyzed.js

java -jar %~dp0\lib\apktool_2.3.4.jar d %1 -o %outPath% -f
java -jar %~dp0\build\jar\apk-dependency-graph.jar -i %outPath% -o %jsonPath% -f %2 -d %3
java -jar %~dp0\build\jar\apk-dependency-graph.jar -i %outPath% -o %jsonPath% -f %2 -d %3 -a %1
7 changes: 3 additions & 4 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ xpref=${xbase%.*}

dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
outPath=${dir}"/output/"${xpref}
jsonPath=${dir}"/gui/analyzed.js"

eval "java -jar ${dir}'/lib/apktool_2.3.4.jar' d ${fileName} -o ${outPath} -f"
eval "java -jar ${dir}'/build/jar/apk-dependency-graph.jar' -i ${outPath} -o ${jsonPath} -f $2 -d $3"
jsonPath=${dir}"/gui/analyzed.js"

eval "java -jar ${dir}'/build/jar/apk-dependency-graph.jar' -i ${outPath} -o ${jsonPath} -f $2 -d $3 -a $1"
2 changes: 2 additions & 0 deletions src/code/CodeUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package code;

import code.util.StringUtils;

public class CodeUtils {

public static boolean isClassR(String className) {
Expand Down
42 changes: 26 additions & 16 deletions src/code/Main.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package code;


import java.io.File;

import code.decode.ApkSmaliDecoderController;
import code.io.ArgumentReader;
import code.io.Arguments;
import code.io.Writer;
import code.util.FileUtils;

import java.io.File;

public class Main {

public static void main(String[] args) {
Arguments arguments = new ArgumentReader(args).read();
if (arguments == null) {
return;
}

File resultFile = new File(arguments.getResultPath());
SmaliAnalyzer analyzer = new SmaliAnalyzer(arguments);
if (analyzer.run()) {
new Writer(resultFile).write(analyzer.getDependencies());
System.out.println("Success! Now open index.html in your browser.");
}
}
public static void main(String[] args) {
Arguments arguments = new ArgumentReader(args).read();
if (arguments == null) {
return;
}

// Delete the output directory for a better decoding result.
if (FileUtils.deleteDir(arguments.getProjectPath())) {
System.out.println("The output directory was deleted!");
}

// Decode the APK file for smali code in the output directory.
ApkSmaliDecoderController.decode(
arguments.getApkFilePath(), arguments.getProjectPath());

File resultFile = new File(arguments.getResultPath());
SmaliAnalyzer analyzer = new SmaliAnalyzer(arguments);
if (analyzer.run()) {
new Writer(resultFile).write(analyzer.getDependencies());
System.out.println("Success! Now open index.html in your browser.");
}
}
}
13 changes: 0 additions & 13 deletions src/code/StringUtils.java

This file was deleted.

118 changes: 118 additions & 0 deletions src/code/decode/ApkSmaliDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package code.decode;

import code.util.ZipFileUtils;

import org.jf.baksmali.Baksmali;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;

import java.io.File;
import java.io.IOException;
import java.lang.Math;
import java.util.List;

public final class ApkSmaliDecoder {
private static final int MAXIMUM_NUMBER_OF_PROCESSORS = 6;

private static final String DEX_FILE_EXTENSION = ".dex";

private static final String WARNING_DISASSEMBLING_ODEX_FILE =
"Warning: You are disassembling an odex file without deodexing it.";
private static final String WARNING_FILE_IS_NOT_FOUND =
"Apk file is not found!";

private final String apkFilePath;
private final String outDirPath;
private final int apiVersion;

ApkSmaliDecoder(String apkFilePath, String outDirPath, int api) {
this.apkFilePath = apkFilePath;
this.outDirPath = outDirPath;
this.apiVersion = api;
}

void decode() throws IOException {
File apkFile = new File(this.apkFilePath);
if (!apkFile.exists()) {
throw new IOException(WARNING_FILE_IS_NOT_FOUND);
}
File outDir = new File(this.outDirPath);

// Read all dex files in the APK file and so decode each one.
for (String dexFileName : getDexFiles(this.apkFilePath)) {
decodeDexFile(apkFile, dexFileName, this.apiVersion, outDir);
}
}

private void decodeDexFile(
File apkFile, String dexFileName, int apiVersion, File outDir)
throws IOException {
try {
log("Baksmaling " + dexFileName + "...");
DexBackedDexFile dexFile =
loadDexFile(apkFile, dexFileName, apiVersion);

Baksmali.disassembleDexFile(
dexFile,
outDir,
getNumberOfAvailableProcessors(),
getSmaliOptions(dexFile));
} catch (Exception ex) {
throw new IOException(ex);
}
}

private int getNumberOfAvailableProcessors() {
int jobs = Runtime.getRuntime().availableProcessors();
return Math.min(jobs, MAXIMUM_NUMBER_OF_PROCESSORS);
}

private BaksmaliOptions getSmaliOptions(final DexBackedDexFile dexFile) {
final BaksmaliOptions options = new BaksmaliOptions();

options.deodex = false;
options.implicitReferences = false;
options.parameterRegisters = true;
options.localsDirective = true;
options.sequentialLabels = true;
options.debugInfo = false;
options.codeOffsets = false;
options.accessorComments = false;
options.registerInfo = 0;

if (dexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)dexFile).getOdexVersion());
} else {
options.inlineResolver = null;
}

return options;
}

private DexBackedDexFile loadDexFile(
File apkFile, String dexFilePath, int apiVersion)
throws IOException {
DexBackedDexFile dexFile = DexFileFactory.loadDexEntry(
apkFile, dexFilePath, true, Opcodes.forApi(apiVersion));

if (dexFile == null || dexFile.isOdexFile()) {
throw new IOException(WARNING_DISASSEMBLING_ODEX_FILE);
}

return dexFile;
}

private List<String> getDexFiles(String apkFilePath) throws IOException {
return ZipFileUtils.filterByExtension(apkFilePath, DEX_FILE_EXTENSION);
}

private void log(final String text) {
System.out.println(text);
}
}
Loading

0 comments on commit 2a2ef99

Please sign in to comment.