diff --git a/build.gradle b/build.gradle index cdf1d5d..4338523 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { +buildscript { + ext.kotlin_version = '1.3.11' + repositories { google() jcenter() @@ -11,7 +13,7 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - } + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { diff --git a/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt b/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d09dd8e --- /dev/null +++ b/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.ld.hprofanalyze + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.ld.hprofanalyze", appContext.packageName) + } +} diff --git a/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt b/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt new file mode 100644 index 0000000..f8b1187 --- /dev/null +++ b/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt @@ -0,0 +1,149 @@ +package com.ld.hprofanalyze + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.os.Debug +import android.os.Environment +import android.support.v4.app.ActivityCompat +import android.support.v4.content.ContextCompat +import com.squareup.haha.perflib.io.MemoryMappedFileBuffer +import kotlinx.android.synthetic.main.activity_test.* +import java.io.File +import java.util.* +import kotlin.collections.ArrayList +import android.util.Log +import com.squareup.haha.perflib.* +import java.lang.StringBuilder +import java.lang.reflect.Array + + +private const val WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 100 +private const val TAG = "TestActivity" + +class TestActivity : AppCompatActivity() { + private var externalReportPath: File? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + WRITE_EXTERNAL_STORAGE_REQUEST_CODE) + } else { + initExternalReportPath() + afterPermissionGrant() + + } + + } + + override fun onRequestPermissionsResult( + requestCode: Int, permissions: kotlin.Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + initExternalReportPath() + afterPermissionGrant() + } + + private fun initExternalReportPath() { + externalReportPath = File(Environment.getExternalStorageDirectory(), "hprofdir") + if (externalReportPath?.exists() ?: false) { + externalReportPath?.mkdirs() + } + + } + + private fun afterPermissionGrant() { +// 1、构造内存泄漏(重复加载同一张图片) + var bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.test) + image1.setImageBitmap(bitmap1) + var bitmap2 = BitmapFactory.decodeResource(resources, R.drawable.test) + image2.setImageBitmap(bitmap2) + var bitmap3 = BitmapFactory.decodeResource(resources, R.drawable.test) + image3.setImageBitmap(bitmap3) + var bitmap4 = BitmapFactory.decodeResource(resources, R.drawable.test) + image4.setImageBitmap(bitmap4) + dump.setOnClickListener { + Debug.dumpHprofData(externalReportPath?.absolutePath) + } + parse.setOnClickListener { + // 2、Haha库使用,生成内存快照。 + var memoryMappedFileBuffer = MemoryMappedFileBuffer(externalReportPath) + var hprofParser = HprofParser(memoryMappedFileBuffer) + var snapshot = hprofParser.parse() + snapshot.computeDominators() + var heaps = snapshot.heaps + heaps = heaps.filter { + it.name.equals("app") || it.name.equals("default") + } +// 3、获取bitmap相关的对象的内存快照。 + var bitmapClass = snapshot.findClass("android.graphics.Bitmap") + var bitmaplist = arrayListOf() + heaps.forEach { + bitmaplist.addAll(bitmapClass.getHeapInstances(it.id)) + } + var bitmapMapToBuffer = mutableMapOf() +// 4、构建map key:bitmap内存映射对象,value:bitmap Class 的mbuffer字段值 + bitmaplist.forEach { + (it as ClassInstance).values.forEach { buffer -> + if (buffer.field.name.equals("mBuffer")) { + bitmapMapToBuffer[it] = buffer.value as ArrayInstance + } + } + } +// 5、构建map key:步骤4中的mbuffer字段的数组的hashcode,value:拥有相同hashcode的Instance 集合。 + var result = mutableMapOf>() + bitmapMapToBuffer.forEach { instance, byteBuffer -> + var hashCode = Arrays.hashCode(byteBuffer.values) + if (result.containsKey(hashCode)) { + result[hashCode]?.add(instance) + } else { + result[hashCode] = arrayListOf(instance) + } + } +// 6、过滤步骤5中map,如果value(list)的size大于1,就表示有多个instance的mbuffer是一样的,就可以被认为是内存泄漏了。 + var filterResult = result.filter { + it.component2().size > 1 + } + Log.d(TAG, "发现重复bitmap 数量为 ${filterResult.size}") +// 7、获取bitmap的宽高和buffer长度 + filterResult.forEach { hashcode, instances -> + var leak = LeakObjectDetaial() + instances.forEach { + (it as ClassInstance).values.forEach { fieldValue -> + leak.leakCount++ + when (fieldValue.field.name) { + "mWidth" -> leak.width = fieldValue.value as Int + "mHeight" -> leak.height = fieldValue.value as Int + "mBuffer" -> leak.bufferSize = (fieldValue.value as ArrayInstance).values.size + } + } +// 8、获取bitmap的引用链。 + var stackFrame = StringBuilder() + stackFrame.append(it.classObj.className).append("\n") + var nextInstanceToGcRoot = it.nextInstanceToGcRoot + while (nextInstanceToGcRoot != null) { + nextInstanceToGcRoot.classObj + stackFrame.insert(0, nextInstanceToGcRoot.classObj.className + "\n") + nextInstanceToGcRoot = nextInstanceToGcRoot.nextInstanceToGcRoot + } + leak.stacks = stackFrame.toString() + Log.d(TAG, "width:${leak.width},height:${leak.height},buffersize:${leak.bufferSize},stack:${leak.stacks}") + } + } + } + } + + class LeakObjectDetaial { + var leakCount: Int = 0 + var stacks: String = "" + var width: Int = 0 + var height: Int = 0 + var bufferSize: Int = 0 + } + +} diff --git a/hprofanalyze/src/main/res/drawable/test.png b/hprofanalyze/src/main/res/drawable/test.png new file mode 100644 index 0000000..c6d00dc Binary files /dev/null and b/hprofanalyze/src/main/res/drawable/test.png differ diff --git a/hprofanalyze/src/main/res/layout/activity_main2.xml b/hprofanalyze/src/main/res/layout/activity_main2.xml new file mode 100644 index 0000000..e696c9d --- /dev/null +++ b/hprofanalyze/src/main/res/layout/activity_main2.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/hprofanalyze/src/main/res/layout/activity_test.xml b/hprofanalyze/src/main/res/layout/activity_test.xml new file mode 100644 index 0000000..4f01a1e --- /dev/null +++ b/hprofanalyze/src/main/res/layout/activity_test.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + +