From c1067d22547d4162dbab805d31fdf57a607d0893 Mon Sep 17 00:00:00 2001 From: ex-liweikun Date: Sun, 23 Dec 2018 17:53:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90bitmap=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E7=9A=84=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuplicatedBitmapAnalyzer/.gitignore | 2 - DuplicatedBitmapAnalyzer/build.gradle | 38 ---- .../src/com/hprof/bitmap/Main.java | 13 -- duplicatedbitmapanalyzer/.gitignore | 1 + duplicatedbitmapanalyzer/build.gradle | 29 +++ duplicatedbitmapanalyzer/proguard-rules.pro | 21 ++ .../ExampleInstrumentedTest.java | 26 +++ .../src/main/AndroidManifest.xml | 2 + .../hprof/bitmap/DuplicatedBitmapCheck.java | 185 ++++++++++++++++++ .../java/com/hprof/bitmap/HahaHelper.java | 179 +++++++++++++++++ .../java/com/hprof/bitmap/Preconditions.java | 35 ++++ .../src/main/res/values/strings.xml | 3 + .../ExampleUnitTest.java | 17 ++ sample/.gitignore | 1 + sample/build.gradle | 36 ++++ sample/proguard-rules.pro | 21 ++ .../sample/ExampleInstrumentedTest.java | 26 +++ sample/src/main/AndroidManifest.xml | 21 ++ .../java/com/example/sample/MainActivity.java | 64 ++++++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ sample/src/main/res/layout/activity_main.xml | 31 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../src/main/res/mipmap-xxhdpi/dict.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes sample/src/main/res/values/colors.xml | 6 + sample/src/main/res/values/strings.xml | 3 + sample/src/main/res/values/styles.xml | 11 ++ .../com/example/sample/ExampleUnitTest.java | 17 ++ settings.gradle | 2 +- tools/DuplicatedBitmapAnalyzer-1.0.jar | Bin 294 -> 294 bytes 41 files changed, 950 insertions(+), 54 deletions(-) delete mode 100644 DuplicatedBitmapAnalyzer/.gitignore delete mode 100644 DuplicatedBitmapAnalyzer/build.gradle delete mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java create mode 100644 duplicatedbitmapanalyzer/.gitignore create mode 100644 duplicatedbitmapanalyzer/build.gradle create mode 100644 duplicatedbitmapanalyzer/proguard-rules.pro create mode 100644 duplicatedbitmapanalyzer/src/androidTest/java/com/example/duplicatedbitmapanalyzer/ExampleInstrumentedTest.java create mode 100644 duplicatedbitmapanalyzer/src/main/AndroidManifest.xml create mode 100644 duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/DuplicatedBitmapCheck.java create mode 100644 duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/HahaHelper.java create mode 100755 duplicatedbitmapanalyzer/src/main/java/com/hprof/bitmap/Preconditions.java create mode 100644 duplicatedbitmapanalyzer/src/main/res/values/strings.xml create mode 100644 duplicatedbitmapanalyzer/src/test/java/com/example/duplicatedbitmapanalyzer/ExampleUnitTest.java create mode 100644 sample/.gitignore create mode 100644 sample/build.gradle create mode 100644 sample/proguard-rules.pro create mode 100644 sample/src/androidTest/java/com/example/sample/ExampleInstrumentedTest.java create mode 100644 sample/src/main/AndroidManifest.xml create mode 100644 sample/src/main/java/com/example/sample/MainActivity.java create mode 100644 sample/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 sample/src/main/res/drawable/ic_launcher_background.xml create mode 100644 sample/src/main/res/layout/activity_main.xml create mode 100644 sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 sample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename doc/4e2b2a183d1b48bcf2b12af45afdbc12.png => sample/src/main/res/mipmap-xxhdpi/dict.png (100%) create mode 100644 sample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/values/colors.xml create mode 100644 sample/src/main/res/values/strings.xml create mode 100644 sample/src/main/res/values/styles.xml create mode 100644 sample/src/test/java/com/example/sample/ExampleUnitTest.java 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 @@ + + + + + + +