diff --git a/DuplicatedBitmapAnalyzer/.gitignore b/DuplicatedBitmapAnalyzer/.gitignore
deleted file mode 100644
index ea68875..0000000
--- a/DuplicatedBitmapAnalyzer/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/build
-/tools_output
\ No newline at end of file
diff --git a/DuplicatedBitmapAnalyzer/build.gradle b/DuplicatedBitmapAnalyzer/build.gradle
deleted file mode 100644
index 912e192..0000000
--- a/DuplicatedBitmapAnalyzer/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-apply plugin: 'java'
-
-
-
-version 1.0
-
-dependencies {
- implementation fileTree(include: ['*.jar'], dir: 'libs')
-}
-
-
-jar {
- manifest {
- attributes 'Main-Class': 'com.hprof.bitmap.Main'
- attributes 'Manifest-Version': version
- }
-
- from {
- exclude 'META-INF/MANIFEST.MF'
- exclude 'META-INF/*.SF'
- exclude 'META-INF/*.DSA'
- exclude 'META-INF/*.RSA'
- configurations.runtime.resolve().collect {
- it.isDirectory() ? it : zipTree(it)
- }
- }
-}
-
-// copy the jar to work directory
-task buildAlloctrackJar(type: Copy, dependsOn: [build, jar]) {
- group = "buildTool"
- from('build/libs') {
- include '*.jar'
- exclude '*-javadoc.jar'
- exclude '*-sources.jar'
- }
- into(rootProject.file("tools"))
-}
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/.gitignore b/duplicatedbitmapanalyzer/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/duplicatedbitmapanalyzer/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/duplicatedbitmapanalyzer/build.gradle b/duplicatedbitmapanalyzer/build.gradle
new file mode 100644
index 0000000..24fc27c
--- /dev/null
+++ b/duplicatedbitmapanalyzer/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 26
+
+
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ api 'com.squareup.haha:haha:2.0.4'
+}
diff --git a/duplicatedbitmapanalyzer/proguard-rules.pro b/duplicatedbitmapanalyzer/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/duplicatedbitmapanalyzer/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/duplicatedbitmapanalyzer/src/androidTest/java/com/example/duplicatedbitmapanalyzer/ExampleInstrumentedTest.java b/duplicatedbitmapanalyzer/src/androidTest/java/com/example/duplicatedbitmapanalyzer/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..16a27f5
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/androidTest/java/com/example/duplicatedbitmapanalyzer/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.duplicatedbitmapanalyzer;
+
+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.example.duplicatedbitmapanalyzer.test", appContext.getPackageName());
+ }
+}
diff --git a/duplicatedbitmapanalyzer/src/main/AndroidManifest.xml b/duplicatedbitmapanalyzer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b984502
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/DuplicatedBitmapCheck.java b/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/DuplicatedBitmapCheck.java
new file mode 100644
index 0000000..67e54a2
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/DuplicatedBitmapCheck.java
@@ -0,0 +1,185 @@
+package com.hprof.bitmap;
+
+import android.os.Debug;
+import android.text.TextUtils;
+
+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.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DuplicatedBitmapCheck {
+ private File mFile;
+
+ private DuplicatedBitmapCheck() {
+ }
+
+ private static class DuplicatedBitmapCheckHolder {
+ private static final DuplicatedBitmapCheck sInstance = new DuplicatedBitmapCheck();
+ }
+
+ public static DuplicatedBitmapCheck getInstance() {
+ return DuplicatedBitmapCheckHolder.sInstance;
+ }
+
+ public void dump(File file) {
+ //1.手动出发一次GC后获取hprof文件
+ triggerGc();
+ try {
+ //生成了Hprof文件
+ Debug.dumpHprofData(file.getAbsolutePath());
+ mFile = file;
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+
+ }
+
+ public void analyze() {
+ if (mFile == null || !mFile.exists()) {
+ return;
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // 打开hprof文件
+ HprofBuffer bufferr = null;
+ try {
+ bufferr = new MemoryMappedFileBuffer(mFile);
+
+ HprofParser parser = new HprofParser(bufferr);
+ Snapshot snapshot = parser.parse();
+ // 获得Bitmap Class
+ final ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");
+
+ // 获得heap, 只需要分析app和default heap即可
+ Heap heap = snapshot.getHeap("app");
+ // 从heap中获得所有的Bitmap实例
+ final List bitmapInstances = bitmapClass.getHeapInstances(heap.getId());
+ // 从Bitmap实例中获得buffer数组
+ if (bitmapInstances == null || bitmapInstances.size() == 0) {
+ //不存在相同的bitmap
+ return;
+ }
+
+ int[] buffers = new int[bitmapInstances.size()];
+ for (int i = 0; i < bitmapInstances.size(); i++) {
+ ArrayInstance arrayInstance = HahaHelper.fieldValue(((ClassInstance) bitmapInstances.get(i)).getValues(), "mBuffer");
+ buffers[i] = Arrays.hashCode(arrayInstance.getValues());
+
+ }
+ HashMap> map = new HashMap<>();
+ for (int i = 0; i < buffers.length; i++) {
+ if (!map.containsKey(buffers[i])) {
+ ArrayList list = new ArrayList<>();
+ list.add(bitmapInstances.get(i));
+ map.put(buffers[i], list);
+ } else {
+ continue;
+ }
+ for (int j = i + 1; j < buffers.length; j++) {
+ if (buffers[i] == buffers[j]) {
+ map.get(buffers[i]).add(bitmapInstances.get(j));
+ }
+ }
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry> entry : map.entrySet()) {
+ if (entry.getValue().size() > 1) {
+ sb.append("duplicateCount:" + entry.getValue().size() + "\n");
+ List instances = entry.getValue();
+ sb.append("stacks:" + "\n");
+ String[] infos = new String[instances.size()];
+ for (int i = 0; i < instances.size(); i++) {
+ infos[i] = getTraceString(getTraceFromInstance(instances.get(i)));
+ }
+ Instance instance = instances.get(0);
+ sb.append(Arrays.toString(infos) + "\n");
+ sb.append("bufferHashcode:" + entry.getKey() + "\n");
+ int width = HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mWidth");
+ int height = HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mHeight");
+ sb.append("width:" + width + "\n");
+ sb.append("height:" + height + "\n");
+ ArrayInstance arrayInstance = HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mBuffer");
+
+ sb.append("bufferSize:" + arrayInstance.getValues().length);
+ }
+ }
+ if (TextUtils.isEmpty(sb.toString())) {
+ sb.append("no duplicatedBitmap" + "\n");
+ }
+ if (mDuplicatedBitmapListener != null) {
+ mDuplicatedBitmapListener.bitmapInfo(sb.toString());
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+
+ }
+
+ public String getTraceString(ArrayList trace) {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (trace != null && trace.size() > 0) {
+ for (Instance instance : trace) {
+ stringBuilder.append(instance.getClassObj().getClassName());
+ stringBuilder.append("\n");
+ }
+ }
+ return stringBuilder.toString();
+ }
+
+ private static ArrayList getTraceFromInstance(Instance instance) {
+ ArrayList arrayList = new ArrayList<>();
+ Instance nextInstance = null;
+ while ((nextInstance = instance.getNextInstanceToGcRoot()) != null) {
+ arrayList.add(nextInstance);
+ instance = nextInstance;
+ }
+ return arrayList;
+ }
+
+
+ private void triggerGc() {
+ Runtime.getRuntime().gc();
+ enqueueReferences();
+ System.runFinalization();
+ }
+
+ private void enqueueReferences() {
+ // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
+ // references to the appropriate queues.
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public DuplicatedBitmapListener mDuplicatedBitmapListener;
+
+ public void setDuplicatedBitmapListener(DuplicatedBitmapListener listener) {
+ mDuplicatedBitmapListener = listener;
+ }
+
+ public interface DuplicatedBitmapListener {
+ void bitmapInfo(String info);
+ }
+}
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..0c907c9
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/HahaHelper.java
@@ -0,0 +1,179 @@
+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 com.hprof.bitmap.Preconditions.checkNotNull;
+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) {
+ checkNotNull(stringObject, "stringObject");
+ Instance instance = (Instance) stringObject;
+ List values = classInstanceValues(instance);
+
+ Integer count = fieldValue(values, "count");
+ checkNotNull(count, "count");
+ if (count == 0) {
+ return "";
+ }
+
+ Object value = fieldValue(values, "value");
+ 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");
+ 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/Preconditions.java b/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/Preconditions.java
new file mode 100755
index 0000000..c71a950
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/Preconditions.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+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/duplicatedbitmapanalyzer/src/main/res/values/strings.xml b/duplicatedbitmapanalyzer/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ecb3ca7
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ DuplicatedBitmapAnalyzer
+
diff --git a/duplicatedbitmapanalyzer/src/test/java/com/example/duplicatedbitmapanalyzer/ExampleUnitTest.java b/duplicatedbitmapanalyzer/src/test/java/com/example/duplicatedbitmapanalyzer/ExampleUnitTest.java
new file mode 100644
index 0000000..1fb9e09
--- /dev/null
+++ b/duplicatedbitmapanalyzer/src/test/java/com/example/duplicatedbitmapanalyzer/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.duplicatedbitmapanalyzer;
+
+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/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..8538d10
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 26
+
+
+
+ defaultConfig {
+ applicationId "com.example.sample"
+ minSdkVersion 15
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:26.0.2'
+ 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'
+ implementation project(':duplicatedbitmapanalyzer')
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/sample/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/sample/src/androidTest/java/com/example/sample/ExampleInstrumentedTest.java b/sample/src/androidTest/java/com/example/sample/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..de07f2f
--- /dev/null
+++ b/sample/src/androidTest/java/com/example/sample/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.sample;
+
+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.example.sample", appContext.getPackageName());
+ }
+}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d95078b
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/com/example/sample/MainActivity.java b/sample/src/main/java/com/example/sample/MainActivity.java
new file mode 100644
index 0000000..8fc5b5d
--- /dev/null
+++ b/sample/src/main/java/com/example/sample/MainActivity.java
@@ -0,0 +1,64 @@
+package com.example.sample;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import com.hprof.bitmap.DuplicatedBitmapCheck;
+
+import java.io.File;
+import java.io.IOException;
+
+public class MainActivity extends AppCompatActivity {
+ private int count=0;
+ private DuplicatedBitmapCheck mDuplicatedBitmapCheck;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ final ImageView iv1=findViewById(R.id.iv1);
+ final ImageView iv2=findViewById(R.id.iv2);
+ final ImageView iv3=findViewById(R.id.iv3);
+ Button btn=findViewById(R.id.btn);
+ Button btn2=findViewById(R.id.btn2);
+ Bitmap bitmap1=BitmapFactory.decodeResource(getResources(),R.mipmap.dict);
+ Bitmap bitmap2=BitmapFactory.decodeResource(getResources(),R.mipmap.dict);
+ Bitmap bitmap3=BitmapFactory.decodeResource(getResources(),R.mipmap.dict);
+ iv1.setImageBitmap(bitmap1);
+ iv2.setImageBitmap(bitmap2);
+ iv3.setImageBitmap(bitmap3);
+ mDuplicatedBitmapCheck = DuplicatedBitmapCheck.getInstance();
+ mDuplicatedBitmapCheck.setDuplicatedBitmapListener(new DuplicatedBitmapCheck.DuplicatedBitmapListener() {
+ @Override
+ public void bitmapInfo(String info) {
+ System.out.println(info);
+ }
+ });
+ btn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ File file=new File(getExternalCacheDir(),"dump");
+ if(!file.exists()){
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ mDuplicatedBitmapCheck.dump(file);
+
+ }
+ });
+ btn2.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mDuplicatedBitmapCheck.analyze();
+ }
+ });
+ }
+}
diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..91e2886
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/doc/4e2b2a183d1b48bcf2b12af45afdbc12.png b/sample/src/main/res/mipmap-xxhdpi/dict.png
similarity index 100%
rename from doc/4e2b2a183d1b48bcf2b12af45afdbc12.png
rename to sample/src/main/res/mipmap-xxhdpi/dict.png
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d17b2cd
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ sample
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/sample/src/test/java/com/example/sample/ExampleUnitTest.java b/sample/src/test/java/com/example/sample/ExampleUnitTest.java
new file mode 100644
index 0000000..6151c57
--- /dev/null
+++ b/sample/src/test/java/com/example/sample/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.sample;
+
+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/settings.gradle b/settings.gradle
index 6262513..73ee6a1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':DuplicatedBitmapAnalyzer'
+include ':sample', ':duplicatedbitmapanalyzer'
diff --git a/tools/DuplicatedBitmapAnalyzer-1.0.jar b/tools/DuplicatedBitmapAnalyzer-1.0.jar
index 03621c3..c668b37 100644
Binary files a/tools/DuplicatedBitmapAnalyzer-1.0.jar and b/tools/DuplicatedBitmapAnalyzer-1.0.jar differ