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