diff --git a/build.gradle b/build.gradle
index cdf1d5d..d91fd7e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.2.0'
+ classpath 'com.android.tools.build:gradle:7.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b9cc257..5e73eb6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
diff --git a/hprof/.gitignore b/hprof/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/hprof/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/hprof/build.gradle b/hprof/build.gradle
new file mode 100644
index 0000000..319791c
--- /dev/null
+++ b/hprof/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "com.example.hprof"
+ minSdk 21
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.squareup.haha:haha:2.0.4'
+ implementation group: 'com.squareup.leakcanary', name: 'leakcanary-watcher', version: '1.6.2'
+ implementation 'com.android.support.constraint:constraint-layout:2.0.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
\ No newline at end of file
diff --git a/hprof/proguard-rules.pro b/hprof/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/hprof/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
\ No newline at end of file
diff --git a/hprof/src/androidTest/java/com/example/hprof/ExampleInstrumentedTest.java b/hprof/src/androidTest/java/com/example/hprof/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..449b4a4
--- /dev/null
+++ b/hprof/src/androidTest/java/com/example/hprof/ExampleInstrumentedTest.java
@@ -0,0 +1,25 @@
+package com.example.hprof;
+
+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.getInstrumentation().getTargetContext();
+ assertEquals("com.example.hprof", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/hprof/src/main/AndroidManifest.xml b/hprof/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..14108f1
--- /dev/null
+++ b/hprof/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/java/com/example/hprof/MainActivity.java b/hprof/src/main/java/com/example/hprof/MainActivity.java
new file mode 100644
index 0000000..e5fa065
--- /dev/null
+++ b/hprof/src/main/java/com/example/hprof/MainActivity.java
@@ -0,0 +1,53 @@
+package com.example.hprof;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Debug;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.example.hprof.util.HprofAnalysis;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.down_faile);
+ Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.down_focus);
+ Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.down_faile);
+ Bitmap bitmap4 = BitmapFactory.decodeResource(getResources(), R.drawable.down_focus);
+
+ ((ImageView) findViewById(R.id.image1)).setImageBitmap(bitmap1);
+ ((ImageView) findViewById(R.id.image3)).setImageBitmap(bitmap2);
+ ((ImageView) findViewById(R.id.image2)).setImageBitmap(bitmap3);
+ ((ImageView) findViewById(R.id.image4)).setImageBitmap(bitmap4);
+
+ findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ File file = new File(getExternalCacheDir(), "dump.hprof");
+ Executors.newSingleThreadExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Debug.dumpHprofData(file.getAbsolutePath());
+ HprofAnalysis.analysis(file.getAbsolutePath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/hprof/src/main/java/com/example/hprof/util/HahaHelper.java b/hprof/src/main/java/com/example/hprof/util/HahaHelper.java
new file mode 100644
index 0000000..31c68ad
--- /dev/null
+++ b/hprof/src/main/java/com/example/hprof/util/HahaHelper.java
@@ -0,0 +1,180 @@
+package com.example.hprof.util;
+
+import static com.example.hprof.util.Preconditions.checkNotNull;
+
+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) {
+ 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/hprof/src/main/java/com/example/hprof/util/HprofAnalysis.java b/hprof/src/main/java/com/example/hprof/util/HprofAnalysis.java
new file mode 100644
index 0000000..061bd45
--- /dev/null
+++ b/hprof/src/main/java/com/example/hprof/util/HprofAnalysis.java
@@ -0,0 +1,110 @@
+package com.example.hprof.util;
+
+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 HprofAnalysis {
+
+ public static void analysis(String hprofPath) throws IOException {
+ if (TextUtils.isEmpty(hprofPath)) {
+ return;
+ }
+ File hprofFile = new File(hprofPath);
+ if (!hprofFile.exists()) {
+ return;
+ }
+ HprofBuffer dataBuffer = new MemoryMappedFileBuffer(hprofFile);
+ HprofParser parser = new HprofParser(dataBuffer);
+ final Snapshot snapshot = parser.parse();
+ snapshot.computeDominators();
+ final ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");
+ Heap appHeap = snapshot.getHeap("app");
+ // 拿到所有的 Bitmap 实例
+ final List bitmapInstances = bitmapClass.getHeapInstances(appHeap.getId());
+ if (bitmapInstances == null || bitmapInstances.size() <= 1) {
+ return;
+ }
+
+ int[] buffers = new int[bitmapInstances.size()];
+ for (int i = 0; i < bitmapInstances.size(); i++) {
+ Instance bitmapInstance = bitmapInstances.get(i);
+ // mBuffer 是一个 byte[]
+ ArrayInstance arrayInstance = HahaHelper.fieldValue(((ClassInstance) bitmapInstance).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])) {
+ List list = new ArrayList<>();
+ list.add(bitmapInstances.get(i));
+ map.put(buffers[i], list);
+ } else {
+ map.get(buffers[i]).add(bitmapInstances.get(i));
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry> entry : map.entrySet()) {
+ if (entry.getValue().size() > 1) {
+ sb.append("\"duplcateCount\":" + entry.getValue().size());
+ sb.append("\n");
+ sb.append("\"stacks\": \n");
+ List instanceList = entry.getValue();
+ for (int i = 0; i < instanceList.size(); i++) {
+ sb.append("===================================================== \n");
+ sb.append(getTraceString(getTraceFromInstance(instanceList.get(i))));
+ sb.append("===================================================== \n");
+ }
+
+ sb.append("\"bufferHashcode\":").append("\"").append(entry.getKey().toString()).append("\"\n");
+ int width = HahaHelper.fieldValue(((ClassInstance) entry.getValue().get(0)).getValues(), "mWidth");
+ int height = HahaHelper.fieldValue(((ClassInstance) entry.getValue().get(0)).getValues(), "mHeight");
+ sb.append("\"width\":" + width + "\n");
+ sb.append("\"height\":" + height + "\n");
+ sb.append("\"bufferSize\":" + entry.getValue().get(0).getSize() + "\n");
+ sb.append("----------------------------------------------------- \n");
+ }
+ }
+ if (!sb.toString().isEmpty()) {
+ System.out.println(sb);
+ }
+ }
+
+ public static ArrayList getTraceFromInstance(Instance instance) {
+ ArrayList arrayList = new ArrayList<>();
+ //Instance nextInstance = null;
+ while(instance != null && instance.getDistanceToGcRoot() != 0 && instance.getDistanceToGcRoot() != Integer.MAX_VALUE) {
+ arrayList.add(instance);
+ instance = instance.getNextInstanceToGcRoot();
+ }
+ return arrayList;
+ }
+
+ public static String getTraceString(List instances) {
+ StringBuilder sb = new StringBuilder();
+ if (instances.size() > 0) {
+ for (Instance instance : instances) {
+ sb.append(instance.getClassObj().getClassName());
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/hprof/src/main/java/com/example/hprof/util/Preconditions.java b/hprof/src/main/java/com/example/hprof/util/Preconditions.java
new file mode 100644
index 0000000..797cb60
--- /dev/null
+++ b/hprof/src/main/java/com/example/hprof/util/Preconditions.java
@@ -0,0 +1,20 @@
+package com.example.hprof.util;
+
+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/hprof/src/main/res/drawable-v24/ic_launcher_foreground.xml b/hprof/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/hprof/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/res/drawable/down_faile.png b/hprof/src/main/res/drawable/down_faile.png
new file mode 100644
index 0000000..38d9357
Binary files /dev/null and b/hprof/src/main/res/drawable/down_faile.png differ
diff --git a/hprof/src/main/res/drawable/down_focus.png b/hprof/src/main/res/drawable/down_focus.png
new file mode 100644
index 0000000..7170b0d
Binary files /dev/null and b/hprof/src/main/res/drawable/down_focus.png differ
diff --git a/hprof/src/main/res/drawable/ic_launcher_background.xml b/hprof/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/hprof/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hprof/src/main/res/layout/activity_main.xml b/hprof/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e757328
--- /dev/null
+++ b/hprof/src/main/res/layout/activity_main.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/hprof/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/res/mipmap-hdpi/ic_launcher.webp b/hprof/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/hprof/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/hprof/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/hprof/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/hprof/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/hprof/src/main/res/mipmap-mdpi/ic_launcher.webp b/hprof/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/hprof/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/hprof/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/hprof/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/hprof/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/hprof/src/main/res/mipmap-xhdpi/ic_launcher.webp b/hprof/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/hprof/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/hprof/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/hprof/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/hprof/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/hprof/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/hprof/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/hprof/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/hprof/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/hprof/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/hprof/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/hprof/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/hprof/src/main/res/values-night/themes.xml b/hprof/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..8d829b7
--- /dev/null
+++ b/hprof/src/main/res/values-night/themes.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/main/res/values/colors.xml b/hprof/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/hprof/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/hprof/src/main/res/values/strings.xml b/hprof/src/main/res/values/strings.xml
new file mode 100644
index 0000000..3de1a39
--- /dev/null
+++ b/hprof/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ My Application
+
\ No newline at end of file
diff --git a/hprof/src/main/res/values/themes.xml b/hprof/src/main/res/values/themes.xml
new file mode 100644
index 0000000..e000f76
--- /dev/null
+++ b/hprof/src/main/res/values/themes.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/hprof/src/test/java/com/example/hprof/ExampleUnitTest.java b/hprof/src/test/java/com/example/hprof/ExampleUnitTest.java
new file mode 100644
index 0000000..9d4c9fe
--- /dev/null
+++ b/hprof/src/test/java/com/example/hprof/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.hprof;
+
+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..b94cf9e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
include ':DuplicatedBitmapAnalyzer'
+include ':hprof'