diff --git a/DuplicateBitmapApp/.gitignore b/DuplicateBitmapApp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/DuplicateBitmapApp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/DuplicateBitmapApp/build.gradle b/DuplicateBitmapApp/build.gradle
new file mode 100644
index 0000000..58c2290
--- /dev/null
+++ b/DuplicateBitmapApp/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+
+
+
+ defaultConfig {
+ applicationId "com.hprof.bitmap.duplicatebitmapapp"
+ minSdkVersion 24
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/DuplicateBitmapApp/proguard-rules.pro b/DuplicateBitmapApp/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/DuplicateBitmapApp/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/DuplicateBitmapApp/src/androidTest/java/com/hprof/bitmap/duplicatebitmapapp/ExampleInstrumentedTest.java b/DuplicateBitmapApp/src/androidTest/java/com/hprof/bitmap/duplicatebitmapapp/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..524a18a
--- /dev/null
+++ b/DuplicateBitmapApp/src/androidTest/java/com/hprof/bitmap/duplicatebitmapapp/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.hprof.bitmap.duplicatebitmapapp;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.hprof.bitmap.duplicatebitmapapp", appContext.getPackageName());
+ }
+}
diff --git a/DuplicateBitmapApp/src/main/AndroidManifest.xml b/DuplicateBitmapApp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ec68167
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DuplicateBitmapApp/src/main/java/com/hprof/bitmap/duplicatebitmapapp/MainActivity.java b/DuplicateBitmapApp/src/main/java/com/hprof/bitmap/duplicatebitmapapp/MainActivity.java
new file mode 100644
index 0000000..3024a31
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/java/com/hprof/bitmap/duplicatebitmapapp/MainActivity.java
@@ -0,0 +1,18 @@
+package com.hprof.bitmap.duplicatebitmapapp;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+
+ Bitmap mBitmap;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
+ }
+}
diff --git a/DuplicateBitmapApp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/DuplicateBitmapApp/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..485bbd3
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DuplicateBitmapApp/src/main/res/drawable-xxhdpi/img.png b/DuplicateBitmapApp/src/main/res/drawable-xxhdpi/img.png
new file mode 100644
index 0000000..c6d00dc
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/drawable-xxhdpi/img.png differ
diff --git a/DuplicateBitmapApp/src/main/res/drawable/ic_launcher_background.xml b/DuplicateBitmapApp/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..7ca0909
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DuplicateBitmapApp/src/main/res/layout/activity_main.xml b/DuplicateBitmapApp/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..a5e3d62
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher.png b/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher.png b/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher.png b/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/DuplicateBitmapApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/DuplicateBitmapApp/src/main/res/values/colors.xml b/DuplicateBitmapApp/src/main/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/DuplicateBitmapApp/src/main/res/values/strings.xml b/DuplicateBitmapApp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..14d4c0f
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ DuplicateBitmapApp
+
diff --git a/DuplicateBitmapApp/src/main/res/values/styles.xml b/DuplicateBitmapApp/src/main/res/values/styles.xml
new file mode 100644
index 0000000..9785e0c
--- /dev/null
+++ b/DuplicateBitmapApp/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/DuplicateBitmapApp/src/test/java/com/hprof/bitmap/duplicatebitmapapp/ExampleUnitTest.java b/DuplicateBitmapApp/src/test/java/com/hprof/bitmap/duplicatebitmapapp/ExampleUnitTest.java
new file mode 100644
index 0000000..3acbf58
--- /dev/null
+++ b/DuplicateBitmapApp/src/test/java/com/hprof/bitmap/duplicatebitmapapp/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.hprof.bitmap.duplicatebitmapapp;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/DuplicatedBitmapAnalyzer/build.gradle b/DuplicatedBitmapAnalyzer/build.gradle
index 912e192..f7bfaf0 100644
--- a/DuplicatedBitmapAnalyzer/build.gradle
+++ b/DuplicatedBitmapAnalyzer/build.gradle
@@ -6,6 +6,8 @@ version 1.0
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
+ compile 'com.squareup.haha:haha:2.0.4'
+ compile 'com.google.code.gson:gson:2.8.5'
}
diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java
deleted file mode 100644
index 202764e..0000000
--- a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.hprof.bitmap;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-
-public class Main {
-
- public static void main(String[] args) throws IOException {
- }
-}
diff --git a/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/HahaHelper.java b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/HahaHelper.java
new file mode 100644
index 0000000..8ec8f7d
--- /dev/null
+++ b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/HahaHelper.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.hprof.bitmap;
+
+import com.squareup.haha.perflib.ArrayInstance;
+import com.squareup.haha.perflib.ClassInstance;
+import com.squareup.haha.perflib.ClassObj;
+import com.squareup.haha.perflib.Instance;
+import com.squareup.haha.perflib.Type;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Arrays.asList;
+
+public final class HahaHelper {
+
+ private static final Set WRAPPER_TYPES = new HashSet<>(
+ asList(Boolean.class.getName(), Character.class.getName(), Float.class.getName(),
+ Double.class.getName(), Byte.class.getName(), Short.class.getName(),
+ Integer.class.getName(), Long.class.getName()));
+
+ static String threadName(Instance holder) {
+ List values = classInstanceValues(holder);
+ Object nameField = fieldValue(values, "name");
+ if (nameField == null) {
+ // Sometimes we can't find the String at the expected memory address in the heap dump.
+ // See https://github.com/square/leakcanary/issues/417 .
+ return "Thread name not available";
+ }
+ return asString(nameField);
+ }
+
+ static boolean extendsThread(ClassObj clazz) {
+ boolean extendsThread = false;
+ ClassObj parentClass = clazz;
+ while (parentClass.getSuperClassObj() != null) {
+ if (parentClass.getClassName().equals(Thread.class.getName())) {
+ extendsThread = true;
+ break;
+ }
+ parentClass = parentClass.getSuperClassObj();
+ }
+ return extendsThread;
+ }
+
+ /**
+ * This returns a string representation of any object or value passed in.
+ */
+ static String valueAsString(Object value) {
+ String stringValue;
+ if (value == null) {
+ stringValue = "null";
+ } else if (value instanceof ClassInstance) {
+ String valueClassName = ((ClassInstance) value).getClassObj().getClassName();
+ if (valueClassName.equals(String.class.getName())) {
+ stringValue = '"' + asString(value) + '"';
+ } else {
+ stringValue = value.toString();
+ }
+ } else {
+ stringValue = value.toString();
+ }
+ return stringValue;
+ }
+
+ /** Given a string instance from the heap dump, this returns its actual string value. */
+ static String asString(Object stringObject) {
+ Preconditions.checkNotNull(stringObject, "stringObject");
+ Instance instance = (Instance) stringObject;
+ List values = classInstanceValues(instance);
+
+ Integer count = fieldValue(values, "count");
+ Preconditions.checkNotNull(count, "count");
+ if (count == 0) {
+ return "";
+ }
+
+ Object value = fieldValue(values, "value");
+ Preconditions.checkNotNull(value, "value");
+
+ Integer offset;
+ ArrayInstance array;
+ if (isCharArray(value)) {
+ array = (ArrayInstance) value;
+
+ offset = 0;
+ // < API 23
+ // As of Marshmallow, substrings no longer share their parent strings' char arrays
+ // eliminating the need for String.offset
+ // https://android-review.googlesource.com/#/c/83611/
+ if (hasField(values, "offset")) {
+ offset = fieldValue(values, "offset");
+ Preconditions.checkNotNull(offset, "offset");
+ }
+
+ char[] chars = array.asCharArray(offset, count);
+ return new String(chars);
+ } else if (isByteArray(value)) {
+ // In API 26, Strings are now internally represented as byte arrays.
+ array = (ArrayInstance) value;
+
+ // HACK - remove when HAHA's perflib is updated to https://goo.gl/Oe7ZwO.
+ try {
+ Method asRawByteArray =
+ ArrayInstance.class.getDeclaredMethod("asRawByteArray", int.class, int.class);
+ asRawByteArray.setAccessible(true);
+ byte[] rawByteArray = (byte[]) asRawByteArray.invoke(array, 0, count);
+ return new String(rawByteArray, Charset.forName("UTF-8"));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new UnsupportedOperationException("Could not find char array in " + instance);
+ }
+ }
+
+ public static boolean isPrimitiveWrapper(Object value) {
+ if (!(value instanceof ClassInstance)) {
+ return false;
+ }
+ return WRAPPER_TYPES.contains(((ClassInstance) value).getClassObj().getClassName());
+ }
+
+ public static boolean isPrimitiveOrWrapperArray(Object value) {
+ if (!(value instanceof ArrayInstance)) {
+ return false;
+ }
+ ArrayInstance arrayInstance = (ArrayInstance) value;
+ if (arrayInstance.getArrayType() != Type.OBJECT) {
+ return true;
+ }
+ return WRAPPER_TYPES.contains(arrayInstance.getClassObj().getClassName());
+ }
+
+ private static boolean isCharArray(Object value) {
+ return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.CHAR;
+ }
+
+ private static boolean isByteArray(Object value) {
+ return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.BYTE;
+ }
+
+ static List classInstanceValues(Instance instance) {
+ ClassInstance classInstance = (ClassInstance) instance;
+ return classInstance.getValues();
+ }
+
+ @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" })
+ static T fieldValue(List values, String fieldName) {
+ for (ClassInstance.FieldValue fieldValue : values) {
+ if (fieldValue.getField().getName().equals(fieldName)) {
+ return (T) fieldValue.getValue();
+ }
+ }
+ throw new IllegalArgumentException("Field " + fieldName + " does not exists");
+ }
+
+ static boolean hasField(List values, String fieldName) {
+ for (ClassInstance.FieldValue fieldValue : values) {
+ if (fieldValue.getField().getName().equals(fieldName)) {
+ //noinspection unchecked
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private HahaHelper() {
+ throw new AssertionError();
+ }
+}
diff --git a/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Main.java b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Main.java
new file mode 100644
index 0000000..b9aac36
--- /dev/null
+++ b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Main.java
@@ -0,0 +1,112 @@
+package com.hprof.bitmap;
+
+import com.google.gson.Gson;
+import com.squareup.haha.perflib.ArrayInstance;
+import com.squareup.haha.perflib.ClassInstance;
+import com.squareup.haha.perflib.ClassObj;
+import com.squareup.haha.perflib.Heap;
+import com.squareup.haha.perflib.HprofParser;
+import com.squareup.haha.perflib.Instance;
+import com.squareup.haha.perflib.Snapshot;
+import com.squareup.haha.perflib.io.HprofBuffer;
+import com.squareup.haha.perflib.io.MemoryMappedFileBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Main {
+
+ public static void main(String[] args) throws IOException {
+ if (args != null && args.length >= 1) {
+ File heapDumpFile = new File(args[0]);
+ HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
+ HprofParser parser = new HprofParser(buffer);
+ Snapshot snapshot = parser.parse();
+ snapshot.computeDominators();
+
+ ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");
+ Heap heap = snapshot.getHeap("app");
+ if (heap != null) {
+ Map> bufferMd5Map = new HashMap<>();
+ List bitmapInstances = bitmapClass.getHeapInstances(heap.getId());
+ for (Instance instance : bitmapInstances) {
+ if (instance != null) {
+ ArrayInstance bitmapBuffer = HahaHelper.fieldValue(((ClassInstance) instance).getValues(),
+ "mBuffer");
+ if (bitmapBuffer != null) {
+ String md5 = getMD5(bitmapBuffer.getValues());
+ List list = bufferMd5Map.get(md5);
+ if (list == null) {
+ list = new ArrayList<>();
+ bufferMd5Map.put(md5, list);
+ }
+ list.add(instance);
+ }
+ }
+ }
+ List outputModules = new ArrayList<>();
+ for (Map.Entry> entry : bufferMd5Map.entrySet()) {
+ if (entry.getValue() != null && entry.getValue().size() >= 2) {
+ OutputModule outputModule = new OutputModule();
+ boolean hasSetBaseInfo = false;
+ for (Instance instance : entry.getValue()) {
+ if (!hasSetBaseInfo) {
+ hasSetBaseInfo = true;
+ outputModule.setWidth(HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mWidth"));
+ outputModule.setHeight(HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mHeight"));
+ outputModule.setBufferHash(entry.getKey());
+ outputModule.setDuplicateCount(entry.getValue().size());
+ ArrayInstance bitmapBuffer = HahaHelper.fieldValue(((ClassInstance) instance).getValues(),
+ "mBuffer");
+ outputModule.setBufferSize(bitmapBuffer.getSize());
+ outputModule.setStacks(new ArrayList<>());
+ }
+
+ List stack = new ArrayList<>();
+ do {
+ stack.add(instance.toString());
+ instance = instance.getNextInstanceToGcRoot();
+ } while (instance != null);
+ outputModule.getStacks().add(stack);
+ }
+ outputModules.add(outputModule);
+ }
+ }
+ System.out.println(new Gson().toJson(outputModules));
+ }
+
+ }
+ }
+
+ private static String getMD5(Object[] source) {
+ if (source == null || source.length <= 0) {
+ return null;
+ }
+ byte[] bytes = new byte[source.length];
+ int i = 0;
+ for (Object object : source) {
+ if (object instanceof Byte) {
+ bytes[i++] = (byte) object;
+ }
+ }
+ StringBuilder sb = new StringBuilder();
+ java.security.MessageDigest md5 = null;
+ try {
+ md5 = java.security.MessageDigest.getInstance("MD5");
+ md5.update(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ if (md5 != null) {
+ for (byte b : md5.digest()) {
+ sb.append(String.format("%02X", b));
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/OutputModule.java b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/OutputModule.java
new file mode 100644
index 0000000..75bb4ae
--- /dev/null
+++ b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/OutputModule.java
@@ -0,0 +1,67 @@
+package com.hprof.bitmap;
+
+import java.util.List;
+
+/**
+ * Created by WolfXu on 2019/3/5.
+ *
+ * @author WolfXu
+ * @email wolf.xu@ximalaya.com
+ * @phoneNumber 13670268092
+ */
+public class OutputModule {
+ private int width;
+ private int height;
+ private int bufferSize;
+ private String bufferHash;
+ private int duplicateCount;
+ private List> stacks;
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public String getBufferHash() {
+ return bufferHash;
+ }
+
+ public void setBufferHash(String bufferHash) {
+ this.bufferHash = bufferHash;
+ }
+
+ public int getDuplicateCount() {
+ return duplicateCount;
+ }
+
+ public void setDuplicateCount(int duplicateCount) {
+ this.duplicateCount = duplicateCount;
+ }
+
+ public List> getStacks() {
+ return stacks;
+ }
+
+ public void setStacks(List> stacks) {
+ this.stacks = stacks;
+ }
+}
diff --git a/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Preconditions.java b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Preconditions.java
new file mode 100644
index 0000000..f20165d
--- /dev/null
+++ b/DuplicatedBitmapAnalyzer/src/main/java/com/hprof/bitmap/Preconditions.java
@@ -0,0 +1,20 @@
+package com.hprof.bitmap;
+
+final class Preconditions {
+
+ /**
+ * Returns instance unless it's null.
+ *
+ * @throws NullPointerException if instance is null
+ */
+ static T checkNotNull(T instance, String name) {
+ if (instance == null) {
+ throw new NullPointerException(name + " must not be null");
+ }
+ return instance;
+ }
+
+ private Preconditions() {
+ throw new AssertionError();
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 6262513..f9c0046 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':DuplicatedBitmapAnalyzer'
+include ':DuplicatedBitmapAnalyzer', ':DuplicateBitmapApp'
diff --git a/tools/DuplicatedBitmapAnalyzer-1.0.jar b/tools/DuplicatedBitmapAnalyzer-1.0.jar
index 03621c3..2ff3fb2 100644
Binary files a/tools/DuplicatedBitmapAnalyzer-1.0.jar and b/tools/DuplicatedBitmapAnalyzer-1.0.jar differ