Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions hprof/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
39 changes: 39 additions & 0 deletions hprof/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
21 changes: 21 additions & 0 deletions hprof/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@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());
}
}
23 changes: 23 additions & 0 deletions hprof/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hprof">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Chapter04">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
53 changes: 53 additions & 0 deletions hprof/src/main/java/com/example/hprof/MainActivity.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
});
}
});
}
}
180 changes: 180 additions & 0 deletions hprof/src/main/java/com/example/hprof/util/HahaHelper.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<ClassInstance.FieldValue> 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<ClassInstance.FieldValue> 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<ClassInstance.FieldValue> classInstanceValues(Instance instance) {
ClassInstance classInstance = (ClassInstance) instance;
return classInstance.getValues();
}

@SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" })
static <T> T fieldValue(List<ClassInstance.FieldValue> 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<ClassInstance.FieldValue> 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();
}
}
Loading